2019-05-27 14:55:05 +08:00
|
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
2005-04-17 06:20:36 +08:00
|
|
|
/*
|
|
|
|
* Digital Audio (PCM) abstract layer
|
2007-10-15 15:50:19 +08:00
|
|
|
* Copyright (c) by Jaroslav Kysela <perex@perex.cz>
|
2005-04-17 06:20:36 +08:00
|
|
|
*/
|
|
|
|
|
2018-04-24 20:06:14 +08:00
|
|
|
#include <linux/compat.h>
|
2005-04-17 06:20:36 +08:00
|
|
|
#include <linux/mm.h>
|
2011-07-16 00:38:28 +08:00
|
|
|
#include <linux/module.h>
|
2005-04-17 06:20:36 +08:00
|
|
|
#include <linux/file.h>
|
|
|
|
#include <linux/slab.h>
|
2017-02-03 02:15:33 +08:00
|
|
|
#include <linux/sched/signal.h>
|
2005-04-17 06:20:36 +08:00
|
|
|
#include <linux/time.h>
|
2011-08-25 21:35:03 +08:00
|
|
|
#include <linux/pm_qos.h>
|
2015-01-28 23:49:33 +08:00
|
|
|
#include <linux/io.h>
|
2009-11-26 19:40:21 +08:00
|
|
|
#include <linux/dma-mapping.h>
|
2019-11-05 16:01:37 +08:00
|
|
|
#include <linux/vmalloc.h>
|
2005-04-17 06:20:36 +08:00
|
|
|
#include <sound/core.h>
|
|
|
|
#include <sound/control.h>
|
|
|
|
#include <sound/info.h>
|
|
|
|
#include <sound/pcm.h>
|
|
|
|
#include <sound/pcm_params.h>
|
|
|
|
#include <sound/timer.h>
|
|
|
|
#include <sound/minors.h>
|
2015-02-23 00:58:50 +08:00
|
|
|
#include <linux/uio.h>
|
ALSA: pcm: Fix starvation on down_write_nonblock()
Commit 67ec1072b053 ("ALSA: pcm: Fix rwsem deadlock for non-atomic PCM
stream") fixes deadlock for non-atomic PCM stream. But, This patch
causes antother stuck.
If writer is RT thread and reader is a normal thread, the reader
thread will be difficult to get scheduled. It may not give chance to
release readlocks and writer gets stuck for a long time if they are
pinned to single cpu.
The deadlock described in the previous commit is because the linux
rwsem queues like a FIFO. So, we might need non-FIFO writelock, not
non-block one.
My suggestion is that the writer gives reader a chance to be scheduled
by using the minimum msleep() instaed of spinning without blocking by
writer. Also, The *_nonblock may be changed to *_nonfifo appropriately
to this concept.
In terms of performance, when trylock is failed, this minimum periodic
msleep will have the same performance as the tick-based
schedule()/wake_up_q().
[ Although this has a fairly high performance penalty, the relevant
code path became already rare due to the previous commit ("ALSA:
pcm: Call snd_pcm_unlink() conditionally at closing"). That is, now
this unconditional msleep appears only when using linked streams,
and this must be a rare case. So we accept this as a quick
workaround until finding a more suitable one -- tiwai ]
Fixes: 67ec1072b053 ("ALSA: pcm: Fix rwsem deadlock for non-atomic PCM stream")
Suggested-by: Wonmin Jung <wonmin.jung@lge.com>
Signed-off-by: Chanho Min <chanho.min@lge.com>
Cc: <stable@vger.kernel.org>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2018-11-26 13:36:37 +08:00
|
|
|
#include <linux/delay.h>
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2017-05-26 08:30:46 +08:00
|
|
|
#include "pcm_local.h"
|
|
|
|
|
2017-06-07 07:46:44 +08:00
|
|
|
#ifdef CONFIG_SND_DEBUG
|
2017-06-07 07:46:43 +08:00
|
|
|
#define CREATE_TRACE_POINTS
|
|
|
|
#include "pcm_param_trace.h"
|
2017-06-07 07:46:44 +08:00
|
|
|
#else
|
|
|
|
#define trace_hw_mask_param_enabled() 0
|
|
|
|
#define trace_hw_interval_param_enabled() 0
|
|
|
|
#define trace_hw_mask_param(substream, type, index, prev, curr)
|
|
|
|
#define trace_hw_interval_param(substream, type, index, prev, curr)
|
|
|
|
#endif
|
2017-06-07 07:46:43 +08:00
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
/*
|
|
|
|
* Compatibility
|
|
|
|
*/
|
|
|
|
|
2005-11-17 20:59:38 +08:00
|
|
|
struct snd_pcm_hw_params_old {
|
2005-04-17 06:20:36 +08:00
|
|
|
unsigned int flags;
|
|
|
|
unsigned int masks[SNDRV_PCM_HW_PARAM_SUBFORMAT -
|
|
|
|
SNDRV_PCM_HW_PARAM_ACCESS + 1];
|
2005-11-17 20:59:38 +08:00
|
|
|
struct snd_interval intervals[SNDRV_PCM_HW_PARAM_TICK_TIME -
|
2005-04-17 06:20:36 +08:00
|
|
|
SNDRV_PCM_HW_PARAM_SAMPLE_BITS + 1];
|
|
|
|
unsigned int rmask;
|
|
|
|
unsigned int cmask;
|
|
|
|
unsigned int info;
|
|
|
|
unsigned int msbits;
|
|
|
|
unsigned int rate_num;
|
|
|
|
unsigned int rate_den;
|
2005-11-17 20:59:38 +08:00
|
|
|
snd_pcm_uframes_t fifo_size;
|
2005-04-17 06:20:36 +08:00
|
|
|
unsigned char reserved[64];
|
|
|
|
};
|
|
|
|
|
2005-12-01 17:51:58 +08:00
|
|
|
#ifdef CONFIG_SND_SUPPORT_OLD_API
|
2005-11-17 20:59:38 +08:00
|
|
|
#define SNDRV_PCM_IOCTL_HW_REFINE_OLD _IOWR('A', 0x10, struct snd_pcm_hw_params_old)
|
|
|
|
#define SNDRV_PCM_IOCTL_HW_PARAMS_OLD _IOWR('A', 0x11, struct snd_pcm_hw_params_old)
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2005-11-17 20:59:38 +08:00
|
|
|
static int snd_pcm_hw_refine_old_user(struct snd_pcm_substream *substream,
|
|
|
|
struct snd_pcm_hw_params_old __user * _oparams);
|
|
|
|
static int snd_pcm_hw_params_old_user(struct snd_pcm_substream *substream,
|
|
|
|
struct snd_pcm_hw_params_old __user * _oparams);
|
2005-12-01 17:51:58 +08:00
|
|
|
#endif
|
2005-11-20 21:06:59 +08:00
|
|
|
static int snd_pcm_open(struct file *file, struct snd_pcm *pcm, int stream);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
/*
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
2014-09-01 17:19:37 +08:00
|
|
|
static DECLARE_RWSEM(snd_pcm_link_rwsem);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2019-01-13 16:35:17 +08:00
|
|
|
void snd_pcm_group_init(struct snd_pcm_group *group)
|
ALSA: pcm: Fix rwsem deadlock for non-atomic PCM stream
A non-atomic PCM stream may take snd_pcm_link_rwsem rw semaphore twice
in the same code path, e.g. one in snd_pcm_action_nonatomic() and
another in snd_pcm_stream_lock(). Usually this is OK, but when a
write lock is issued between these two read locks, the problem
happens: the write lock is blocked due to the first reade lock, and
the second read lock is also blocked by the write lock. This
eventually deadlocks.
The reason is the way rwsem manages waiters; it's queued like FIFO, so
even if the writer itself doesn't take the lock yet, it blocks all the
waiters (including reads) queued after it.
As a workaround, in this patch, we replace the standard down_write()
with an spinning loop. This is far from optimal, but it's good
enough, as the spinning time is supposed to be relatively short for
normal PCM operations, and the code paths requiring the write lock
aren't called so often.
Reported-by: Vinod Koul <vinod.koul@intel.com>
Tested-by: Ramesh Babu <ramesh.babu@intel.com>
Cc: <stable@vger.kernel.org> # v3.18+
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2016-02-17 21:30:26 +08:00
|
|
|
{
|
2019-01-13 16:35:17 +08:00
|
|
|
spin_lock_init(&group->lock);
|
|
|
|
mutex_init(&group->mutex);
|
|
|
|
INIT_LIST_HEAD(&group->substreams);
|
2019-07-19 16:55:05 +08:00
|
|
|
refcount_set(&group->refs, 1);
|
ALSA: pcm: Fix rwsem deadlock for non-atomic PCM stream
A non-atomic PCM stream may take snd_pcm_link_rwsem rw semaphore twice
in the same code path, e.g. one in snd_pcm_action_nonatomic() and
another in snd_pcm_stream_lock(). Usually this is OK, but when a
write lock is issued between these two read locks, the problem
happens: the write lock is blocked due to the first reade lock, and
the second read lock is also blocked by the write lock. This
eventually deadlocks.
The reason is the way rwsem manages waiters; it's queued like FIFO, so
even if the writer itself doesn't take the lock yet, it blocks all the
waiters (including reads) queued after it.
As a workaround, in this patch, we replace the standard down_write()
with an spinning loop. This is far from optimal, but it's good
enough, as the spinning time is supposed to be relatively short for
normal PCM operations, and the code paths requiring the write lock
aren't called so often.
Reported-by: Vinod Koul <vinod.koul@intel.com>
Tested-by: Ramesh Babu <ramesh.babu@intel.com>
Cc: <stable@vger.kernel.org> # v3.18+
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2016-02-17 21:30:26 +08:00
|
|
|
}
|
|
|
|
|
ALSA: pcm: More fine-grained PCM link locking
We have currently two global locks, a rwlock and a rwsem, that are
used for managing linking the PCM streams. Due to these global locks,
once when a linked stream is used, the lock granularity suffers a
lot.
This patch attempts to eliminate the former global lock for atomic
ops. The latter rwsem needs remaining because of the loosy way of the
loop calls in snd_pcm_action_nonatomic(), as well as for avoiding the
deadlock at linking. However, these are used far rarely, actually
only by two actions (prepare and reset), where both are no timing
critical ones. So this can be still seen as a good improvement.
The basic strategy to eliminate the rwlock is to assure group->lock at
adding or removing a stream to / from the group. Since we already
takes the group lock whenever taking the all substream locks under the
group, this shouldn't be a big problem. The reference to group
pointer in snd_pcm_substream object is protected by the stream lock
itself.
However, there are still pitfalls: a race window at re-locking and the
lifecycle of group object. The former is a small race window for
dereferencing the substream group object opened while snd_pcm_action()
performs re-locking to avoid ABBA deadlocks. This includes the unlink
of group during that window, too. And the latter is the kfree
performed after all streams are removed from the group while it's
still dereferenced.
For addressing these corner cases, two new tricks are introduced:
- After re-locking, the group assigned to the stream is checked again;
if the group is changed, we retry the whole procedure.
- Introduce a refcount to snd_pcm_group object, so that it's freed
only when it's empty and really no one refers to it.
(Some readers might wonder why not RCU for the latter. RCU in this
case would cost more than refcounting, unfortunately. We take the
group lock sooner or later, hence the performance improvement by RCU
would be negligible. Meanwhile, because we need to deal with
schedulable context depending on the pcm->nonatomic flag, it'll become
dynamic RCU/SRCU switch, and the grace period may become too long.)
Along with these changes, there are a significant amount of code
refactoring. The complex group re-lock & ref code is factored out to
snd_pcm_stream_group_ref() function, for example.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2019-01-13 16:50:33 +08:00
|
|
|
/* define group lock helpers */
|
|
|
|
#define DEFINE_PCM_GROUP_LOCK(action, mutex_action) \
|
|
|
|
static void snd_pcm_group_ ## action(struct snd_pcm_group *group, bool nonatomic) \
|
|
|
|
{ \
|
|
|
|
if (nonatomic) \
|
|
|
|
mutex_ ## mutex_action(&group->mutex); \
|
|
|
|
else \
|
|
|
|
spin_ ## action(&group->lock); \
|
2018-05-04 23:28:10 +08:00
|
|
|
}
|
|
|
|
|
ALSA: pcm: More fine-grained PCM link locking
We have currently two global locks, a rwlock and a rwsem, that are
used for managing linking the PCM streams. Due to these global locks,
once when a linked stream is used, the lock granularity suffers a
lot.
This patch attempts to eliminate the former global lock for atomic
ops. The latter rwsem needs remaining because of the loosy way of the
loop calls in snd_pcm_action_nonatomic(), as well as for avoiding the
deadlock at linking. However, these are used far rarely, actually
only by two actions (prepare and reset), where both are no timing
critical ones. So this can be still seen as a good improvement.
The basic strategy to eliminate the rwlock is to assure group->lock at
adding or removing a stream to / from the group. Since we already
takes the group lock whenever taking the all substream locks under the
group, this shouldn't be a big problem. The reference to group
pointer in snd_pcm_substream object is protected by the stream lock
itself.
However, there are still pitfalls: a race window at re-locking and the
lifecycle of group object. The former is a small race window for
dereferencing the substream group object opened while snd_pcm_action()
performs re-locking to avoid ABBA deadlocks. This includes the unlink
of group during that window, too. And the latter is the kfree
performed after all streams are removed from the group while it's
still dereferenced.
For addressing these corner cases, two new tricks are introduced:
- After re-locking, the group assigned to the stream is checked again;
if the group is changed, we retry the whole procedure.
- Introduce a refcount to snd_pcm_group object, so that it's freed
only when it's empty and really no one refers to it.
(Some readers might wonder why not RCU for the latter. RCU in this
case would cost more than refcounting, unfortunately. We take the
group lock sooner or later, hence the performance improvement by RCU
would be negligible. Meanwhile, because we need to deal with
schedulable context depending on the pcm->nonatomic flag, it'll become
dynamic RCU/SRCU switch, and the grace period may become too long.)
Along with these changes, there are a significant amount of code
refactoring. The complex group re-lock & ref code is factored out to
snd_pcm_stream_group_ref() function, for example.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2019-01-13 16:50:33 +08:00
|
|
|
DEFINE_PCM_GROUP_LOCK(lock, lock);
|
|
|
|
DEFINE_PCM_GROUP_LOCK(unlock, unlock);
|
|
|
|
DEFINE_PCM_GROUP_LOCK(lock_irq, lock);
|
|
|
|
DEFINE_PCM_GROUP_LOCK(unlock_irq, unlock);
|
2018-05-04 23:28:10 +08:00
|
|
|
|
2014-10-30 22:02:50 +08:00
|
|
|
/**
|
|
|
|
* snd_pcm_stream_lock - Lock the PCM stream
|
|
|
|
* @substream: PCM substream
|
|
|
|
*
|
|
|
|
* This locks the PCM stream's spinlock or mutex depending on the nonatomic
|
|
|
|
* flag of the given substream. This also takes the global link rw lock
|
|
|
|
* (or rw sem), too, for avoiding the race with linked streams.
|
|
|
|
*/
|
2014-09-01 17:19:37 +08:00
|
|
|
void snd_pcm_stream_lock(struct snd_pcm_substream *substream)
|
|
|
|
{
|
2019-01-13 17:15:03 +08:00
|
|
|
snd_pcm_group_lock(&substream->self_group, substream->pcm->nonatomic);
|
2014-09-01 17:19:37 +08:00
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(snd_pcm_stream_lock);
|
|
|
|
|
2014-10-30 22:02:50 +08:00
|
|
|
/**
|
|
|
|
* snd_pcm_stream_lock - Unlock the PCM stream
|
|
|
|
* @substream: PCM substream
|
|
|
|
*
|
|
|
|
* This unlocks the PCM stream that has been locked via snd_pcm_stream_lock().
|
|
|
|
*/
|
2014-09-01 17:19:37 +08:00
|
|
|
void snd_pcm_stream_unlock(struct snd_pcm_substream *substream)
|
|
|
|
{
|
2019-01-13 17:15:03 +08:00
|
|
|
snd_pcm_group_unlock(&substream->self_group, substream->pcm->nonatomic);
|
2014-09-01 17:19:37 +08:00
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(snd_pcm_stream_unlock);
|
|
|
|
|
2014-10-30 22:02:50 +08:00
|
|
|
/**
|
|
|
|
* snd_pcm_stream_lock_irq - Lock the PCM stream
|
|
|
|
* @substream: PCM substream
|
|
|
|
*
|
|
|
|
* This locks the PCM stream like snd_pcm_stream_lock() and disables the local
|
|
|
|
* IRQ (only when nonatomic is false). In nonatomic case, this is identical
|
|
|
|
* as snd_pcm_stream_lock().
|
|
|
|
*/
|
2014-09-01 17:19:37 +08:00
|
|
|
void snd_pcm_stream_lock_irq(struct snd_pcm_substream *substream)
|
|
|
|
{
|
2019-01-13 17:15:03 +08:00
|
|
|
snd_pcm_group_lock_irq(&substream->self_group,
|
|
|
|
substream->pcm->nonatomic);
|
2014-09-01 17:19:37 +08:00
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(snd_pcm_stream_lock_irq);
|
|
|
|
|
2020-06-08 18:06:32 +08:00
|
|
|
static void snd_pcm_stream_lock_nested(struct snd_pcm_substream *substream)
|
|
|
|
{
|
|
|
|
struct snd_pcm_group *group = &substream->self_group;
|
|
|
|
|
|
|
|
if (substream->pcm->nonatomic)
|
|
|
|
mutex_lock_nested(&group->mutex, SINGLE_DEPTH_NESTING);
|
|
|
|
else
|
|
|
|
spin_lock_nested(&group->lock, SINGLE_DEPTH_NESTING);
|
|
|
|
}
|
|
|
|
|
2014-10-30 22:02:50 +08:00
|
|
|
/**
|
|
|
|
* snd_pcm_stream_unlock_irq - Unlock the PCM stream
|
|
|
|
* @substream: PCM substream
|
|
|
|
*
|
|
|
|
* This is a counter-part of snd_pcm_stream_lock_irq().
|
|
|
|
*/
|
2014-09-01 17:19:37 +08:00
|
|
|
void snd_pcm_stream_unlock_irq(struct snd_pcm_substream *substream)
|
|
|
|
{
|
2019-01-13 17:15:03 +08:00
|
|
|
snd_pcm_group_unlock_irq(&substream->self_group,
|
|
|
|
substream->pcm->nonatomic);
|
2014-09-01 17:19:37 +08:00
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(snd_pcm_stream_unlock_irq);
|
|
|
|
|
|
|
|
unsigned long _snd_pcm_stream_lock_irqsave(struct snd_pcm_substream *substream)
|
|
|
|
{
|
2019-01-13 17:15:03 +08:00
|
|
|
unsigned long flags = 0;
|
|
|
|
if (substream->pcm->nonatomic)
|
|
|
|
mutex_lock(&substream->self_group.mutex);
|
|
|
|
else
|
|
|
|
spin_lock_irqsave(&substream->self_group.lock, flags);
|
|
|
|
return flags;
|
2014-09-01 17:19:37 +08:00
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(_snd_pcm_stream_lock_irqsave);
|
|
|
|
|
2014-10-30 22:02:50 +08:00
|
|
|
/**
|
|
|
|
* snd_pcm_stream_unlock_irqrestore - Unlock the PCM stream
|
|
|
|
* @substream: PCM substream
|
|
|
|
* @flags: irq flags
|
|
|
|
*
|
|
|
|
* This is a counter-part of snd_pcm_stream_lock_irqsave().
|
|
|
|
*/
|
2014-09-01 17:19:37 +08:00
|
|
|
void snd_pcm_stream_unlock_irqrestore(struct snd_pcm_substream *substream,
|
|
|
|
unsigned long flags)
|
|
|
|
{
|
2019-01-13 17:15:03 +08:00
|
|
|
if (substream->pcm->nonatomic)
|
|
|
|
mutex_unlock(&substream->self_group.mutex);
|
|
|
|
else
|
|
|
|
spin_unlock_irqrestore(&substream->self_group.lock, flags);
|
2014-09-01 17:19:37 +08:00
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(snd_pcm_stream_unlock_irqrestore);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2019-11-17 16:53:03 +08:00
|
|
|
/* Run PCM ioctl ops */
|
|
|
|
static int snd_pcm_ops_ioctl(struct snd_pcm_substream *substream,
|
|
|
|
unsigned cmd, void *arg)
|
|
|
|
{
|
|
|
|
if (substream->ops->ioctl)
|
|
|
|
return substream->ops->ioctl(substream, cmd, arg);
|
|
|
|
else
|
|
|
|
return snd_pcm_lib_ioctl(substream, cmd, arg);
|
|
|
|
}
|
|
|
|
|
2005-11-17 20:59:38 +08:00
|
|
|
int snd_pcm_info(struct snd_pcm_substream *substream, struct snd_pcm_info *info)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2005-11-17 20:59:38 +08:00
|
|
|
struct snd_pcm *pcm = substream->pcm;
|
|
|
|
struct snd_pcm_str *pstr = substream->pstr;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
memset(info, 0, sizeof(*info));
|
|
|
|
info->card = pcm->card->number;
|
|
|
|
info->device = pcm->device;
|
|
|
|
info->stream = substream->stream;
|
|
|
|
info->subdevice = substream->number;
|
|
|
|
strlcpy(info->id, pcm->id, sizeof(info->id));
|
|
|
|
strlcpy(info->name, pcm->name, sizeof(info->name));
|
|
|
|
info->dev_class = pcm->dev_class;
|
|
|
|
info->dev_subclass = pcm->dev_subclass;
|
|
|
|
info->subdevices_count = pstr->substream_count;
|
|
|
|
info->subdevices_avail = pstr->substream_count - pstr->substream_opened;
|
|
|
|
strlcpy(info->subname, substream->name, sizeof(info->subname));
|
2017-06-14 18:30:03 +08:00
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2005-11-17 20:59:38 +08:00
|
|
|
int snd_pcm_info_user(struct snd_pcm_substream *substream,
|
|
|
|
struct snd_pcm_info __user * _info)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2005-11-17 20:59:38 +08:00
|
|
|
struct snd_pcm_info *info;
|
2005-04-17 06:20:36 +08:00
|
|
|
int err;
|
|
|
|
|
|
|
|
info = kmalloc(sizeof(*info), GFP_KERNEL);
|
|
|
|
if (! info)
|
|
|
|
return -ENOMEM;
|
|
|
|
err = snd_pcm_info(substream, info);
|
|
|
|
if (err >= 0) {
|
|
|
|
if (copy_to_user(_info, info, sizeof(*info)))
|
|
|
|
err = -EFAULT;
|
|
|
|
}
|
|
|
|
kfree(info);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2020-02-07 00:39:43 +08:00
|
|
|
/* macro for simplified cast */
|
|
|
|
#define PARAM_MASK_BIT(b) (1U << (__force int)(b))
|
|
|
|
|
2014-10-22 18:04:46 +08:00
|
|
|
static bool hw_support_mmap(struct snd_pcm_substream *substream)
|
|
|
|
{
|
|
|
|
if (!(substream->runtime->hw.info & SNDRV_PCM_INFO_MMAP))
|
|
|
|
return false;
|
2019-08-03 18:31:59 +08:00
|
|
|
|
|
|
|
if (substream->ops->mmap ||
|
2019-11-04 18:11:15 +08:00
|
|
|
(substream->dma_buffer.dev.type != SNDRV_DMA_TYPE_DEV &&
|
|
|
|
substream->dma_buffer.dev.type != SNDRV_DMA_TYPE_DEV_UC))
|
2019-08-03 18:31:59 +08:00
|
|
|
return true;
|
|
|
|
|
|
|
|
return dma_can_mmap(substream->dma_buffer.dev.dev);
|
2014-10-22 18:04:46 +08:00
|
|
|
}
|
|
|
|
|
2017-06-09 05:36:59 +08:00
|
|
|
static int constrain_mask_params(struct snd_pcm_substream *substream,
|
|
|
|
struct snd_pcm_hw_params *params)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2017-06-09 05:36:59 +08:00
|
|
|
struct snd_pcm_hw_constraints *constrs =
|
|
|
|
&substream->runtime->hw_constraints;
|
|
|
|
struct snd_mask *m;
|
2005-04-17 06:20:36 +08:00
|
|
|
unsigned int k;
|
2017-06-09 05:36:59 +08:00
|
|
|
struct snd_mask old_mask;
|
|
|
|
int changed;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
for (k = SNDRV_PCM_HW_PARAM_FIRST_MASK; k <= SNDRV_PCM_HW_PARAM_LAST_MASK; k++) {
|
|
|
|
m = hw_param_mask(params, k);
|
|
|
|
if (snd_mask_empty(m))
|
|
|
|
return -EINVAL;
|
2017-06-09 05:37:06 +08:00
|
|
|
|
|
|
|
/* This parameter is not requested to change by a caller. */
|
2020-02-07 00:39:43 +08:00
|
|
|
if (!(params->rmask & PARAM_MASK_BIT(k)))
|
2005-04-17 06:20:36 +08:00
|
|
|
continue;
|
2017-06-09 05:36:59 +08:00
|
|
|
|
|
|
|
if (trace_hw_mask_param_enabled())
|
|
|
|
old_mask = *m;
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
changed = snd_mask_refine(m, constrs_mask(constrs, k));
|
|
|
|
if (changed < 0)
|
|
|
|
return changed;
|
2017-06-11 22:56:13 +08:00
|
|
|
if (changed == 0)
|
|
|
|
continue;
|
2017-06-09 05:36:59 +08:00
|
|
|
|
2017-06-09 05:37:06 +08:00
|
|
|
/* Set corresponding flag so that the caller gets it. */
|
2017-06-11 22:56:13 +08:00
|
|
|
trace_hw_mask_param(substream, k, 0, &old_mask, m);
|
2020-02-07 00:39:43 +08:00
|
|
|
params->cmask |= PARAM_MASK_BIT(k);
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
2017-06-09 05:36:59 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2017-06-09 05:37:00 +08:00
|
|
|
static int constrain_interval_params(struct snd_pcm_substream *substream,
|
|
|
|
struct snd_pcm_hw_params *params)
|
|
|
|
{
|
|
|
|
struct snd_pcm_hw_constraints *constrs =
|
|
|
|
&substream->runtime->hw_constraints;
|
|
|
|
struct snd_interval *i;
|
|
|
|
unsigned int k;
|
|
|
|
struct snd_interval old_interval;
|
|
|
|
int changed;
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
for (k = SNDRV_PCM_HW_PARAM_FIRST_INTERVAL; k <= SNDRV_PCM_HW_PARAM_LAST_INTERVAL; k++) {
|
|
|
|
i = hw_param_interval(params, k);
|
|
|
|
if (snd_interval_empty(i))
|
|
|
|
return -EINVAL;
|
2017-06-09 05:37:06 +08:00
|
|
|
|
|
|
|
/* This parameter is not requested to change by a caller. */
|
2020-02-07 00:39:43 +08:00
|
|
|
if (!(params->rmask & PARAM_MASK_BIT(k)))
|
2005-04-17 06:20:36 +08:00
|
|
|
continue;
|
2017-06-09 05:37:00 +08:00
|
|
|
|
|
|
|
if (trace_hw_interval_param_enabled())
|
|
|
|
old_interval = *i;
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
changed = snd_interval_refine(i, constrs_interval(constrs, k));
|
|
|
|
if (changed < 0)
|
|
|
|
return changed;
|
2017-06-11 22:56:13 +08:00
|
|
|
if (changed == 0)
|
|
|
|
continue;
|
2017-06-09 05:37:00 +08:00
|
|
|
|
2017-06-09 05:37:06 +08:00
|
|
|
/* Set corresponding flag so that the caller gets it. */
|
2017-06-11 22:56:13 +08:00
|
|
|
trace_hw_interval_param(substream, k, 0, &old_interval, i);
|
2020-02-07 00:39:43 +08:00
|
|
|
params->cmask |= PARAM_MASK_BIT(k);
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
2017-06-09 05:37:00 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2017-06-09 05:37:01 +08:00
|
|
|
static int constrain_params_by_rules(struct snd_pcm_substream *substream,
|
|
|
|
struct snd_pcm_hw_params *params)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2017-06-09 05:37:01 +08:00
|
|
|
struct snd_pcm_hw_constraints *constrs =
|
|
|
|
&substream->runtime->hw_constraints;
|
2005-04-17 06:20:36 +08:00
|
|
|
unsigned int k;
|
2018-03-13 18:18:57 +08:00
|
|
|
unsigned int *rstamps;
|
2005-04-17 06:20:36 +08:00
|
|
|
unsigned int vstamps[SNDRV_PCM_HW_PARAM_LAST_INTERVAL + 1];
|
2017-06-09 05:37:06 +08:00
|
|
|
unsigned int stamp;
|
2017-06-09 05:37:04 +08:00
|
|
|
struct snd_pcm_hw_rule *r;
|
|
|
|
unsigned int d;
|
2017-06-09 05:37:01 +08:00
|
|
|
struct snd_mask old_mask;
|
|
|
|
struct snd_interval old_interval;
|
2017-06-09 05:37:04 +08:00
|
|
|
bool again;
|
2018-03-13 18:18:57 +08:00
|
|
|
int changed, err = 0;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2017-06-09 05:37:06 +08:00
|
|
|
/*
|
|
|
|
* Each application of rule has own sequence number.
|
|
|
|
*
|
|
|
|
* Each member of 'rstamps' array represents the sequence number of
|
|
|
|
* recent application of corresponding rule.
|
|
|
|
*/
|
2018-03-13 18:18:57 +08:00
|
|
|
rstamps = kcalloc(constrs->rules_num, sizeof(unsigned int), GFP_KERNEL);
|
|
|
|
if (!rstamps)
|
|
|
|
return -ENOMEM;
|
2017-06-09 05:37:06 +08:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Each member of 'vstamps' array represents the sequence number of
|
|
|
|
* recent application of rule in which corresponding parameters were
|
|
|
|
* changed.
|
|
|
|
*
|
|
|
|
* In initial state, elements corresponding to parameters requested by
|
|
|
|
* a caller is 1. For unrequested parameters, corresponding members
|
|
|
|
* have 0 so that the parameters are never changed anymore.
|
|
|
|
*/
|
2017-06-09 05:37:01 +08:00
|
|
|
for (k = 0; k <= SNDRV_PCM_HW_PARAM_LAST_INTERVAL; k++)
|
2020-02-07 00:39:43 +08:00
|
|
|
vstamps[k] = (params->rmask & PARAM_MASK_BIT(k)) ? 1 : 0;
|
2017-06-09 05:37:06 +08:00
|
|
|
|
|
|
|
/* Due to the above design, actual sequence number starts at 2. */
|
|
|
|
stamp = 2;
|
2017-06-09 05:37:02 +08:00
|
|
|
retry:
|
2017-06-09 05:37:06 +08:00
|
|
|
/* Apply all rules in order. */
|
2017-06-09 05:37:04 +08:00
|
|
|
again = false;
|
2017-06-09 05:37:02 +08:00
|
|
|
for (k = 0; k < constrs->rules_num; k++) {
|
2017-06-09 05:37:04 +08:00
|
|
|
r = &constrs->rules[k];
|
2017-06-09 05:37:06 +08:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Check condition bits of this rule. When the rule has
|
|
|
|
* some condition bits, parameter without the bits is
|
|
|
|
* never processed. SNDRV_PCM_HW_PARAMS_NO_PERIOD_WAKEUP
|
|
|
|
* is an example of the condition bits.
|
|
|
|
*/
|
2017-06-09 05:37:02 +08:00
|
|
|
if (r->cond && !(r->cond & params->flags))
|
|
|
|
continue;
|
2017-06-09 05:37:06 +08:00
|
|
|
|
|
|
|
/*
|
|
|
|
* The 'deps' array includes maximum three dependencies
|
|
|
|
* to SNDRV_PCM_HW_PARAM_XXXs for this rule. The fourth
|
|
|
|
* member of this array is a sentinel and should be
|
|
|
|
* negative value.
|
|
|
|
*
|
|
|
|
* This rule should be processed in this time when dependent
|
|
|
|
* parameters were changed at former applications of the other
|
|
|
|
* rules.
|
|
|
|
*/
|
2017-06-09 05:37:02 +08:00
|
|
|
for (d = 0; r->deps[d] >= 0; d++) {
|
2017-06-09 05:37:03 +08:00
|
|
|
if (vstamps[r->deps[d]] > rstamps[k])
|
2017-06-09 05:37:02 +08:00
|
|
|
break;
|
|
|
|
}
|
2017-06-09 05:37:03 +08:00
|
|
|
if (r->deps[d] < 0)
|
2017-06-09 05:37:02 +08:00
|
|
|
continue;
|
2017-06-07 07:46:43 +08:00
|
|
|
|
2017-06-09 05:37:02 +08:00
|
|
|
if (trace_hw_mask_param_enabled()) {
|
|
|
|
if (hw_is_mask(r->var))
|
|
|
|
old_mask = *hw_param_mask(params, r->var);
|
|
|
|
}
|
|
|
|
if (trace_hw_interval_param_enabled()) {
|
|
|
|
if (hw_is_interval(r->var))
|
|
|
|
old_interval = *hw_param_interval(params, r->var);
|
|
|
|
}
|
2017-06-07 07:46:45 +08:00
|
|
|
|
2017-06-09 05:37:02 +08:00
|
|
|
changed = r->func(params, r);
|
2018-03-13 18:18:57 +08:00
|
|
|
if (changed < 0) {
|
|
|
|
err = changed;
|
|
|
|
goto out;
|
|
|
|
}
|
2017-06-07 07:46:45 +08:00
|
|
|
|
2017-06-09 05:37:06 +08:00
|
|
|
/*
|
2017-06-11 22:56:13 +08:00
|
|
|
* When the parameter is changed, notify it to the caller
|
2017-06-09 05:37:06 +08:00
|
|
|
* by corresponding returned bit, then preparing for next
|
|
|
|
* iteration.
|
|
|
|
*/
|
2017-06-09 05:37:02 +08:00
|
|
|
if (changed && r->var >= 0) {
|
2017-06-11 22:56:13 +08:00
|
|
|
if (hw_is_mask(r->var)) {
|
|
|
|
trace_hw_mask_param(substream, r->var,
|
|
|
|
k + 1, &old_mask,
|
|
|
|
hw_param_mask(params, r->var));
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
2017-06-11 22:56:13 +08:00
|
|
|
if (hw_is_interval(r->var)) {
|
|
|
|
trace_hw_interval_param(substream, r->var,
|
|
|
|
k + 1, &old_interval,
|
|
|
|
hw_param_interval(params, r->var));
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
2017-06-11 22:56:13 +08:00
|
|
|
|
2020-02-07 00:39:43 +08:00
|
|
|
params->cmask |= PARAM_MASK_BIT(r->var);
|
2017-06-09 05:37:02 +08:00
|
|
|
vstamps[r->var] = stamp;
|
2017-06-09 05:37:04 +08:00
|
|
|
again = true;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
2017-06-11 22:56:12 +08:00
|
|
|
|
2017-06-11 22:56:13 +08:00
|
|
|
rstamps[k] = stamp++;
|
2017-06-09 05:37:02 +08:00
|
|
|
}
|
|
|
|
|
2017-06-09 05:37:06 +08:00
|
|
|
/* Iterate to evaluate all rules till no parameters are changed. */
|
2017-06-09 05:37:02 +08:00
|
|
|
if (again)
|
|
|
|
goto retry;
|
2017-06-09 05:37:01 +08:00
|
|
|
|
2018-03-13 18:18:57 +08:00
|
|
|
out:
|
|
|
|
kfree(rstamps);
|
|
|
|
return err;
|
2017-06-09 05:37:01 +08:00
|
|
|
}
|
|
|
|
|
2017-06-09 08:34:39 +08:00
|
|
|
static int fixup_unreferenced_params(struct snd_pcm_substream *substream,
|
|
|
|
struct snd_pcm_hw_params *params)
|
|
|
|
{
|
|
|
|
const struct snd_interval *i;
|
|
|
|
const struct snd_mask *m;
|
|
|
|
int err;
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
if (!params->msbits) {
|
2017-06-09 08:34:39 +08:00
|
|
|
i = hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_SAMPLE_BITS);
|
2005-04-17 06:20:36 +08:00
|
|
|
if (snd_interval_single(i))
|
|
|
|
params->msbits = snd_interval_value(i);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!params->rate_den) {
|
2017-06-09 08:34:39 +08:00
|
|
|
i = hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_RATE);
|
2005-04-17 06:20:36 +08:00
|
|
|
if (snd_interval_single(i)) {
|
|
|
|
params->rate_num = snd_interval_value(i);
|
|
|
|
params->rate_den = 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-06-09 08:34:39 +08:00
|
|
|
if (!params->fifo_size) {
|
|
|
|
m = hw_param_mask_c(params, SNDRV_PCM_HW_PARAM_FORMAT);
|
|
|
|
i = hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_CHANNELS);
|
|
|
|
if (snd_mask_single(m) && snd_interval_single(i)) {
|
2019-11-17 16:53:03 +08:00
|
|
|
err = snd_pcm_ops_ioctl(substream,
|
|
|
|
SNDRV_PCM_IOCTL1_FIFO_SIZE,
|
|
|
|
params);
|
2017-06-09 08:34:39 +08:00
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-10-22 18:04:46 +08:00
|
|
|
if (!params->info) {
|
2017-06-09 08:34:40 +08:00
|
|
|
params->info = substream->runtime->hw.info;
|
|
|
|
params->info &= ~(SNDRV_PCM_INFO_FIFO_IN_FRAMES |
|
|
|
|
SNDRV_PCM_INFO_DRAIN_TRIGGER);
|
2014-10-22 18:04:46 +08:00
|
|
|
if (!hw_support_mmap(substream))
|
|
|
|
params->info &= ~(SNDRV_PCM_INFO_MMAP |
|
|
|
|
SNDRV_PCM_INFO_MMAP_VALID);
|
|
|
|
}
|
2017-06-09 08:34:40 +08:00
|
|
|
|
2017-06-09 08:34:39 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2017-06-09 05:37:01 +08:00
|
|
|
int snd_pcm_hw_refine(struct snd_pcm_substream *substream,
|
|
|
|
struct snd_pcm_hw_params *params)
|
|
|
|
{
|
|
|
|
int err;
|
|
|
|
|
|
|
|
params->info = 0;
|
|
|
|
params->fifo_size = 0;
|
2020-02-07 00:39:43 +08:00
|
|
|
if (params->rmask & PARAM_MASK_BIT(SNDRV_PCM_HW_PARAM_SAMPLE_BITS))
|
2017-06-09 05:37:01 +08:00
|
|
|
params->msbits = 0;
|
2020-02-07 00:39:43 +08:00
|
|
|
if (params->rmask & PARAM_MASK_BIT(SNDRV_PCM_HW_PARAM_RATE)) {
|
2017-06-09 05:37:01 +08:00
|
|
|
params->rate_num = 0;
|
|
|
|
params->rate_den = 0;
|
2009-04-27 15:44:40 +08:00
|
|
|
}
|
2017-06-09 05:37:01 +08:00
|
|
|
|
|
|
|
err = constrain_mask_params(substream, params);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
|
|
|
|
err = constrain_interval_params(substream, params);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
|
|
|
|
err = constrain_params_by_rules(substream, params);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
params->rmask = 0;
|
2017-06-09 08:34:40 +08:00
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
return 0;
|
|
|
|
}
|
2006-04-28 21:13:40 +08:00
|
|
|
EXPORT_SYMBOL(snd_pcm_hw_refine);
|
|
|
|
|
2005-11-17 20:59:38 +08:00
|
|
|
static int snd_pcm_hw_refine_user(struct snd_pcm_substream *substream,
|
|
|
|
struct snd_pcm_hw_params __user * _params)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2005-11-17 20:59:38 +08:00
|
|
|
struct snd_pcm_hw_params *params;
|
2005-04-17 06:20:36 +08:00
|
|
|
int err;
|
|
|
|
|
2009-04-10 09:43:08 +08:00
|
|
|
params = memdup_user(_params, sizeof(*params));
|
|
|
|
if (IS_ERR(params))
|
|
|
|
return PTR_ERR(params);
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
err = snd_pcm_hw_refine(substream, params);
|
2017-06-11 22:56:12 +08:00
|
|
|
if (err < 0)
|
|
|
|
goto end;
|
2009-04-10 09:43:08 +08:00
|
|
|
|
2017-06-11 22:56:12 +08:00
|
|
|
err = fixup_unreferenced_params(substream, params);
|
|
|
|
if (err < 0)
|
|
|
|
goto end;
|
2009-04-10 09:43:08 +08:00
|
|
|
|
2017-06-11 22:56:12 +08:00
|
|
|
if (copy_to_user(_params, params, sizeof(*params)))
|
|
|
|
err = -EFAULT;
|
|
|
|
end:
|
2005-04-17 06:20:36 +08:00
|
|
|
kfree(params);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2006-10-01 14:27:19 +08:00
|
|
|
static int period_to_usecs(struct snd_pcm_runtime *runtime)
|
|
|
|
{
|
|
|
|
int usecs;
|
|
|
|
|
|
|
|
if (! runtime->rate)
|
|
|
|
return -1; /* invalid */
|
|
|
|
|
|
|
|
/* take 75% of period time as the deadline */
|
|
|
|
usecs = (750000 / runtime->rate) * runtime->period_size;
|
|
|
|
usecs += ((750000 % runtime->rate) * runtime->period_size) /
|
|
|
|
runtime->rate;
|
|
|
|
|
|
|
|
return usecs;
|
|
|
|
}
|
|
|
|
|
2020-01-31 23:22:14 +08:00
|
|
|
static void snd_pcm_set_state(struct snd_pcm_substream *substream,
|
|
|
|
snd_pcm_state_t state)
|
2012-10-12 21:07:34 +08:00
|
|
|
{
|
|
|
|
snd_pcm_stream_lock_irq(substream);
|
|
|
|
if (substream->runtime->status->state != SNDRV_PCM_STATE_DISCONNECTED)
|
|
|
|
substream->runtime->status->state = state;
|
|
|
|
snd_pcm_stream_unlock_irq(substream);
|
|
|
|
}
|
|
|
|
|
2015-10-16 17:57:46 +08:00
|
|
|
static inline void snd_pcm_timer_notify(struct snd_pcm_substream *substream,
|
|
|
|
int event)
|
|
|
|
{
|
|
|
|
#ifdef CONFIG_SND_PCM_TIMER
|
|
|
|
if (substream->timer)
|
|
|
|
snd_timer_notify(substream->timer, event,
|
|
|
|
&substream->runtime->trigger_tstamp);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
ALSA: pcm: Add the support for sync-stop operation
The standard programming model of a PCM sound driver is to process
snd_pcm_period_elapsed() from an interrupt handler. When a running
stream is stopped, PCM core calls the trigger-STOP PCM ops, sets the
stream state to SETUP, and moves on to the next step. This is
performed in an atomic manner -- this could be called from the interrupt
context, after all.
The problem is that, if the stream goes further and reaches to the
CLOSE state immediately, the stream might be still being processed in
snd_pcm_period_elapsed() in the interrupt context, and hits a NULL
dereference. Such a crash happens because of the atomic operation,
and we can't wait until the stream-stop finishes.
For addressing such a problem, this commit adds a new PCM ops,
sync_stop. This gets called at the appropriate places that need a
sync with the stream-stop, i.e. at hw_params, prepare and hw_free.
Some drivers already have a similar mechanism implemented locally, and
we'll refactor the code later.
Link: https://lore.kernel.org/r/20191117085308.23915-7-tiwai@suse.de
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2019-11-17 16:53:06 +08:00
|
|
|
static void snd_pcm_sync_stop(struct snd_pcm_substream *substream)
|
|
|
|
{
|
|
|
|
if (substream->runtime->stop_operating) {
|
|
|
|
substream->runtime->stop_operating = false;
|
|
|
|
if (substream->ops->sync_stop)
|
|
|
|
substream->ops->sync_stop(substream);
|
2019-11-17 16:53:07 +08:00
|
|
|
else if (substream->pcm->card->sync_irq > 0)
|
|
|
|
synchronize_irq(substream->pcm->card->sync_irq);
|
ALSA: pcm: Add the support for sync-stop operation
The standard programming model of a PCM sound driver is to process
snd_pcm_period_elapsed() from an interrupt handler. When a running
stream is stopped, PCM core calls the trigger-STOP PCM ops, sets the
stream state to SETUP, and moves on to the next step. This is
performed in an atomic manner -- this could be called from the interrupt
context, after all.
The problem is that, if the stream goes further and reaches to the
CLOSE state immediately, the stream might be still being processed in
snd_pcm_period_elapsed() in the interrupt context, and hits a NULL
dereference. Such a crash happens because of the atomic operation,
and we can't wait until the stream-stop finishes.
For addressing such a problem, this commit adds a new PCM ops,
sync_stop. This gets called at the appropriate places that need a
sync with the stream-stop, i.e. at hw_params, prepare and hw_free.
Some drivers already have a similar mechanism implemented locally, and
we'll refactor the code later.
Link: https://lore.kernel.org/r/20191117085308.23915-7-tiwai@suse.de
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2019-11-17 16:53:06 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-06-09 20:46:48 +08:00
|
|
|
/**
|
|
|
|
* snd_pcm_hw_param_choose - choose a configuration defined by @params
|
|
|
|
* @pcm: PCM instance
|
|
|
|
* @params: the hw_params instance
|
|
|
|
*
|
|
|
|
* Choose one configuration from configuration space defined by @params.
|
|
|
|
* The configuration chosen is that obtained fixing in this order:
|
|
|
|
* first access, first format, first subformat, min channels,
|
|
|
|
* min rate, min period time, max buffer size, min tick time
|
|
|
|
*
|
|
|
|
* Return: Zero if successful, or a negative error code on failure.
|
|
|
|
*/
|
|
|
|
static int snd_pcm_hw_params_choose(struct snd_pcm_substream *pcm,
|
|
|
|
struct snd_pcm_hw_params *params)
|
|
|
|
{
|
|
|
|
static const int vars[] = {
|
|
|
|
SNDRV_PCM_HW_PARAM_ACCESS,
|
|
|
|
SNDRV_PCM_HW_PARAM_FORMAT,
|
|
|
|
SNDRV_PCM_HW_PARAM_SUBFORMAT,
|
|
|
|
SNDRV_PCM_HW_PARAM_CHANNELS,
|
|
|
|
SNDRV_PCM_HW_PARAM_RATE,
|
|
|
|
SNDRV_PCM_HW_PARAM_PERIOD_TIME,
|
|
|
|
SNDRV_PCM_HW_PARAM_BUFFER_SIZE,
|
|
|
|
SNDRV_PCM_HW_PARAM_TICK_TIME,
|
|
|
|
-1
|
|
|
|
};
|
|
|
|
const int *v;
|
2017-06-09 20:46:49 +08:00
|
|
|
struct snd_mask old_mask;
|
|
|
|
struct snd_interval old_interval;
|
2017-06-11 22:56:13 +08:00
|
|
|
int changed;
|
2017-06-09 20:46:48 +08:00
|
|
|
|
|
|
|
for (v = vars; *v != -1; v++) {
|
2017-06-09 20:46:49 +08:00
|
|
|
/* Keep old parameter to trace. */
|
|
|
|
if (trace_hw_mask_param_enabled()) {
|
|
|
|
if (hw_is_mask(*v))
|
|
|
|
old_mask = *hw_param_mask(params, *v);
|
|
|
|
}
|
|
|
|
if (trace_hw_interval_param_enabled()) {
|
|
|
|
if (hw_is_interval(*v))
|
|
|
|
old_interval = *hw_param_interval(params, *v);
|
|
|
|
}
|
2017-06-09 20:46:48 +08:00
|
|
|
if (*v != SNDRV_PCM_HW_PARAM_BUFFER_SIZE)
|
2017-06-11 22:56:13 +08:00
|
|
|
changed = snd_pcm_hw_param_first(pcm, params, *v, NULL);
|
2017-06-09 20:46:48 +08:00
|
|
|
else
|
2017-06-11 22:56:13 +08:00
|
|
|
changed = snd_pcm_hw_param_last(pcm, params, *v, NULL);
|
2018-04-09 23:12:16 +08:00
|
|
|
if (changed < 0)
|
2017-06-11 22:56:13 +08:00
|
|
|
return changed;
|
|
|
|
if (changed == 0)
|
|
|
|
continue;
|
2017-06-09 20:46:49 +08:00
|
|
|
|
2017-06-11 22:56:13 +08:00
|
|
|
/* Trace the changed parameter. */
|
2017-06-09 20:46:49 +08:00
|
|
|
if (hw_is_mask(*v)) {
|
|
|
|
trace_hw_mask_param(pcm, *v, 0, &old_mask,
|
|
|
|
hw_param_mask(params, *v));
|
|
|
|
}
|
|
|
|
if (hw_is_interval(*v)) {
|
|
|
|
trace_hw_interval_param(pcm, *v, 0, &old_interval,
|
|
|
|
hw_param_interval(params, *v));
|
|
|
|
}
|
2017-06-09 20:46:48 +08:00
|
|
|
}
|
2017-06-09 20:46:49 +08:00
|
|
|
|
2017-06-09 20:46:48 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2005-11-17 20:59:38 +08:00
|
|
|
static int snd_pcm_hw_params(struct snd_pcm_substream *substream,
|
|
|
|
struct snd_pcm_hw_params *params)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2005-11-17 20:59:38 +08:00
|
|
|
struct snd_pcm_runtime *runtime;
|
2006-10-01 14:27:19 +08:00
|
|
|
int err, usecs;
|
2005-04-17 06:20:36 +08:00
|
|
|
unsigned int bits;
|
|
|
|
snd_pcm_uframes_t frames;
|
|
|
|
|
2008-08-08 23:09:09 +08:00
|
|
|
if (PCM_RUNTIME_CHECK(substream))
|
|
|
|
return -ENXIO;
|
2005-04-17 06:20:36 +08:00
|
|
|
runtime = substream->runtime;
|
|
|
|
snd_pcm_stream_lock_irq(substream);
|
|
|
|
switch (runtime->status->state) {
|
|
|
|
case SNDRV_PCM_STATE_OPEN:
|
|
|
|
case SNDRV_PCM_STATE_SETUP:
|
|
|
|
case SNDRV_PCM_STATE_PREPARED:
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
snd_pcm_stream_unlock_irq(substream);
|
|
|
|
return -EBADFD;
|
|
|
|
}
|
|
|
|
snd_pcm_stream_unlock_irq(substream);
|
2014-02-10 16:48:47 +08:00
|
|
|
#if IS_ENABLED(CONFIG_SND_PCM_OSS)
|
2005-04-17 06:20:36 +08:00
|
|
|
if (!substream->oss.oss)
|
|
|
|
#endif
|
2006-04-28 21:13:41 +08:00
|
|
|
if (atomic_read(&substream->mmap_count))
|
2005-04-17 06:20:36 +08:00
|
|
|
return -EBADFD;
|
|
|
|
|
ALSA: pcm: Add the support for sync-stop operation
The standard programming model of a PCM sound driver is to process
snd_pcm_period_elapsed() from an interrupt handler. When a running
stream is stopped, PCM core calls the trigger-STOP PCM ops, sets the
stream state to SETUP, and moves on to the next step. This is
performed in an atomic manner -- this could be called from the interrupt
context, after all.
The problem is that, if the stream goes further and reaches to the
CLOSE state immediately, the stream might be still being processed in
snd_pcm_period_elapsed() in the interrupt context, and hits a NULL
dereference. Such a crash happens because of the atomic operation,
and we can't wait until the stream-stop finishes.
For addressing such a problem, this commit adds a new PCM ops,
sync_stop. This gets called at the appropriate places that need a
sync with the stream-stop, i.e. at hw_params, prepare and hw_free.
Some drivers already have a similar mechanism implemented locally, and
we'll refactor the code later.
Link: https://lore.kernel.org/r/20191117085308.23915-7-tiwai@suse.de
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2019-11-17 16:53:06 +08:00
|
|
|
snd_pcm_sync_stop(substream);
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
params->rmask = ~0U;
|
|
|
|
err = snd_pcm_hw_refine(substream, params);
|
|
|
|
if (err < 0)
|
|
|
|
goto _error;
|
|
|
|
|
|
|
|
err = snd_pcm_hw_params_choose(substream, params);
|
|
|
|
if (err < 0)
|
|
|
|
goto _error;
|
|
|
|
|
2017-06-09 08:34:39 +08:00
|
|
|
err = fixup_unreferenced_params(substream, params);
|
|
|
|
if (err < 0)
|
|
|
|
goto _error;
|
|
|
|
|
ALSA: pcm: Introduce managed buffer allocation mode
This patch adds the support for the feature to automatically allocate
and free PCM buffers, so called "managed buffer allocation" mode.
It's set up via new PCM helpers, snd_pcm_set_managed_buffer() and
snd_pcm_set_managed_buffer_all(), both of which correspond to the
existing preallocator helpers, snd_pcm_lib_preallocate_pages() and
snd_pcm_lib_preallocate_pages_for_all(). When the new helper is used,
it not only performs the pre-allocation of buffers, but also it
manages to call snd_pcm_lib_malloc_pages() before the PCM hw_params
ops and snd_lib_pcm_free() after the PCM hw_free ops inside PCM core,
respectively. This allows drivers to drop the explicit calls of the
memory allocation / release functions, and it will be a good amount of
code reduction in the end of this patch series.
When the PCM substream is set to the managed buffer allocation mode,
the managed_buffer_alloc flag is set in the substream object. Since
some drivers want to know when a buffer is newly allocated or
re-allocated at hw_params callback (e.g. want to set up the additional
stuff for the given buffer only at allocation time), now PCM core
turns on buffer_changed flag when the buffer has changed.
The standard conversions to use the new API will be straightforward:
- Replace snd_pcm_lib_preallocate*() calls with the corresponding
snd_pcm_set_managed_buffer*(); the arguments should be unchanged
- Drop superfluous snd_pcm_lib_malloc() and snd_pcm_lib_free() calls;
the check of snd_pcm_lib_malloc() returns should be replaced with
the check of runtime->buffer_changed flag.
- If hw_params or hw_free becomes empty, drop them from PCM ops
Link: https://lore.kernel.org/r/20191117085308.23915-2-tiwai@suse.de
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2019-11-17 16:53:01 +08:00
|
|
|
if (substream->managed_buffer_alloc) {
|
|
|
|
err = snd_pcm_lib_malloc_pages(substream,
|
|
|
|
params_buffer_bytes(params));
|
|
|
|
if (err < 0)
|
|
|
|
goto _error;
|
|
|
|
runtime->buffer_changed = err > 0;
|
|
|
|
}
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
if (substream->ops->hw_params != NULL) {
|
|
|
|
err = substream->ops->hw_params(substream, params);
|
|
|
|
if (err < 0)
|
|
|
|
goto _error;
|
|
|
|
}
|
|
|
|
|
|
|
|
runtime->access = params_access(params);
|
|
|
|
runtime->format = params_format(params);
|
|
|
|
runtime->subformat = params_subformat(params);
|
|
|
|
runtime->channels = params_channels(params);
|
|
|
|
runtime->rate = params_rate(params);
|
|
|
|
runtime->period_size = params_period_size(params);
|
|
|
|
runtime->periods = params_periods(params);
|
|
|
|
runtime->buffer_size = params_buffer_size(params);
|
|
|
|
runtime->info = params->info;
|
|
|
|
runtime->rate_num = params->rate_num;
|
|
|
|
runtime->rate_den = params->rate_den;
|
2010-11-15 17:46:23 +08:00
|
|
|
runtime->no_period_wakeup =
|
|
|
|
(params->info & SNDRV_PCM_INFO_NO_PERIOD_WAKEUP) &&
|
|
|
|
(params->flags & SNDRV_PCM_HW_PARAMS_NO_PERIOD_WAKEUP);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
bits = snd_pcm_format_physical_width(runtime->format);
|
|
|
|
runtime->sample_bits = bits;
|
|
|
|
bits *= runtime->channels;
|
|
|
|
runtime->frame_bits = bits;
|
|
|
|
frames = 1;
|
|
|
|
while (bits % 8 != 0) {
|
|
|
|
bits *= 2;
|
|
|
|
frames *= 2;
|
|
|
|
}
|
|
|
|
runtime->byte_align = bits / 8;
|
|
|
|
runtime->min_align = frames;
|
|
|
|
|
|
|
|
/* Default sw params */
|
|
|
|
runtime->tstamp_mode = SNDRV_PCM_TSTAMP_NONE;
|
|
|
|
runtime->period_step = 1;
|
|
|
|
runtime->control->avail_min = runtime->period_size;
|
|
|
|
runtime->start_threshold = 1;
|
|
|
|
runtime->stop_threshold = runtime->buffer_size;
|
|
|
|
runtime->silence_threshold = 0;
|
|
|
|
runtime->silence_size = 0;
|
2010-05-21 15:15:59 +08:00
|
|
|
runtime->boundary = runtime->buffer_size;
|
|
|
|
while (runtime->boundary * 2 <= LONG_MAX - runtime->buffer_size)
|
|
|
|
runtime->boundary *= 2;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2019-12-11 23:57:42 +08:00
|
|
|
/* clear the buffer for avoiding possible kernel info leaks */
|
|
|
|
if (runtime->dma_area && !substream->ops->copy_user)
|
|
|
|
memset(runtime->dma_area, 0, runtime->dma_bytes);
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
snd_pcm_timer_resolution_change(substream);
|
2012-10-12 21:07:34 +08:00
|
|
|
snd_pcm_set_state(substream, SNDRV_PCM_STATE_SETUP);
|
2006-10-01 14:27:19 +08:00
|
|
|
|
2020-02-12 07:34:15 +08:00
|
|
|
if (cpu_latency_qos_request_active(&substream->latency_pm_qos_req))
|
|
|
|
cpu_latency_qos_remove_request(&substream->latency_pm_qos_req);
|
2006-10-01 14:27:19 +08:00
|
|
|
if ((usecs = period_to_usecs(runtime)) >= 0)
|
2020-02-12 07:34:15 +08:00
|
|
|
cpu_latency_qos_add_request(&substream->latency_pm_qos_req,
|
|
|
|
usecs);
|
2005-04-17 06:20:36 +08:00
|
|
|
return 0;
|
|
|
|
_error:
|
2011-03-31 09:57:33 +08:00
|
|
|
/* hardware might be unusable from this time,
|
2005-04-17 06:20:36 +08:00
|
|
|
so we force application to retry to set
|
|
|
|
the correct hardware parameter settings */
|
2012-10-12 21:07:34 +08:00
|
|
|
snd_pcm_set_state(substream, SNDRV_PCM_STATE_OPEN);
|
2005-04-17 06:20:36 +08:00
|
|
|
if (substream->ops->hw_free != NULL)
|
|
|
|
substream->ops->hw_free(substream);
|
ALSA: pcm: Introduce managed buffer allocation mode
This patch adds the support for the feature to automatically allocate
and free PCM buffers, so called "managed buffer allocation" mode.
It's set up via new PCM helpers, snd_pcm_set_managed_buffer() and
snd_pcm_set_managed_buffer_all(), both of which correspond to the
existing preallocator helpers, snd_pcm_lib_preallocate_pages() and
snd_pcm_lib_preallocate_pages_for_all(). When the new helper is used,
it not only performs the pre-allocation of buffers, but also it
manages to call snd_pcm_lib_malloc_pages() before the PCM hw_params
ops and snd_lib_pcm_free() after the PCM hw_free ops inside PCM core,
respectively. This allows drivers to drop the explicit calls of the
memory allocation / release functions, and it will be a good amount of
code reduction in the end of this patch series.
When the PCM substream is set to the managed buffer allocation mode,
the managed_buffer_alloc flag is set in the substream object. Since
some drivers want to know when a buffer is newly allocated or
re-allocated at hw_params callback (e.g. want to set up the additional
stuff for the given buffer only at allocation time), now PCM core
turns on buffer_changed flag when the buffer has changed.
The standard conversions to use the new API will be straightforward:
- Replace snd_pcm_lib_preallocate*() calls with the corresponding
snd_pcm_set_managed_buffer*(); the arguments should be unchanged
- Drop superfluous snd_pcm_lib_malloc() and snd_pcm_lib_free() calls;
the check of snd_pcm_lib_malloc() returns should be replaced with
the check of runtime->buffer_changed flag.
- If hw_params or hw_free becomes empty, drop them from PCM ops
Link: https://lore.kernel.org/r/20191117085308.23915-2-tiwai@suse.de
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2019-11-17 16:53:01 +08:00
|
|
|
if (substream->managed_buffer_alloc)
|
|
|
|
snd_pcm_lib_free_pages(substream);
|
2005-04-17 06:20:36 +08:00
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2005-11-17 20:59:38 +08:00
|
|
|
static int snd_pcm_hw_params_user(struct snd_pcm_substream *substream,
|
|
|
|
struct snd_pcm_hw_params __user * _params)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2005-11-17 20:59:38 +08:00
|
|
|
struct snd_pcm_hw_params *params;
|
2005-04-17 06:20:36 +08:00
|
|
|
int err;
|
|
|
|
|
2009-04-10 09:43:08 +08:00
|
|
|
params = memdup_user(_params, sizeof(*params));
|
|
|
|
if (IS_ERR(params))
|
|
|
|
return PTR_ERR(params);
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
err = snd_pcm_hw_params(substream, params);
|
2017-06-11 22:56:12 +08:00
|
|
|
if (err < 0)
|
|
|
|
goto end;
|
2009-04-10 09:43:08 +08:00
|
|
|
|
2017-06-11 22:56:12 +08:00
|
|
|
if (copy_to_user(_params, params, sizeof(*params)))
|
|
|
|
err = -EFAULT;
|
|
|
|
end:
|
2005-04-17 06:20:36 +08:00
|
|
|
kfree(params);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2020-01-30 03:59:07 +08:00
|
|
|
static int do_hw_free(struct snd_pcm_substream *substream)
|
|
|
|
{
|
|
|
|
int result = 0;
|
|
|
|
|
|
|
|
snd_pcm_sync_stop(substream);
|
|
|
|
if (substream->ops->hw_free)
|
|
|
|
result = substream->ops->hw_free(substream);
|
|
|
|
if (substream->managed_buffer_alloc)
|
|
|
|
snd_pcm_lib_free_pages(substream);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2005-11-17 20:59:38 +08:00
|
|
|
static int snd_pcm_hw_free(struct snd_pcm_substream *substream)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2005-11-17 20:59:38 +08:00
|
|
|
struct snd_pcm_runtime *runtime;
|
2020-01-30 03:59:07 +08:00
|
|
|
int result;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2008-08-08 23:09:09 +08:00
|
|
|
if (PCM_RUNTIME_CHECK(substream))
|
|
|
|
return -ENXIO;
|
2005-04-17 06:20:36 +08:00
|
|
|
runtime = substream->runtime;
|
|
|
|
snd_pcm_stream_lock_irq(substream);
|
|
|
|
switch (runtime->status->state) {
|
|
|
|
case SNDRV_PCM_STATE_SETUP:
|
|
|
|
case SNDRV_PCM_STATE_PREPARED:
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
snd_pcm_stream_unlock_irq(substream);
|
|
|
|
return -EBADFD;
|
|
|
|
}
|
|
|
|
snd_pcm_stream_unlock_irq(substream);
|
2006-04-28 21:13:41 +08:00
|
|
|
if (atomic_read(&substream->mmap_count))
|
2005-04-17 06:20:36 +08:00
|
|
|
return -EBADFD;
|
2020-01-30 03:59:07 +08:00
|
|
|
result = do_hw_free(substream);
|
2012-10-12 21:07:34 +08:00
|
|
|
snd_pcm_set_state(substream, SNDRV_PCM_STATE_OPEN);
|
2020-02-12 07:34:15 +08:00
|
|
|
cpu_latency_qos_remove_request(&substream->latency_pm_qos_req);
|
2005-04-17 06:20:36 +08:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2005-11-17 20:59:38 +08:00
|
|
|
static int snd_pcm_sw_params(struct snd_pcm_substream *substream,
|
|
|
|
struct snd_pcm_sw_params *params)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2005-11-17 20:59:38 +08:00
|
|
|
struct snd_pcm_runtime *runtime;
|
2010-01-07 22:36:31 +08:00
|
|
|
int err;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2008-08-08 23:09:09 +08:00
|
|
|
if (PCM_RUNTIME_CHECK(substream))
|
|
|
|
return -ENXIO;
|
2005-04-17 06:20:36 +08:00
|
|
|
runtime = substream->runtime;
|
|
|
|
snd_pcm_stream_lock_irq(substream);
|
|
|
|
if (runtime->status->state == SNDRV_PCM_STATE_OPEN) {
|
|
|
|
snd_pcm_stream_unlock_irq(substream);
|
|
|
|
return -EBADFD;
|
|
|
|
}
|
|
|
|
snd_pcm_stream_unlock_irq(substream);
|
|
|
|
|
2015-09-23 17:42:28 +08:00
|
|
|
if (params->tstamp_mode < 0 ||
|
|
|
|
params->tstamp_mode > SNDRV_PCM_TSTAMP_LAST)
|
2005-04-17 06:20:36 +08:00
|
|
|
return -EINVAL;
|
2014-07-16 23:45:27 +08:00
|
|
|
if (params->proto >= SNDRV_PROTOCOL_VERSION(2, 0, 12) &&
|
|
|
|
params->tstamp_type > SNDRV_PCM_TSTAMP_TYPE_LAST)
|
2014-07-10 15:50:19 +08:00
|
|
|
return -EINVAL;
|
2005-04-17 06:20:36 +08:00
|
|
|
if (params->avail_min == 0)
|
|
|
|
return -EINVAL;
|
|
|
|
if (params->silence_size >= runtime->boundary) {
|
|
|
|
if (params->silence_threshold != 0)
|
|
|
|
return -EINVAL;
|
|
|
|
} else {
|
|
|
|
if (params->silence_size > params->silence_threshold)
|
|
|
|
return -EINVAL;
|
|
|
|
if (params->silence_threshold > runtime->buffer_size)
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
2010-01-07 22:36:31 +08:00
|
|
|
err = 0;
|
2005-04-17 06:20:36 +08:00
|
|
|
snd_pcm_stream_lock_irq(substream);
|
|
|
|
runtime->tstamp_mode = params->tstamp_mode;
|
2014-07-16 23:45:27 +08:00
|
|
|
if (params->proto >= SNDRV_PROTOCOL_VERSION(2, 0, 12))
|
|
|
|
runtime->tstamp_type = params->tstamp_type;
|
2005-04-17 06:20:36 +08:00
|
|
|
runtime->period_step = params->period_step;
|
|
|
|
runtime->control->avail_min = params->avail_min;
|
|
|
|
runtime->start_threshold = params->start_threshold;
|
|
|
|
runtime->stop_threshold = params->stop_threshold;
|
|
|
|
runtime->silence_threshold = params->silence_threshold;
|
|
|
|
runtime->silence_size = params->silence_size;
|
|
|
|
params->boundary = runtime->boundary;
|
|
|
|
if (snd_pcm_running(substream)) {
|
|
|
|
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK &&
|
|
|
|
runtime->silence_size > 0)
|
|
|
|
snd_pcm_playback_silence(substream, ULONG_MAX);
|
2010-01-07 22:36:31 +08:00
|
|
|
err = snd_pcm_update_state(substream, runtime);
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
snd_pcm_stream_unlock_irq(substream);
|
2010-01-07 22:36:31 +08:00
|
|
|
return err;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
2005-11-17 20:59:38 +08:00
|
|
|
static int snd_pcm_sw_params_user(struct snd_pcm_substream *substream,
|
|
|
|
struct snd_pcm_sw_params __user * _params)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2005-11-17 20:59:38 +08:00
|
|
|
struct snd_pcm_sw_params params;
|
2005-04-17 06:20:36 +08:00
|
|
|
int err;
|
|
|
|
if (copy_from_user(¶ms, _params, sizeof(params)))
|
|
|
|
return -EFAULT;
|
|
|
|
err = snd_pcm_sw_params(substream, ¶ms);
|
|
|
|
if (copy_to_user(_params, ¶ms, sizeof(params)))
|
|
|
|
return -EFAULT;
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2018-04-12 00:07:27 +08:00
|
|
|
static inline snd_pcm_uframes_t
|
|
|
|
snd_pcm_calc_delay(struct snd_pcm_substream *substream)
|
|
|
|
{
|
|
|
|
snd_pcm_uframes_t delay;
|
|
|
|
|
|
|
|
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
|
|
|
delay = snd_pcm_playback_hw_avail(substream->runtime);
|
|
|
|
else
|
|
|
|
delay = snd_pcm_capture_avail(substream->runtime);
|
|
|
|
return delay + substream->runtime->delay;
|
|
|
|
}
|
|
|
|
|
2018-04-24 20:06:11 +08:00
|
|
|
int snd_pcm_status64(struct snd_pcm_substream *substream,
|
|
|
|
struct snd_pcm_status64 *status)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2005-11-17 20:59:38 +08:00
|
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
snd_pcm_stream_lock_irq(substream);
|
2015-02-14 05:14:06 +08:00
|
|
|
|
|
|
|
snd_pcm_unpack_audio_tstamp_config(status->audio_tstamp_data,
|
|
|
|
&runtime->audio_tstamp_config);
|
|
|
|
|
|
|
|
/* backwards compatible behavior */
|
|
|
|
if (runtime->audio_tstamp_config.type_requested ==
|
|
|
|
SNDRV_PCM_AUDIO_TSTAMP_TYPE_COMPAT) {
|
|
|
|
if (runtime->hw.info & SNDRV_PCM_INFO_HAS_WALL_CLOCK)
|
|
|
|
runtime->audio_tstamp_config.type_requested =
|
|
|
|
SNDRV_PCM_AUDIO_TSTAMP_TYPE_LINK;
|
|
|
|
else
|
|
|
|
runtime->audio_tstamp_config.type_requested =
|
|
|
|
SNDRV_PCM_AUDIO_TSTAMP_TYPE_DEFAULT;
|
|
|
|
runtime->audio_tstamp_report.valid = 0;
|
|
|
|
} else
|
|
|
|
runtime->audio_tstamp_report.valid = 1;
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
status->state = runtime->status->state;
|
|
|
|
status->suspended_state = runtime->status->suspended_state;
|
|
|
|
if (status->state == SNDRV_PCM_STATE_OPEN)
|
|
|
|
goto _end;
|
2018-04-24 20:06:11 +08:00
|
|
|
status->trigger_tstamp_sec = runtime->trigger_tstamp.tv_sec;
|
|
|
|
status->trigger_tstamp_nsec = runtime->trigger_tstamp.tv_nsec;
|
2008-01-11 15:45:08 +08:00
|
|
|
if (snd_pcm_running(substream)) {
|
2005-04-17 06:20:36 +08:00
|
|
|
snd_pcm_update_hw_ptr(substream);
|
2008-01-11 15:45:08 +08:00
|
|
|
if (runtime->tstamp_mode == SNDRV_PCM_TSTAMP_ENABLE) {
|
2018-04-24 20:06:11 +08:00
|
|
|
status->tstamp_sec = runtime->status->tstamp.tv_sec;
|
|
|
|
status->tstamp_nsec =
|
|
|
|
runtime->status->tstamp.tv_nsec;
|
|
|
|
status->driver_tstamp_sec =
|
|
|
|
runtime->driver_tstamp.tv_sec;
|
|
|
|
status->driver_tstamp_nsec =
|
|
|
|
runtime->driver_tstamp.tv_nsec;
|
|
|
|
status->audio_tstamp_sec =
|
|
|
|
runtime->status->audio_tstamp.tv_sec;
|
|
|
|
status->audio_tstamp_nsec =
|
|
|
|
runtime->status->audio_tstamp.tv_nsec;
|
2015-02-14 05:14:06 +08:00
|
|
|
if (runtime->audio_tstamp_report.valid == 1)
|
|
|
|
/* backwards compatibility, no report provided in COMPAT mode */
|
|
|
|
snd_pcm_pack_audio_tstamp_report(&status->audio_tstamp_data,
|
|
|
|
&status->audio_tstamp_accuracy,
|
|
|
|
&runtime->audio_tstamp_report);
|
|
|
|
|
2008-01-11 15:45:08 +08:00
|
|
|
goto _tstamp_end;
|
|
|
|
}
|
2015-02-07 05:55:50 +08:00
|
|
|
} else {
|
|
|
|
/* get tstamp only in fallback mode and only if enabled */
|
2018-04-24 20:06:08 +08:00
|
|
|
if (runtime->tstamp_mode == SNDRV_PCM_TSTAMP_ENABLE) {
|
|
|
|
struct timespec64 tstamp;
|
|
|
|
|
|
|
|
snd_pcm_gettime(runtime, &tstamp);
|
2018-04-24 20:06:11 +08:00
|
|
|
status->tstamp_sec = tstamp.tv_sec;
|
|
|
|
status->tstamp_nsec = tstamp.tv_nsec;
|
2018-04-24 20:06:08 +08:00
|
|
|
}
|
2008-01-11 15:45:08 +08:00
|
|
|
}
|
|
|
|
_tstamp_end:
|
2005-04-17 06:20:36 +08:00
|
|
|
status->appl_ptr = runtime->control->appl_ptr;
|
|
|
|
status->hw_ptr = runtime->status->hw_ptr;
|
2018-04-11 23:56:52 +08:00
|
|
|
status->avail = snd_pcm_avail(substream);
|
2018-04-12 00:07:27 +08:00
|
|
|
status->delay = snd_pcm_running(substream) ?
|
|
|
|
snd_pcm_calc_delay(substream) : 0;
|
2005-04-17 06:20:36 +08:00
|
|
|
status->avail_max = runtime->avail_max;
|
|
|
|
status->overrange = runtime->overrange;
|
|
|
|
runtime->avail_max = 0;
|
|
|
|
runtime->overrange = 0;
|
|
|
|
_end:
|
|
|
|
snd_pcm_stream_unlock_irq(substream);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2018-04-24 20:06:11 +08:00
|
|
|
static int snd_pcm_status_user64(struct snd_pcm_substream *substream,
|
|
|
|
struct snd_pcm_status64 __user * _status,
|
|
|
|
bool ext)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2018-04-24 20:06:11 +08:00
|
|
|
struct snd_pcm_status64 status;
|
2005-04-17 06:20:36 +08:00
|
|
|
int res;
|
2015-02-14 05:14:04 +08:00
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
memset(&status, 0, sizeof(status));
|
2015-02-14 05:14:04 +08:00
|
|
|
/*
|
|
|
|
* with extension, parameters are read/write,
|
|
|
|
* get audio_tstamp_data from user,
|
|
|
|
* ignore rest of status structure
|
|
|
|
*/
|
|
|
|
if (ext && get_user(status.audio_tstamp_data,
|
|
|
|
(u32 __user *)(&_status->audio_tstamp_data)))
|
|
|
|
return -EFAULT;
|
2018-04-24 20:06:11 +08:00
|
|
|
res = snd_pcm_status64(substream, &status);
|
2005-04-17 06:20:36 +08:00
|
|
|
if (res < 0)
|
|
|
|
return res;
|
|
|
|
if (copy_to_user(_status, &status, sizeof(status)))
|
|
|
|
return -EFAULT;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2018-04-24 20:06:11 +08:00
|
|
|
static int snd_pcm_status_user32(struct snd_pcm_substream *substream,
|
|
|
|
struct snd_pcm_status32 __user * _status,
|
|
|
|
bool ext)
|
|
|
|
{
|
|
|
|
struct snd_pcm_status64 status64;
|
|
|
|
struct snd_pcm_status32 status32;
|
|
|
|
int res;
|
|
|
|
|
|
|
|
memset(&status64, 0, sizeof(status64));
|
|
|
|
memset(&status32, 0, sizeof(status32));
|
|
|
|
/*
|
|
|
|
* with extension, parameters are read/write,
|
|
|
|
* get audio_tstamp_data from user,
|
|
|
|
* ignore rest of status structure
|
|
|
|
*/
|
|
|
|
if (ext && get_user(status64.audio_tstamp_data,
|
|
|
|
(u32 __user *)(&_status->audio_tstamp_data)))
|
|
|
|
return -EFAULT;
|
|
|
|
res = snd_pcm_status64(substream, &status64);
|
|
|
|
if (res < 0)
|
|
|
|
return res;
|
|
|
|
|
|
|
|
status32 = (struct snd_pcm_status32) {
|
|
|
|
.state = status64.state,
|
|
|
|
.trigger_tstamp_sec = status64.trigger_tstamp_sec,
|
|
|
|
.trigger_tstamp_nsec = status64.trigger_tstamp_nsec,
|
|
|
|
.tstamp_sec = status64.tstamp_sec,
|
|
|
|
.tstamp_nsec = status64.tstamp_nsec,
|
|
|
|
.appl_ptr = status64.appl_ptr,
|
|
|
|
.hw_ptr = status64.hw_ptr,
|
|
|
|
.delay = status64.delay,
|
|
|
|
.avail = status64.avail,
|
|
|
|
.avail_max = status64.avail_max,
|
|
|
|
.overrange = status64.overrange,
|
|
|
|
.suspended_state = status64.suspended_state,
|
|
|
|
.audio_tstamp_data = status64.audio_tstamp_data,
|
|
|
|
.audio_tstamp_sec = status64.audio_tstamp_sec,
|
|
|
|
.audio_tstamp_nsec = status64.audio_tstamp_nsec,
|
|
|
|
.driver_tstamp_sec = status64.audio_tstamp_sec,
|
|
|
|
.driver_tstamp_nsec = status64.audio_tstamp_nsec,
|
|
|
|
.audio_tstamp_accuracy = status64.audio_tstamp_accuracy,
|
|
|
|
};
|
|
|
|
|
|
|
|
if (copy_to_user(_status, &status32, sizeof(status32)))
|
|
|
|
return -EFAULT;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2005-11-17 20:59:38 +08:00
|
|
|
static int snd_pcm_channel_info(struct snd_pcm_substream *substream,
|
|
|
|
struct snd_pcm_channel_info * info)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2005-11-17 20:59:38 +08:00
|
|
|
struct snd_pcm_runtime *runtime;
|
2005-04-17 06:20:36 +08:00
|
|
|
unsigned int channel;
|
|
|
|
|
|
|
|
channel = info->channel;
|
|
|
|
runtime = substream->runtime;
|
|
|
|
snd_pcm_stream_lock_irq(substream);
|
|
|
|
if (runtime->status->state == SNDRV_PCM_STATE_OPEN) {
|
|
|
|
snd_pcm_stream_unlock_irq(substream);
|
|
|
|
return -EBADFD;
|
|
|
|
}
|
|
|
|
snd_pcm_stream_unlock_irq(substream);
|
|
|
|
if (channel >= runtime->channels)
|
|
|
|
return -EINVAL;
|
|
|
|
memset(info, 0, sizeof(*info));
|
|
|
|
info->channel = channel;
|
2019-11-17 16:53:03 +08:00
|
|
|
return snd_pcm_ops_ioctl(substream, SNDRV_PCM_IOCTL1_CHANNEL_INFO, info);
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
2005-11-17 20:59:38 +08:00
|
|
|
static int snd_pcm_channel_info_user(struct snd_pcm_substream *substream,
|
|
|
|
struct snd_pcm_channel_info __user * _info)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2005-11-17 20:59:38 +08:00
|
|
|
struct snd_pcm_channel_info info;
|
2005-04-17 06:20:36 +08:00
|
|
|
int res;
|
|
|
|
|
|
|
|
if (copy_from_user(&info, _info, sizeof(info)))
|
|
|
|
return -EFAULT;
|
|
|
|
res = snd_pcm_channel_info(substream, &info);
|
|
|
|
if (res < 0)
|
|
|
|
return res;
|
|
|
|
if (copy_to_user(_info, &info, sizeof(info)))
|
|
|
|
return -EFAULT;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2005-11-17 20:59:38 +08:00
|
|
|
static void snd_pcm_trigger_tstamp(struct snd_pcm_substream *substream)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2005-11-17 20:59:38 +08:00
|
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
2005-04-17 06:20:36 +08:00
|
|
|
if (runtime->trigger_master == NULL)
|
|
|
|
return;
|
|
|
|
if (runtime->trigger_master == substream) {
|
2015-02-07 05:55:51 +08:00
|
|
|
if (!runtime->trigger_tstamp_latched)
|
|
|
|
snd_pcm_gettime(runtime, &runtime->trigger_tstamp);
|
2005-04-17 06:20:36 +08:00
|
|
|
} else {
|
|
|
|
snd_pcm_trigger_tstamp(runtime->trigger_master);
|
|
|
|
runtime->trigger_tstamp = runtime->trigger_master->runtime->trigger_tstamp;
|
|
|
|
}
|
|
|
|
runtime->trigger_master = NULL;
|
|
|
|
}
|
|
|
|
|
2020-01-31 23:22:14 +08:00
|
|
|
#define ACTION_ARG_IGNORE (__force snd_pcm_state_t)0
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
struct action_ops {
|
2020-01-31 23:22:14 +08:00
|
|
|
int (*pre_action)(struct snd_pcm_substream *substream,
|
|
|
|
snd_pcm_state_t state);
|
|
|
|
int (*do_action)(struct snd_pcm_substream *substream,
|
|
|
|
snd_pcm_state_t state);
|
|
|
|
void (*undo_action)(struct snd_pcm_substream *substream,
|
|
|
|
snd_pcm_state_t state);
|
|
|
|
void (*post_action)(struct snd_pcm_substream *substream,
|
|
|
|
snd_pcm_state_t state);
|
2005-04-17 06:20:36 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
/*
|
|
|
|
* this functions is core for handling of linked stream
|
|
|
|
* Note: the stream state might be changed also on failure
|
|
|
|
* Note2: call with calling stream lock + link lock
|
|
|
|
*/
|
2015-11-29 23:36:40 +08:00
|
|
|
static int snd_pcm_action_group(const struct action_ops *ops,
|
2005-11-17 20:59:38 +08:00
|
|
|
struct snd_pcm_substream *substream,
|
2020-01-31 23:22:14 +08:00
|
|
|
snd_pcm_state_t state,
|
|
|
|
bool do_lock)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2005-11-17 20:59:38 +08:00
|
|
|
struct snd_pcm_substream *s = NULL;
|
|
|
|
struct snd_pcm_substream *s1;
|
2014-10-21 21:32:13 +08:00
|
|
|
int res = 0, depth = 1;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2007-02-22 19:52:53 +08:00
|
|
|
snd_pcm_group_for_each_entry(s, substream) {
|
2014-08-29 21:32:29 +08:00
|
|
|
if (do_lock && s != substream) {
|
|
|
|
if (s->pcm->nonatomic)
|
2014-10-21 21:32:13 +08:00
|
|
|
mutex_lock_nested(&s->self_group.mutex, depth);
|
2014-08-29 21:32:29 +08:00
|
|
|
else
|
2014-10-21 21:32:13 +08:00
|
|
|
spin_lock_nested(&s->self_group.lock, depth);
|
|
|
|
depth++;
|
2014-08-29 21:32:29 +08:00
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
res = ops->pre_action(s, state);
|
|
|
|
if (res < 0)
|
|
|
|
goto _unlock;
|
|
|
|
}
|
2007-02-22 19:52:53 +08:00
|
|
|
snd_pcm_group_for_each_entry(s, substream) {
|
2005-04-17 06:20:36 +08:00
|
|
|
res = ops->do_action(s, state);
|
|
|
|
if (res < 0) {
|
|
|
|
if (ops->undo_action) {
|
2007-02-22 19:52:53 +08:00
|
|
|
snd_pcm_group_for_each_entry(s1, substream) {
|
2005-04-17 06:20:36 +08:00
|
|
|
if (s1 == s) /* failed stream */
|
|
|
|
break;
|
|
|
|
ops->undo_action(s1, state);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
s = NULL; /* unlock all */
|
|
|
|
goto _unlock;
|
|
|
|
}
|
|
|
|
}
|
2007-02-22 19:52:53 +08:00
|
|
|
snd_pcm_group_for_each_entry(s, substream) {
|
2005-04-17 06:20:36 +08:00
|
|
|
ops->post_action(s, state);
|
|
|
|
}
|
|
|
|
_unlock:
|
|
|
|
if (do_lock) {
|
|
|
|
/* unlock streams */
|
2007-02-22 19:52:53 +08:00
|
|
|
snd_pcm_group_for_each_entry(s1, substream) {
|
2014-08-29 21:32:29 +08:00
|
|
|
if (s1 != substream) {
|
2014-10-14 05:14:46 +08:00
|
|
|
if (s1->pcm->nonatomic)
|
2014-08-29 21:32:29 +08:00
|
|
|
mutex_unlock(&s1->self_group.mutex);
|
|
|
|
else
|
|
|
|
spin_unlock(&s1->self_group.lock);
|
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
if (s1 == s) /* end */
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Note: call with stream lock
|
|
|
|
*/
|
2015-11-29 23:36:40 +08:00
|
|
|
static int snd_pcm_action_single(const struct action_ops *ops,
|
2005-11-17 20:59:38 +08:00
|
|
|
struct snd_pcm_substream *substream,
|
2020-01-31 23:22:14 +08:00
|
|
|
snd_pcm_state_t state)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
int res;
|
|
|
|
|
|
|
|
res = ops->pre_action(substream, state);
|
|
|
|
if (res < 0)
|
|
|
|
return res;
|
|
|
|
res = ops->do_action(substream, state);
|
|
|
|
if (res == 0)
|
|
|
|
ops->post_action(substream, state);
|
|
|
|
else if (ops->undo_action)
|
|
|
|
ops->undo_action(substream, state);
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
2019-01-13 16:40:21 +08:00
|
|
|
static void snd_pcm_group_assign(struct snd_pcm_substream *substream,
|
|
|
|
struct snd_pcm_group *new_group)
|
|
|
|
{
|
|
|
|
substream->group = new_group;
|
|
|
|
list_move(&substream->link_list, &new_group->substreams);
|
|
|
|
}
|
|
|
|
|
ALSA: pcm: More fine-grained PCM link locking
We have currently two global locks, a rwlock and a rwsem, that are
used for managing linking the PCM streams. Due to these global locks,
once when a linked stream is used, the lock granularity suffers a
lot.
This patch attempts to eliminate the former global lock for atomic
ops. The latter rwsem needs remaining because of the loosy way of the
loop calls in snd_pcm_action_nonatomic(), as well as for avoiding the
deadlock at linking. However, these are used far rarely, actually
only by two actions (prepare and reset), where both are no timing
critical ones. So this can be still seen as a good improvement.
The basic strategy to eliminate the rwlock is to assure group->lock at
adding or removing a stream to / from the group. Since we already
takes the group lock whenever taking the all substream locks under the
group, this shouldn't be a big problem. The reference to group
pointer in snd_pcm_substream object is protected by the stream lock
itself.
However, there are still pitfalls: a race window at re-locking and the
lifecycle of group object. The former is a small race window for
dereferencing the substream group object opened while snd_pcm_action()
performs re-locking to avoid ABBA deadlocks. This includes the unlink
of group during that window, too. And the latter is the kfree
performed after all streams are removed from the group while it's
still dereferenced.
For addressing these corner cases, two new tricks are introduced:
- After re-locking, the group assigned to the stream is checked again;
if the group is changed, we retry the whole procedure.
- Introduce a refcount to snd_pcm_group object, so that it's freed
only when it's empty and really no one refers to it.
(Some readers might wonder why not RCU for the latter. RCU in this
case would cost more than refcounting, unfortunately. We take the
group lock sooner or later, hence the performance improvement by RCU
would be negligible. Meanwhile, because we need to deal with
schedulable context depending on the pcm->nonatomic flag, it'll become
dynamic RCU/SRCU switch, and the grace period may become too long.)
Along with these changes, there are a significant amount of code
refactoring. The complex group re-lock & ref code is factored out to
snd_pcm_stream_group_ref() function, for example.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2019-01-13 16:50:33 +08:00
|
|
|
/*
|
|
|
|
* Unref and unlock the group, but keep the stream lock;
|
|
|
|
* when the group becomes empty and no longer referred, destroy itself
|
|
|
|
*/
|
|
|
|
static void snd_pcm_group_unref(struct snd_pcm_group *group,
|
|
|
|
struct snd_pcm_substream *substream)
|
|
|
|
{
|
|
|
|
bool do_free;
|
|
|
|
|
|
|
|
if (!group)
|
|
|
|
return;
|
2019-07-19 16:55:05 +08:00
|
|
|
do_free = refcount_dec_and_test(&group->refs);
|
ALSA: pcm: More fine-grained PCM link locking
We have currently two global locks, a rwlock and a rwsem, that are
used for managing linking the PCM streams. Due to these global locks,
once when a linked stream is used, the lock granularity suffers a
lot.
This patch attempts to eliminate the former global lock for atomic
ops. The latter rwsem needs remaining because of the loosy way of the
loop calls in snd_pcm_action_nonatomic(), as well as for avoiding the
deadlock at linking. However, these are used far rarely, actually
only by two actions (prepare and reset), where both are no timing
critical ones. So this can be still seen as a good improvement.
The basic strategy to eliminate the rwlock is to assure group->lock at
adding or removing a stream to / from the group. Since we already
takes the group lock whenever taking the all substream locks under the
group, this shouldn't be a big problem. The reference to group
pointer in snd_pcm_substream object is protected by the stream lock
itself.
However, there are still pitfalls: a race window at re-locking and the
lifecycle of group object. The former is a small race window for
dereferencing the substream group object opened while snd_pcm_action()
performs re-locking to avoid ABBA deadlocks. This includes the unlink
of group during that window, too. And the latter is the kfree
performed after all streams are removed from the group while it's
still dereferenced.
For addressing these corner cases, two new tricks are introduced:
- After re-locking, the group assigned to the stream is checked again;
if the group is changed, we retry the whole procedure.
- Introduce a refcount to snd_pcm_group object, so that it's freed
only when it's empty and really no one refers to it.
(Some readers might wonder why not RCU for the latter. RCU in this
case would cost more than refcounting, unfortunately. We take the
group lock sooner or later, hence the performance improvement by RCU
would be negligible. Meanwhile, because we need to deal with
schedulable context depending on the pcm->nonatomic flag, it'll become
dynamic RCU/SRCU switch, and the grace period may become too long.)
Along with these changes, there are a significant amount of code
refactoring. The complex group re-lock & ref code is factored out to
snd_pcm_stream_group_ref() function, for example.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2019-01-13 16:50:33 +08:00
|
|
|
snd_pcm_group_unlock(group, substream->pcm->nonatomic);
|
|
|
|
if (do_free)
|
|
|
|
kfree(group);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Lock the group inside a stream lock and reference it;
|
|
|
|
* return the locked group object, or NULL if not linked
|
|
|
|
*/
|
|
|
|
static struct snd_pcm_group *
|
|
|
|
snd_pcm_stream_group_ref(struct snd_pcm_substream *substream)
|
|
|
|
{
|
|
|
|
bool nonatomic = substream->pcm->nonatomic;
|
|
|
|
struct snd_pcm_group *group;
|
|
|
|
bool trylock;
|
|
|
|
|
|
|
|
for (;;) {
|
|
|
|
if (!snd_pcm_stream_linked(substream))
|
|
|
|
return NULL;
|
|
|
|
group = substream->group;
|
|
|
|
/* block freeing the group object */
|
|
|
|
refcount_inc(&group->refs);
|
|
|
|
|
|
|
|
trylock = nonatomic ? mutex_trylock(&group->mutex) :
|
|
|
|
spin_trylock(&group->lock);
|
|
|
|
if (trylock)
|
|
|
|
break; /* OK */
|
|
|
|
|
|
|
|
/* re-lock for avoiding ABBA deadlock */
|
|
|
|
snd_pcm_stream_unlock(substream);
|
|
|
|
snd_pcm_group_lock(group, nonatomic);
|
|
|
|
snd_pcm_stream_lock(substream);
|
|
|
|
|
|
|
|
/* check the group again; the above opens a small race window */
|
|
|
|
if (substream->group == group)
|
|
|
|
break; /* OK */
|
|
|
|
/* group changed, try again */
|
|
|
|
snd_pcm_group_unref(group, substream);
|
|
|
|
}
|
|
|
|
return group;
|
|
|
|
}
|
|
|
|
|
2014-10-31 22:19:36 +08:00
|
|
|
/*
|
|
|
|
* Note: call with stream lock
|
|
|
|
*/
|
2015-11-29 23:36:40 +08:00
|
|
|
static int snd_pcm_action(const struct action_ops *ops,
|
2014-10-31 22:19:36 +08:00
|
|
|
struct snd_pcm_substream *substream,
|
2020-01-31 23:22:14 +08:00
|
|
|
snd_pcm_state_t state)
|
2014-08-29 21:32:29 +08:00
|
|
|
{
|
ALSA: pcm: More fine-grained PCM link locking
We have currently two global locks, a rwlock and a rwsem, that are
used for managing linking the PCM streams. Due to these global locks,
once when a linked stream is used, the lock granularity suffers a
lot.
This patch attempts to eliminate the former global lock for atomic
ops. The latter rwsem needs remaining because of the loosy way of the
loop calls in snd_pcm_action_nonatomic(), as well as for avoiding the
deadlock at linking. However, these are used far rarely, actually
only by two actions (prepare and reset), where both are no timing
critical ones. So this can be still seen as a good improvement.
The basic strategy to eliminate the rwlock is to assure group->lock at
adding or removing a stream to / from the group. Since we already
takes the group lock whenever taking the all substream locks under the
group, this shouldn't be a big problem. The reference to group
pointer in snd_pcm_substream object is protected by the stream lock
itself.
However, there are still pitfalls: a race window at re-locking and the
lifecycle of group object. The former is a small race window for
dereferencing the substream group object opened while snd_pcm_action()
performs re-locking to avoid ABBA deadlocks. This includes the unlink
of group during that window, too. And the latter is the kfree
performed after all streams are removed from the group while it's
still dereferenced.
For addressing these corner cases, two new tricks are introduced:
- After re-locking, the group assigned to the stream is checked again;
if the group is changed, we retry the whole procedure.
- Introduce a refcount to snd_pcm_group object, so that it's freed
only when it's empty and really no one refers to it.
(Some readers might wonder why not RCU for the latter. RCU in this
case would cost more than refcounting, unfortunately. We take the
group lock sooner or later, hence the performance improvement by RCU
would be negligible. Meanwhile, because we need to deal with
schedulable context depending on the pcm->nonatomic flag, it'll become
dynamic RCU/SRCU switch, and the grace period may become too long.)
Along with these changes, there are a significant amount of code
refactoring. The complex group re-lock & ref code is factored out to
snd_pcm_stream_group_ref() function, for example.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2019-01-13 16:50:33 +08:00
|
|
|
struct snd_pcm_group *group;
|
2014-08-29 21:32:29 +08:00
|
|
|
int res;
|
|
|
|
|
ALSA: pcm: More fine-grained PCM link locking
We have currently two global locks, a rwlock and a rwsem, that are
used for managing linking the PCM streams. Due to these global locks,
once when a linked stream is used, the lock granularity suffers a
lot.
This patch attempts to eliminate the former global lock for atomic
ops. The latter rwsem needs remaining because of the loosy way of the
loop calls in snd_pcm_action_nonatomic(), as well as for avoiding the
deadlock at linking. However, these are used far rarely, actually
only by two actions (prepare and reset), where both are no timing
critical ones. So this can be still seen as a good improvement.
The basic strategy to eliminate the rwlock is to assure group->lock at
adding or removing a stream to / from the group. Since we already
takes the group lock whenever taking the all substream locks under the
group, this shouldn't be a big problem. The reference to group
pointer in snd_pcm_substream object is protected by the stream lock
itself.
However, there are still pitfalls: a race window at re-locking and the
lifecycle of group object. The former is a small race window for
dereferencing the substream group object opened while snd_pcm_action()
performs re-locking to avoid ABBA deadlocks. This includes the unlink
of group during that window, too. And the latter is the kfree
performed after all streams are removed from the group while it's
still dereferenced.
For addressing these corner cases, two new tricks are introduced:
- After re-locking, the group assigned to the stream is checked again;
if the group is changed, we retry the whole procedure.
- Introduce a refcount to snd_pcm_group object, so that it's freed
only when it's empty and really no one refers to it.
(Some readers might wonder why not RCU for the latter. RCU in this
case would cost more than refcounting, unfortunately. We take the
group lock sooner or later, hence the performance improvement by RCU
would be negligible. Meanwhile, because we need to deal with
schedulable context depending on the pcm->nonatomic flag, it'll become
dynamic RCU/SRCU switch, and the grace period may become too long.)
Along with these changes, there are a significant amount of code
refactoring. The complex group re-lock & ref code is factored out to
snd_pcm_stream_group_ref() function, for example.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2019-01-13 16:50:33 +08:00
|
|
|
group = snd_pcm_stream_group_ref(substream);
|
|
|
|
if (group)
|
2020-01-31 23:22:14 +08:00
|
|
|
res = snd_pcm_action_group(ops, substream, state, true);
|
ALSA: pcm: More fine-grained PCM link locking
We have currently two global locks, a rwlock and a rwsem, that are
used for managing linking the PCM streams. Due to these global locks,
once when a linked stream is used, the lock granularity suffers a
lot.
This patch attempts to eliminate the former global lock for atomic
ops. The latter rwsem needs remaining because of the loosy way of the
loop calls in snd_pcm_action_nonatomic(), as well as for avoiding the
deadlock at linking. However, these are used far rarely, actually
only by two actions (prepare and reset), where both are no timing
critical ones. So this can be still seen as a good improvement.
The basic strategy to eliminate the rwlock is to assure group->lock at
adding or removing a stream to / from the group. Since we already
takes the group lock whenever taking the all substream locks under the
group, this shouldn't be a big problem. The reference to group
pointer in snd_pcm_substream object is protected by the stream lock
itself.
However, there are still pitfalls: a race window at re-locking and the
lifecycle of group object. The former is a small race window for
dereferencing the substream group object opened while snd_pcm_action()
performs re-locking to avoid ABBA deadlocks. This includes the unlink
of group during that window, too. And the latter is the kfree
performed after all streams are removed from the group while it's
still dereferenced.
For addressing these corner cases, two new tricks are introduced:
- After re-locking, the group assigned to the stream is checked again;
if the group is changed, we retry the whole procedure.
- Introduce a refcount to snd_pcm_group object, so that it's freed
only when it's empty and really no one refers to it.
(Some readers might wonder why not RCU for the latter. RCU in this
case would cost more than refcounting, unfortunately. We take the
group lock sooner or later, hence the performance improvement by RCU
would be negligible. Meanwhile, because we need to deal with
schedulable context depending on the pcm->nonatomic flag, it'll become
dynamic RCU/SRCU switch, and the grace period may become too long.)
Along with these changes, there are a significant amount of code
refactoring. The complex group re-lock & ref code is factored out to
snd_pcm_stream_group_ref() function, for example.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2019-01-13 16:50:33 +08:00
|
|
|
else
|
|
|
|
res = snd_pcm_action_single(ops, substream, state);
|
|
|
|
snd_pcm_group_unref(group, substream);
|
2005-04-17 06:20:36 +08:00
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Note: don't use any locks before
|
|
|
|
*/
|
2015-11-29 23:36:40 +08:00
|
|
|
static int snd_pcm_action_lock_irq(const struct action_ops *ops,
|
2005-11-17 20:59:38 +08:00
|
|
|
struct snd_pcm_substream *substream,
|
2020-01-31 23:22:14 +08:00
|
|
|
snd_pcm_state_t state)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
int res;
|
|
|
|
|
2014-10-31 21:45:04 +08:00
|
|
|
snd_pcm_stream_lock_irq(substream);
|
|
|
|
res = snd_pcm_action(ops, substream, state);
|
|
|
|
snd_pcm_stream_unlock_irq(substream);
|
2005-04-17 06:20:36 +08:00
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
*/
|
2015-11-29 23:36:40 +08:00
|
|
|
static int snd_pcm_action_nonatomic(const struct action_ops *ops,
|
2005-11-17 20:59:38 +08:00
|
|
|
struct snd_pcm_substream *substream,
|
2020-01-31 23:22:14 +08:00
|
|
|
snd_pcm_state_t state)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
int res;
|
|
|
|
|
ALSA: pcm: More fine-grained PCM link locking
We have currently two global locks, a rwlock and a rwsem, that are
used for managing linking the PCM streams. Due to these global locks,
once when a linked stream is used, the lock granularity suffers a
lot.
This patch attempts to eliminate the former global lock for atomic
ops. The latter rwsem needs remaining because of the loosy way of the
loop calls in snd_pcm_action_nonatomic(), as well as for avoiding the
deadlock at linking. However, these are used far rarely, actually
only by two actions (prepare and reset), where both are no timing
critical ones. So this can be still seen as a good improvement.
The basic strategy to eliminate the rwlock is to assure group->lock at
adding or removing a stream to / from the group. Since we already
takes the group lock whenever taking the all substream locks under the
group, this shouldn't be a big problem. The reference to group
pointer in snd_pcm_substream object is protected by the stream lock
itself.
However, there are still pitfalls: a race window at re-locking and the
lifecycle of group object. The former is a small race window for
dereferencing the substream group object opened while snd_pcm_action()
performs re-locking to avoid ABBA deadlocks. This includes the unlink
of group during that window, too. And the latter is the kfree
performed after all streams are removed from the group while it's
still dereferenced.
For addressing these corner cases, two new tricks are introduced:
- After re-locking, the group assigned to the stream is checked again;
if the group is changed, we retry the whole procedure.
- Introduce a refcount to snd_pcm_group object, so that it's freed
only when it's empty and really no one refers to it.
(Some readers might wonder why not RCU for the latter. RCU in this
case would cost more than refcounting, unfortunately. We take the
group lock sooner or later, hence the performance improvement by RCU
would be negligible. Meanwhile, because we need to deal with
schedulable context depending on the pcm->nonatomic flag, it'll become
dynamic RCU/SRCU switch, and the grace period may become too long.)
Along with these changes, there are a significant amount of code
refactoring. The complex group re-lock & ref code is factored out to
snd_pcm_stream_group_ref() function, for example.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2019-01-13 16:50:33 +08:00
|
|
|
/* Guarantee the group members won't change during non-atomic action */
|
2005-04-17 06:20:36 +08:00
|
|
|
down_read(&snd_pcm_link_rwsem);
|
|
|
|
if (snd_pcm_stream_linked(substream))
|
2020-01-31 23:22:14 +08:00
|
|
|
res = snd_pcm_action_group(ops, substream, state, false);
|
2005-04-17 06:20:36 +08:00
|
|
|
else
|
|
|
|
res = snd_pcm_action_single(ops, substream, state);
|
|
|
|
up_read(&snd_pcm_link_rwsem);
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* start callbacks
|
|
|
|
*/
|
2020-01-31 23:22:14 +08:00
|
|
|
static int snd_pcm_pre_start(struct snd_pcm_substream *substream,
|
|
|
|
snd_pcm_state_t state)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2005-11-17 20:59:38 +08:00
|
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
2005-04-17 06:20:36 +08:00
|
|
|
if (runtime->status->state != SNDRV_PCM_STATE_PREPARED)
|
|
|
|
return -EBADFD;
|
|
|
|
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK &&
|
|
|
|
!snd_pcm_playback_data(substream))
|
|
|
|
return -EPIPE;
|
2015-02-07 05:55:51 +08:00
|
|
|
runtime->trigger_tstamp_latched = false;
|
2005-04-17 06:20:36 +08:00
|
|
|
runtime->trigger_master = substream;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2020-01-31 23:22:14 +08:00
|
|
|
static int snd_pcm_do_start(struct snd_pcm_substream *substream,
|
|
|
|
snd_pcm_state_t state)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
if (substream->runtime->trigger_master != substream)
|
|
|
|
return 0;
|
|
|
|
return substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_START);
|
|
|
|
}
|
|
|
|
|
2020-01-31 23:22:14 +08:00
|
|
|
static void snd_pcm_undo_start(struct snd_pcm_substream *substream,
|
|
|
|
snd_pcm_state_t state)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
if (substream->runtime->trigger_master == substream)
|
|
|
|
substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_STOP);
|
|
|
|
}
|
|
|
|
|
2020-01-31 23:22:14 +08:00
|
|
|
static void snd_pcm_post_start(struct snd_pcm_substream *substream,
|
|
|
|
snd_pcm_state_t state)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2005-11-17 20:59:38 +08:00
|
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
2005-04-17 06:20:36 +08:00
|
|
|
snd_pcm_trigger_tstamp(substream);
|
2009-05-27 16:49:26 +08:00
|
|
|
runtime->hw_ptr_jiffies = jiffies;
|
2010-08-18 20:16:54 +08:00
|
|
|
runtime->hw_ptr_buffer_jiffies = (runtime->buffer_size * HZ) /
|
|
|
|
runtime->rate;
|
2005-04-17 06:20:36 +08:00
|
|
|
runtime->status->state = state;
|
|
|
|
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK &&
|
|
|
|
runtime->silence_size > 0)
|
|
|
|
snd_pcm_playback_silence(substream, ULONG_MAX);
|
2015-10-16 17:57:46 +08:00
|
|
|
snd_pcm_timer_notify(substream, SNDRV_TIMER_EVENT_MSTART);
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
2015-11-29 23:36:40 +08:00
|
|
|
static const struct action_ops snd_pcm_action_start = {
|
2005-04-17 06:20:36 +08:00
|
|
|
.pre_action = snd_pcm_pre_start,
|
|
|
|
.do_action = snd_pcm_do_start,
|
|
|
|
.undo_action = snd_pcm_undo_start,
|
|
|
|
.post_action = snd_pcm_post_start
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
2008-10-16 05:38:40 +08:00
|
|
|
* snd_pcm_start - start all linked streams
|
2005-09-07 19:38:19 +08:00
|
|
|
* @substream: the PCM substream instance
|
2013-03-12 05:05:14 +08:00
|
|
|
*
|
|
|
|
* Return: Zero if successful, or a negative error code.
|
2017-05-10 20:33:44 +08:00
|
|
|
* The stream lock must be acquired before calling this function.
|
2005-04-17 06:20:36 +08:00
|
|
|
*/
|
2005-11-17 20:59:38 +08:00
|
|
|
int snd_pcm_start(struct snd_pcm_substream *substream)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2005-11-17 20:59:38 +08:00
|
|
|
return snd_pcm_action(&snd_pcm_action_start, substream,
|
|
|
|
SNDRV_PCM_STATE_RUNNING);
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
2017-05-10 20:33:44 +08:00
|
|
|
/* take the stream lock and start the streams */
|
|
|
|
static int snd_pcm_start_lock_irq(struct snd_pcm_substream *substream)
|
|
|
|
{
|
|
|
|
return snd_pcm_action_lock_irq(&snd_pcm_action_start, substream,
|
|
|
|
SNDRV_PCM_STATE_RUNNING);
|
|
|
|
}
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
/*
|
|
|
|
* stop callbacks
|
|
|
|
*/
|
2020-01-31 23:22:14 +08:00
|
|
|
static int snd_pcm_pre_stop(struct snd_pcm_substream *substream,
|
|
|
|
snd_pcm_state_t state)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2005-11-17 20:59:38 +08:00
|
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
2005-04-17 06:20:36 +08:00
|
|
|
if (runtime->status->state == SNDRV_PCM_STATE_OPEN)
|
|
|
|
return -EBADFD;
|
|
|
|
runtime->trigger_master = substream;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2020-01-31 23:22:14 +08:00
|
|
|
static int snd_pcm_do_stop(struct snd_pcm_substream *substream,
|
|
|
|
snd_pcm_state_t state)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
if (substream->runtime->trigger_master == substream &&
|
|
|
|
snd_pcm_running(substream))
|
|
|
|
substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_STOP);
|
|
|
|
return 0; /* unconditonally stop all substreams */
|
|
|
|
}
|
|
|
|
|
2020-01-31 23:22:14 +08:00
|
|
|
static void snd_pcm_post_stop(struct snd_pcm_substream *substream,
|
|
|
|
snd_pcm_state_t state)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2005-11-17 20:59:38 +08:00
|
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
2005-04-17 06:20:36 +08:00
|
|
|
if (runtime->status->state != state) {
|
|
|
|
snd_pcm_trigger_tstamp(substream);
|
2014-11-06 19:15:25 +08:00
|
|
|
runtime->status->state = state;
|
2015-10-16 17:57:46 +08:00
|
|
|
snd_pcm_timer_notify(substream, SNDRV_TIMER_EVENT_MSTOP);
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
ALSA: pcm: Add the support for sync-stop operation
The standard programming model of a PCM sound driver is to process
snd_pcm_period_elapsed() from an interrupt handler. When a running
stream is stopped, PCM core calls the trigger-STOP PCM ops, sets the
stream state to SETUP, and moves on to the next step. This is
performed in an atomic manner -- this could be called from the interrupt
context, after all.
The problem is that, if the stream goes further and reaches to the
CLOSE state immediately, the stream might be still being processed in
snd_pcm_period_elapsed() in the interrupt context, and hits a NULL
dereference. Such a crash happens because of the atomic operation,
and we can't wait until the stream-stop finishes.
For addressing such a problem, this commit adds a new PCM ops,
sync_stop. This gets called at the appropriate places that need a
sync with the stream-stop, i.e. at hw_params, prepare and hw_free.
Some drivers already have a similar mechanism implemented locally, and
we'll refactor the code later.
Link: https://lore.kernel.org/r/20191117085308.23915-7-tiwai@suse.de
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2019-11-17 16:53:06 +08:00
|
|
|
runtime->stop_operating = true;
|
2005-04-17 06:20:36 +08:00
|
|
|
wake_up(&runtime->sleep);
|
2010-01-21 17:32:15 +08:00
|
|
|
wake_up(&runtime->tsleep);
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
2015-11-29 23:36:40 +08:00
|
|
|
static const struct action_ops snd_pcm_action_stop = {
|
2005-04-17 06:20:36 +08:00
|
|
|
.pre_action = snd_pcm_pre_stop,
|
|
|
|
.do_action = snd_pcm_do_stop,
|
|
|
|
.post_action = snd_pcm_post_stop
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
2008-10-16 05:38:40 +08:00
|
|
|
* snd_pcm_stop - try to stop all running streams in the substream group
|
2005-09-07 19:38:19 +08:00
|
|
|
* @substream: the PCM substream instance
|
|
|
|
* @state: PCM state after stopping the stream
|
2005-04-17 06:20:36 +08:00
|
|
|
*
|
2008-10-16 05:38:40 +08:00
|
|
|
* The state of each stream is then changed to the given state unconditionally.
|
2013-03-12 05:05:14 +08:00
|
|
|
*
|
2014-02-08 23:47:36 +08:00
|
|
|
* Return: Zero if successful, or a negative error code.
|
2005-04-17 06:20:36 +08:00
|
|
|
*/
|
2011-02-14 18:00:47 +08:00
|
|
|
int snd_pcm_stop(struct snd_pcm_substream *substream, snd_pcm_state_t state)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
return snd_pcm_action(&snd_pcm_action_stop, substream, state);
|
|
|
|
}
|
2006-04-28 21:13:40 +08:00
|
|
|
EXPORT_SYMBOL(snd_pcm_stop);
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
/**
|
2008-10-16 05:38:40 +08:00
|
|
|
* snd_pcm_drain_done - stop the DMA only when the given stream is playback
|
2005-09-07 19:38:19 +08:00
|
|
|
* @substream: the PCM substream
|
2005-04-17 06:20:36 +08:00
|
|
|
*
|
2008-10-16 05:38:40 +08:00
|
|
|
* After stopping, the state is changed to SETUP.
|
2005-04-17 06:20:36 +08:00
|
|
|
* Unlike snd_pcm_stop(), this affects only the given stream.
|
2013-03-12 05:05:14 +08:00
|
|
|
*
|
|
|
|
* Return: Zero if succesful, or a negative error code.
|
2005-04-17 06:20:36 +08:00
|
|
|
*/
|
2005-11-17 20:59:38 +08:00
|
|
|
int snd_pcm_drain_done(struct snd_pcm_substream *substream)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2005-11-17 20:59:38 +08:00
|
|
|
return snd_pcm_action_single(&snd_pcm_action_stop, substream,
|
|
|
|
SNDRV_PCM_STATE_SETUP);
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
2014-11-08 00:08:28 +08:00
|
|
|
/**
|
|
|
|
* snd_pcm_stop_xrun - stop the running streams as XRUN
|
|
|
|
* @substream: the PCM substream instance
|
|
|
|
*
|
|
|
|
* This stops the given running substream (and all linked substreams) as XRUN.
|
|
|
|
* Unlike snd_pcm_stop(), this function takes the substream lock by itself.
|
|
|
|
*
|
|
|
|
* Return: Zero if successful, or a negative error code.
|
|
|
|
*/
|
|
|
|
int snd_pcm_stop_xrun(struct snd_pcm_substream *substream)
|
|
|
|
{
|
|
|
|
unsigned long flags;
|
|
|
|
|
|
|
|
snd_pcm_stream_lock_irqsave(substream, flags);
|
2018-07-04 21:08:05 +08:00
|
|
|
if (substream->runtime && snd_pcm_running(substream))
|
2018-07-04 20:46:27 +08:00
|
|
|
__snd_pcm_xrun(substream);
|
2014-11-08 00:08:28 +08:00
|
|
|
snd_pcm_stream_unlock_irqrestore(substream, flags);
|
2018-07-04 20:46:27 +08:00
|
|
|
return 0;
|
2014-11-08 00:08:28 +08:00
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(snd_pcm_stop_xrun);
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
/*
|
2020-01-31 23:22:14 +08:00
|
|
|
* pause callbacks: pass boolean (to start pause or resume) as state argument
|
2005-04-17 06:20:36 +08:00
|
|
|
*/
|
2020-01-31 23:22:14 +08:00
|
|
|
#define pause_pushed(state) (__force bool)(state)
|
|
|
|
|
|
|
|
static int snd_pcm_pre_pause(struct snd_pcm_substream *substream,
|
|
|
|
snd_pcm_state_t state)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2005-11-17 20:59:38 +08:00
|
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
2005-04-17 06:20:36 +08:00
|
|
|
if (!(runtime->info & SNDRV_PCM_INFO_PAUSE))
|
|
|
|
return -ENOSYS;
|
2020-01-31 23:22:14 +08:00
|
|
|
if (pause_pushed(state)) {
|
2005-04-17 06:20:36 +08:00
|
|
|
if (runtime->status->state != SNDRV_PCM_STATE_RUNNING)
|
|
|
|
return -EBADFD;
|
|
|
|
} else if (runtime->status->state != SNDRV_PCM_STATE_PAUSED)
|
|
|
|
return -EBADFD;
|
|
|
|
runtime->trigger_master = substream;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2020-01-31 23:22:14 +08:00
|
|
|
static int snd_pcm_do_pause(struct snd_pcm_substream *substream,
|
|
|
|
snd_pcm_state_t state)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
if (substream->runtime->trigger_master != substream)
|
|
|
|
return 0;
|
2010-08-18 20:08:17 +08:00
|
|
|
/* some drivers might use hw_ptr to recover from the pause -
|
|
|
|
update the hw_ptr now */
|
2020-01-31 23:22:14 +08:00
|
|
|
if (pause_pushed(state))
|
2010-08-18 20:08:17 +08:00
|
|
|
snd_pcm_update_hw_ptr(substream);
|
2009-05-27 16:49:26 +08:00
|
|
|
/* The jiffies check in snd_pcm_update_hw_ptr*() is done by
|
tree-wide: fix comment/printk typos
"gadget", "through", "command", "maintain", "maintain", "controller", "address",
"between", "initiali[zs]e", "instead", "function", "select", "already",
"equal", "access", "management", "hierarchy", "registration", "interest",
"relative", "memory", "offset", "already",
Signed-off-by: Uwe Kleine-König <u.kleine-koenig@pengutronix.de>
Signed-off-by: Jiri Kosina <jkosina@suse.cz>
2010-11-02 03:38:34 +08:00
|
|
|
* a delta between the current jiffies, this gives a large enough
|
2009-05-27 16:49:26 +08:00
|
|
|
* delta, effectively to skip the check once.
|
|
|
|
*/
|
|
|
|
substream->runtime->hw_ptr_jiffies = jiffies - HZ * 1000;
|
2005-04-17 06:20:36 +08:00
|
|
|
return substream->ops->trigger(substream,
|
2020-01-31 23:22:14 +08:00
|
|
|
pause_pushed(state) ?
|
|
|
|
SNDRV_PCM_TRIGGER_PAUSE_PUSH :
|
|
|
|
SNDRV_PCM_TRIGGER_PAUSE_RELEASE);
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
2020-01-31 23:22:14 +08:00
|
|
|
static void snd_pcm_undo_pause(struct snd_pcm_substream *substream,
|
|
|
|
snd_pcm_state_t state)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
if (substream->runtime->trigger_master == substream)
|
|
|
|
substream->ops->trigger(substream,
|
2020-01-31 23:22:14 +08:00
|
|
|
pause_pushed(state) ?
|
|
|
|
SNDRV_PCM_TRIGGER_PAUSE_RELEASE :
|
2005-04-17 06:20:36 +08:00
|
|
|
SNDRV_PCM_TRIGGER_PAUSE_PUSH);
|
|
|
|
}
|
|
|
|
|
2020-01-31 23:22:14 +08:00
|
|
|
static void snd_pcm_post_pause(struct snd_pcm_substream *substream,
|
|
|
|
snd_pcm_state_t state)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2005-11-17 20:59:38 +08:00
|
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
2005-04-17 06:20:36 +08:00
|
|
|
snd_pcm_trigger_tstamp(substream);
|
2020-01-31 23:22:14 +08:00
|
|
|
if (pause_pushed(state)) {
|
2005-04-17 06:20:36 +08:00
|
|
|
runtime->status->state = SNDRV_PCM_STATE_PAUSED;
|
2015-10-16 17:57:46 +08:00
|
|
|
snd_pcm_timer_notify(substream, SNDRV_TIMER_EVENT_MPAUSE);
|
2005-04-17 06:20:36 +08:00
|
|
|
wake_up(&runtime->sleep);
|
2010-01-21 17:32:15 +08:00
|
|
|
wake_up(&runtime->tsleep);
|
2005-04-17 06:20:36 +08:00
|
|
|
} else {
|
|
|
|
runtime->status->state = SNDRV_PCM_STATE_RUNNING;
|
2015-10-16 17:57:46 +08:00
|
|
|
snd_pcm_timer_notify(substream, SNDRV_TIMER_EVENT_MCONTINUE);
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-11-29 23:36:40 +08:00
|
|
|
static const struct action_ops snd_pcm_action_pause = {
|
2005-04-17 06:20:36 +08:00
|
|
|
.pre_action = snd_pcm_pre_pause,
|
|
|
|
.do_action = snd_pcm_do_pause,
|
|
|
|
.undo_action = snd_pcm_undo_pause,
|
|
|
|
.post_action = snd_pcm_post_pause
|
|
|
|
};
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Push/release the pause for all linked streams.
|
|
|
|
*/
|
2020-01-31 23:22:14 +08:00
|
|
|
static int snd_pcm_pause(struct snd_pcm_substream *substream, bool push)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2020-01-31 23:22:14 +08:00
|
|
|
return snd_pcm_action(&snd_pcm_action_pause, substream,
|
|
|
|
(__force snd_pcm_state_t)push);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int snd_pcm_pause_lock_irq(struct snd_pcm_substream *substream,
|
|
|
|
bool push)
|
|
|
|
{
|
|
|
|
return snd_pcm_action_lock_irq(&snd_pcm_action_pause, substream,
|
|
|
|
(__force snd_pcm_state_t)push);
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef CONFIG_PM
|
2020-01-31 23:22:14 +08:00
|
|
|
/* suspend callback: state argument ignored */
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2020-01-31 23:22:14 +08:00
|
|
|
static int snd_pcm_pre_suspend(struct snd_pcm_substream *substream,
|
|
|
|
snd_pcm_state_t state)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2005-11-17 20:59:38 +08:00
|
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
ALSA: pcm: Don't suspend stream in unrecoverable PCM state
Currently PCM core sets each opened stream forcibly to SUSPENDED state
via snd_pcm_suspend_all() call, and the user-space is responsible for
re-triggering the resume manually either via snd_pcm_resume() or
prepare call. The scheme works fine usually, but there are corner
cases where the stream can't be resumed by that call: the streams
still in OPEN state before finishing hw_params. When they are
suspended, user-space cannot perform resume or prepare because they
haven't been set up yet. The only possible recovery is to re-open the
device, which isn't nice at all. Similarly, when a stream is in
DISCONNECTED state, it makes no sense to change it to SUSPENDED
state. Ditto for in SETUP state; which you can re-prepare directly.
So, this patch addresses these issues by filtering the PCM streams to
be suspended by checking the PCM state. When a stream is in either
OPEN, SETUP or DISCONNECTED as well as already SUSPENDED, the suspend
action is skipped.
To be noted, this problem was originally reported for the PCM runtime
PM on HD-audio. And, the runtime PM problem itself was already
addressed (although not intended) by the code refactoring commits
3d21ef0b49f8 ("ALSA: pcm: Suspend streams globally via device type PM
ops") and 17bc4815de58 ("ALSA: pci: Remove superfluous
snd_pcm_suspend*() calls"). These commits eliminated the
snd_pcm_suspend*() calls from the runtime PM suspend callback code
path, hence the racy OPEN state won't appear while runtime PM.
(FWIW, the race window is between snd_pcm_open_substream() and the
first power up in azx_pcm_open().)
Although the runtime PM issue was already "fixed", the same problem is
still present for the system PM, hence this patch is still needed.
And for stable trees, this patch alone should suffice for fixing the
runtime PM problem, too.
Reported-and-tested-by: Jon Hunter <jonathanh@nvidia.com>
Cc: <stable@vger.kernel.org>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2019-03-25 17:38:58 +08:00
|
|
|
switch (runtime->status->state) {
|
|
|
|
case SNDRV_PCM_STATE_SUSPENDED:
|
2005-04-17 06:20:36 +08:00
|
|
|
return -EBUSY;
|
ALSA: pcm: Don't suspend stream in unrecoverable PCM state
Currently PCM core sets each opened stream forcibly to SUSPENDED state
via snd_pcm_suspend_all() call, and the user-space is responsible for
re-triggering the resume manually either via snd_pcm_resume() or
prepare call. The scheme works fine usually, but there are corner
cases where the stream can't be resumed by that call: the streams
still in OPEN state before finishing hw_params. When they are
suspended, user-space cannot perform resume or prepare because they
haven't been set up yet. The only possible recovery is to re-open the
device, which isn't nice at all. Similarly, when a stream is in
DISCONNECTED state, it makes no sense to change it to SUSPENDED
state. Ditto for in SETUP state; which you can re-prepare directly.
So, this patch addresses these issues by filtering the PCM streams to
be suspended by checking the PCM state. When a stream is in either
OPEN, SETUP or DISCONNECTED as well as already SUSPENDED, the suspend
action is skipped.
To be noted, this problem was originally reported for the PCM runtime
PM on HD-audio. And, the runtime PM problem itself was already
addressed (although not intended) by the code refactoring commits
3d21ef0b49f8 ("ALSA: pcm: Suspend streams globally via device type PM
ops") and 17bc4815de58 ("ALSA: pci: Remove superfluous
snd_pcm_suspend*() calls"). These commits eliminated the
snd_pcm_suspend*() calls from the runtime PM suspend callback code
path, hence the racy OPEN state won't appear while runtime PM.
(FWIW, the race window is between snd_pcm_open_substream() and the
first power up in azx_pcm_open().)
Although the runtime PM issue was already "fixed", the same problem is
still present for the system PM, hence this patch is still needed.
And for stable trees, this patch alone should suffice for fixing the
runtime PM problem, too.
Reported-and-tested-by: Jon Hunter <jonathanh@nvidia.com>
Cc: <stable@vger.kernel.org>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2019-03-25 17:38:58 +08:00
|
|
|
/* unresumable PCM state; return -EBUSY for skipping suspend */
|
|
|
|
case SNDRV_PCM_STATE_OPEN:
|
|
|
|
case SNDRV_PCM_STATE_SETUP:
|
|
|
|
case SNDRV_PCM_STATE_DISCONNECTED:
|
|
|
|
return -EBUSY;
|
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
runtime->trigger_master = substream;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2020-01-31 23:22:14 +08:00
|
|
|
static int snd_pcm_do_suspend(struct snd_pcm_substream *substream,
|
|
|
|
snd_pcm_state_t state)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2005-11-17 20:59:38 +08:00
|
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
2005-04-17 06:20:36 +08:00
|
|
|
if (runtime->trigger_master != substream)
|
|
|
|
return 0;
|
|
|
|
if (! snd_pcm_running(substream))
|
|
|
|
return 0;
|
|
|
|
substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_SUSPEND);
|
|
|
|
return 0; /* suspend unconditionally */
|
|
|
|
}
|
|
|
|
|
2020-01-31 23:22:14 +08:00
|
|
|
static void snd_pcm_post_suspend(struct snd_pcm_substream *substream,
|
|
|
|
snd_pcm_state_t state)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2005-11-17 20:59:38 +08:00
|
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
2005-04-17 06:20:36 +08:00
|
|
|
snd_pcm_trigger_tstamp(substream);
|
2014-11-06 19:15:25 +08:00
|
|
|
runtime->status->suspended_state = runtime->status->state;
|
|
|
|
runtime->status->state = SNDRV_PCM_STATE_SUSPENDED;
|
2015-10-16 17:57:46 +08:00
|
|
|
snd_pcm_timer_notify(substream, SNDRV_TIMER_EVENT_MSUSPEND);
|
2005-04-17 06:20:36 +08:00
|
|
|
wake_up(&runtime->sleep);
|
2010-01-21 17:32:15 +08:00
|
|
|
wake_up(&runtime->tsleep);
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
2015-11-29 23:36:40 +08:00
|
|
|
static const struct action_ops snd_pcm_action_suspend = {
|
2005-04-17 06:20:36 +08:00
|
|
|
.pre_action = snd_pcm_pre_suspend,
|
|
|
|
.do_action = snd_pcm_do_suspend,
|
|
|
|
.post_action = snd_pcm_post_suspend
|
|
|
|
};
|
|
|
|
|
2019-01-15 17:54:02 +08:00
|
|
|
/*
|
2008-10-16 05:38:40 +08:00
|
|
|
* snd_pcm_suspend - trigger SUSPEND to all linked streams
|
2005-09-07 19:38:19 +08:00
|
|
|
* @substream: the PCM substream
|
2005-04-17 06:20:36 +08:00
|
|
|
*
|
|
|
|
* After this call, all streams are changed to SUSPENDED state.
|
2013-03-12 05:05:14 +08:00
|
|
|
*
|
2019-01-15 17:54:02 +08:00
|
|
|
* Return: Zero if successful, or a negative error code.
|
2005-04-17 06:20:36 +08:00
|
|
|
*/
|
2019-01-15 17:54:02 +08:00
|
|
|
static int snd_pcm_suspend(struct snd_pcm_substream *substream)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
int err;
|
|
|
|
unsigned long flags;
|
|
|
|
|
|
|
|
snd_pcm_stream_lock_irqsave(substream, flags);
|
2020-01-31 23:22:14 +08:00
|
|
|
err = snd_pcm_action(&snd_pcm_action_suspend, substream,
|
|
|
|
ACTION_ARG_IGNORE);
|
2005-04-17 06:20:36 +08:00
|
|
|
snd_pcm_stream_unlock_irqrestore(substream, flags);
|
|
|
|
return err;
|
|
|
|
}
|
2006-04-28 21:13:40 +08:00
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
/**
|
2008-10-16 05:38:40 +08:00
|
|
|
* snd_pcm_suspend_all - trigger SUSPEND to all substreams in the given pcm
|
2005-09-07 19:38:19 +08:00
|
|
|
* @pcm: the PCM instance
|
2005-04-17 06:20:36 +08:00
|
|
|
*
|
|
|
|
* After this call, all streams are changed to SUSPENDED state.
|
2013-03-12 05:05:14 +08:00
|
|
|
*
|
|
|
|
* Return: Zero if successful (or @pcm is %NULL), or a negative error code.
|
2005-04-17 06:20:36 +08:00
|
|
|
*/
|
2005-11-17 20:59:38 +08:00
|
|
|
int snd_pcm_suspend_all(struct snd_pcm *pcm)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2005-11-17 20:59:38 +08:00
|
|
|
struct snd_pcm_substream *substream;
|
2005-04-17 06:20:36 +08:00
|
|
|
int stream, err = 0;
|
|
|
|
|
2005-11-17 22:59:14 +08:00
|
|
|
if (! pcm)
|
|
|
|
return 0;
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
for (stream = 0; stream < 2; stream++) {
|
2005-11-17 20:59:38 +08:00
|
|
|
for (substream = pcm->streams[stream].substream;
|
|
|
|
substream; substream = substream->next) {
|
2005-04-17 06:20:36 +08:00
|
|
|
/* FIXME: the open/close code should lock this as well */
|
|
|
|
if (substream->runtime == NULL)
|
|
|
|
continue;
|
2019-02-09 07:29:53 +08:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Skip BE dai link PCM's that are internal and may
|
|
|
|
* not have their substream ops set.
|
|
|
|
*/
|
|
|
|
if (!substream->ops)
|
|
|
|
continue;
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
err = snd_pcm_suspend(substream);
|
|
|
|
if (err < 0 && err != -EBUSY)
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
2006-04-28 21:13:40 +08:00
|
|
|
EXPORT_SYMBOL(snd_pcm_suspend_all);
|
|
|
|
|
2020-01-31 23:22:14 +08:00
|
|
|
/* resume callbacks: state argument ignored */
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2020-01-31 23:22:14 +08:00
|
|
|
static int snd_pcm_pre_resume(struct snd_pcm_substream *substream,
|
|
|
|
snd_pcm_state_t state)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2005-11-17 20:59:38 +08:00
|
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
2005-04-17 06:20:36 +08:00
|
|
|
if (!(runtime->info & SNDRV_PCM_INFO_RESUME))
|
|
|
|
return -ENOSYS;
|
|
|
|
runtime->trigger_master = substream;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2020-01-31 23:22:14 +08:00
|
|
|
static int snd_pcm_do_resume(struct snd_pcm_substream *substream,
|
|
|
|
snd_pcm_state_t state)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2005-11-17 20:59:38 +08:00
|
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
2005-04-17 06:20:36 +08:00
|
|
|
if (runtime->trigger_master != substream)
|
|
|
|
return 0;
|
|
|
|
/* DMA not running previously? */
|
|
|
|
if (runtime->status->suspended_state != SNDRV_PCM_STATE_RUNNING &&
|
|
|
|
(runtime->status->suspended_state != SNDRV_PCM_STATE_DRAINING ||
|
|
|
|
substream->stream != SNDRV_PCM_STREAM_PLAYBACK))
|
|
|
|
return 0;
|
|
|
|
return substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_RESUME);
|
|
|
|
}
|
|
|
|
|
2020-01-31 23:22:14 +08:00
|
|
|
static void snd_pcm_undo_resume(struct snd_pcm_substream *substream,
|
|
|
|
snd_pcm_state_t state)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
if (substream->runtime->trigger_master == substream &&
|
|
|
|
snd_pcm_running(substream))
|
|
|
|
substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_SUSPEND);
|
|
|
|
}
|
|
|
|
|
2020-01-31 23:22:14 +08:00
|
|
|
static void snd_pcm_post_resume(struct snd_pcm_substream *substream,
|
|
|
|
snd_pcm_state_t state)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2005-11-17 20:59:38 +08:00
|
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
2005-04-17 06:20:36 +08:00
|
|
|
snd_pcm_trigger_tstamp(substream);
|
2014-11-06 19:15:25 +08:00
|
|
|
runtime->status->state = runtime->status->suspended_state;
|
2015-10-16 17:57:46 +08:00
|
|
|
snd_pcm_timer_notify(substream, SNDRV_TIMER_EVENT_MRESUME);
|
ALSA: pcm: Add the support for sync-stop operation
The standard programming model of a PCM sound driver is to process
snd_pcm_period_elapsed() from an interrupt handler. When a running
stream is stopped, PCM core calls the trigger-STOP PCM ops, sets the
stream state to SETUP, and moves on to the next step. This is
performed in an atomic manner -- this could be called from the interrupt
context, after all.
The problem is that, if the stream goes further and reaches to the
CLOSE state immediately, the stream might be still being processed in
snd_pcm_period_elapsed() in the interrupt context, and hits a NULL
dereference. Such a crash happens because of the atomic operation,
and we can't wait until the stream-stop finishes.
For addressing such a problem, this commit adds a new PCM ops,
sync_stop. This gets called at the appropriate places that need a
sync with the stream-stop, i.e. at hw_params, prepare and hw_free.
Some drivers already have a similar mechanism implemented locally, and
we'll refactor the code later.
Link: https://lore.kernel.org/r/20191117085308.23915-7-tiwai@suse.de
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2019-11-17 16:53:06 +08:00
|
|
|
snd_pcm_sync_stop(substream);
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
2015-11-29 23:36:40 +08:00
|
|
|
static const struct action_ops snd_pcm_action_resume = {
|
2005-04-17 06:20:36 +08:00
|
|
|
.pre_action = snd_pcm_pre_resume,
|
|
|
|
.do_action = snd_pcm_do_resume,
|
|
|
|
.undo_action = snd_pcm_undo_resume,
|
|
|
|
.post_action = snd_pcm_post_resume
|
|
|
|
};
|
|
|
|
|
2005-11-17 20:59:38 +08:00
|
|
|
static int snd_pcm_resume(struct snd_pcm_substream *substream)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2020-01-31 23:22:14 +08:00
|
|
|
return snd_pcm_action_lock_irq(&snd_pcm_action_resume, substream,
|
|
|
|
ACTION_ARG_IGNORE);
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
#else
|
|
|
|
|
2005-11-17 20:59:38 +08:00
|
|
|
static int snd_pcm_resume(struct snd_pcm_substream *substream)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
return -ENOSYS;
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif /* CONFIG_PM */
|
|
|
|
|
|
|
|
/*
|
|
|
|
* xrun ioctl
|
|
|
|
*
|
|
|
|
* Change the RUNNING stream(s) to XRUN state.
|
|
|
|
*/
|
2005-11-17 20:59:38 +08:00
|
|
|
static int snd_pcm_xrun(struct snd_pcm_substream *substream)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2005-11-17 20:59:38 +08:00
|
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
2005-04-17 06:20:36 +08:00
|
|
|
int result;
|
|
|
|
|
|
|
|
snd_pcm_stream_lock_irq(substream);
|
|
|
|
switch (runtime->status->state) {
|
|
|
|
case SNDRV_PCM_STATE_XRUN:
|
|
|
|
result = 0; /* already there */
|
|
|
|
break;
|
|
|
|
case SNDRV_PCM_STATE_RUNNING:
|
2018-07-04 20:46:27 +08:00
|
|
|
__snd_pcm_xrun(substream);
|
|
|
|
result = 0;
|
2005-04-17 06:20:36 +08:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
result = -EBADFD;
|
|
|
|
}
|
|
|
|
snd_pcm_stream_unlock_irq(substream);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* reset ioctl
|
|
|
|
*/
|
2020-01-31 23:22:14 +08:00
|
|
|
/* reset callbacks: state argument ignored */
|
|
|
|
static int snd_pcm_pre_reset(struct snd_pcm_substream *substream,
|
|
|
|
snd_pcm_state_t state)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2005-11-17 20:59:38 +08:00
|
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
2005-04-17 06:20:36 +08:00
|
|
|
switch (runtime->status->state) {
|
|
|
|
case SNDRV_PCM_STATE_RUNNING:
|
|
|
|
case SNDRV_PCM_STATE_PREPARED:
|
|
|
|
case SNDRV_PCM_STATE_PAUSED:
|
|
|
|
case SNDRV_PCM_STATE_SUSPENDED:
|
|
|
|
return 0;
|
|
|
|
default:
|
|
|
|
return -EBADFD;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-31 23:22:14 +08:00
|
|
|
static int snd_pcm_do_reset(struct snd_pcm_substream *substream,
|
|
|
|
snd_pcm_state_t state)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2005-11-17 20:59:38 +08:00
|
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
2019-11-17 16:53:03 +08:00
|
|
|
int err = snd_pcm_ops_ioctl(substream, SNDRV_PCM_IOCTL1_RESET, NULL);
|
2005-04-17 06:20:36 +08:00
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
runtime->hw_ptr_base = 0;
|
2010-01-27 00:08:24 +08:00
|
|
|
runtime->hw_ptr_interrupt = runtime->status->hw_ptr -
|
|
|
|
runtime->status->hw_ptr % runtime->period_size;
|
2005-04-17 06:20:36 +08:00
|
|
|
runtime->silence_start = runtime->status->hw_ptr;
|
|
|
|
runtime->silence_filled = 0;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2020-01-31 23:22:14 +08:00
|
|
|
static void snd_pcm_post_reset(struct snd_pcm_substream *substream,
|
|
|
|
snd_pcm_state_t state)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2005-11-17 20:59:38 +08:00
|
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
2005-04-17 06:20:36 +08:00
|
|
|
runtime->control->appl_ptr = runtime->status->hw_ptr;
|
|
|
|
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK &&
|
|
|
|
runtime->silence_size > 0)
|
|
|
|
snd_pcm_playback_silence(substream, ULONG_MAX);
|
|
|
|
}
|
|
|
|
|
2015-11-29 23:36:40 +08:00
|
|
|
static const struct action_ops snd_pcm_action_reset = {
|
2005-04-17 06:20:36 +08:00
|
|
|
.pre_action = snd_pcm_pre_reset,
|
|
|
|
.do_action = snd_pcm_do_reset,
|
|
|
|
.post_action = snd_pcm_post_reset
|
|
|
|
};
|
|
|
|
|
2005-11-17 20:59:38 +08:00
|
|
|
static int snd_pcm_reset(struct snd_pcm_substream *substream)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2020-01-31 23:22:14 +08:00
|
|
|
return snd_pcm_action_nonatomic(&snd_pcm_action_reset, substream,
|
|
|
|
ACTION_ARG_IGNORE);
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* prepare ioctl
|
|
|
|
*/
|
2020-01-31 23:22:14 +08:00
|
|
|
/* pass f_flags as state argument */
|
2006-04-28 21:13:41 +08:00
|
|
|
static int snd_pcm_pre_prepare(struct snd_pcm_substream *substream,
|
2020-01-31 23:22:14 +08:00
|
|
|
snd_pcm_state_t state)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2005-11-17 20:59:38 +08:00
|
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
2020-01-31 23:22:14 +08:00
|
|
|
int f_flags = (__force int)state;
|
|
|
|
|
2006-11-08 22:41:29 +08:00
|
|
|
if (runtime->status->state == SNDRV_PCM_STATE_OPEN ||
|
|
|
|
runtime->status->state == SNDRV_PCM_STATE_DISCONNECTED)
|
2005-04-17 06:20:36 +08:00
|
|
|
return -EBADFD;
|
|
|
|
if (snd_pcm_running(substream))
|
|
|
|
return -EBUSY;
|
2006-04-28 21:13:41 +08:00
|
|
|
substream->f_flags = f_flags;
|
2005-04-17 06:20:36 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2020-01-31 23:22:14 +08:00
|
|
|
static int snd_pcm_do_prepare(struct snd_pcm_substream *substream,
|
|
|
|
snd_pcm_state_t state)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
int err;
|
ALSA: pcm: Add the support for sync-stop operation
The standard programming model of a PCM sound driver is to process
snd_pcm_period_elapsed() from an interrupt handler. When a running
stream is stopped, PCM core calls the trigger-STOP PCM ops, sets the
stream state to SETUP, and moves on to the next step. This is
performed in an atomic manner -- this could be called from the interrupt
context, after all.
The problem is that, if the stream goes further and reaches to the
CLOSE state immediately, the stream might be still being processed in
snd_pcm_period_elapsed() in the interrupt context, and hits a NULL
dereference. Such a crash happens because of the atomic operation,
and we can't wait until the stream-stop finishes.
For addressing such a problem, this commit adds a new PCM ops,
sync_stop. This gets called at the appropriate places that need a
sync with the stream-stop, i.e. at hw_params, prepare and hw_free.
Some drivers already have a similar mechanism implemented locally, and
we'll refactor the code later.
Link: https://lore.kernel.org/r/20191117085308.23915-7-tiwai@suse.de
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2019-11-17 16:53:06 +08:00
|
|
|
snd_pcm_sync_stop(substream);
|
2005-04-17 06:20:36 +08:00
|
|
|
err = substream->ops->prepare(substream);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
2020-01-31 23:22:14 +08:00
|
|
|
return snd_pcm_do_reset(substream, state);
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
2020-01-31 23:22:14 +08:00
|
|
|
static void snd_pcm_post_prepare(struct snd_pcm_substream *substream,
|
|
|
|
snd_pcm_state_t state)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2005-11-17 20:59:38 +08:00
|
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
2005-04-17 06:20:36 +08:00
|
|
|
runtime->control->appl_ptr = runtime->status->hw_ptr;
|
2012-10-12 21:07:34 +08:00
|
|
|
snd_pcm_set_state(substream, SNDRV_PCM_STATE_PREPARED);
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
2015-11-29 23:36:40 +08:00
|
|
|
static const struct action_ops snd_pcm_action_prepare = {
|
2005-04-17 06:20:36 +08:00
|
|
|
.pre_action = snd_pcm_pre_prepare,
|
|
|
|
.do_action = snd_pcm_do_prepare,
|
|
|
|
.post_action = snd_pcm_post_prepare
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
2008-10-16 05:38:40 +08:00
|
|
|
* snd_pcm_prepare - prepare the PCM substream to be triggerable
|
2005-09-07 19:38:19 +08:00
|
|
|
* @substream: the PCM substream instance
|
2006-04-28 21:13:41 +08:00
|
|
|
* @file: file to refer f_flags
|
2013-03-12 05:05:14 +08:00
|
|
|
*
|
|
|
|
* Return: Zero if successful, or a negative error code.
|
2005-04-17 06:20:36 +08:00
|
|
|
*/
|
2006-04-28 21:13:41 +08:00
|
|
|
static int snd_pcm_prepare(struct snd_pcm_substream *substream,
|
|
|
|
struct file *file)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2006-04-28 21:13:41 +08:00
|
|
|
int f_flags;
|
|
|
|
|
|
|
|
if (file)
|
|
|
|
f_flags = file->f_flags;
|
|
|
|
else
|
|
|
|
f_flags = substream->f_flags;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2016-05-24 21:40:03 +08:00
|
|
|
snd_pcm_stream_lock_irq(substream);
|
|
|
|
switch (substream->runtime->status->state) {
|
|
|
|
case SNDRV_PCM_STATE_PAUSED:
|
2020-01-31 23:22:14 +08:00
|
|
|
snd_pcm_pause(substream, false);
|
2020-07-09 04:32:36 +08:00
|
|
|
fallthrough;
|
2016-05-24 21:40:03 +08:00
|
|
|
case SNDRV_PCM_STATE_SUSPENDED:
|
|
|
|
snd_pcm_stop(substream, SNDRV_PCM_STATE_SETUP);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
snd_pcm_stream_unlock_irq(substream);
|
|
|
|
|
2016-05-24 21:07:39 +08:00
|
|
|
return snd_pcm_action_nonatomic(&snd_pcm_action_prepare,
|
2020-01-31 23:22:14 +08:00
|
|
|
substream,
|
|
|
|
(__force snd_pcm_state_t)f_flags);
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* drain ioctl
|
|
|
|
*/
|
|
|
|
|
2020-01-31 23:22:14 +08:00
|
|
|
/* drain init callbacks: state argument ignored */
|
|
|
|
static int snd_pcm_pre_drain_init(struct snd_pcm_substream *substream,
|
|
|
|
snd_pcm_state_t state)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2012-05-21 17:59:57 +08:00
|
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
|
|
|
switch (runtime->status->state) {
|
|
|
|
case SNDRV_PCM_STATE_OPEN:
|
|
|
|
case SNDRV_PCM_STATE_DISCONNECTED:
|
|
|
|
case SNDRV_PCM_STATE_SUSPENDED:
|
|
|
|
return -EBADFD;
|
|
|
|
}
|
|
|
|
runtime->trigger_master = substream;
|
2005-04-17 06:20:36 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2020-01-31 23:22:14 +08:00
|
|
|
static int snd_pcm_do_drain_init(struct snd_pcm_substream *substream,
|
|
|
|
snd_pcm_state_t state)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2005-11-17 20:59:38 +08:00
|
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
2005-04-17 06:20:36 +08:00
|
|
|
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
|
|
|
switch (runtime->status->state) {
|
|
|
|
case SNDRV_PCM_STATE_PREPARED:
|
|
|
|
/* start playback stream if possible */
|
|
|
|
if (! snd_pcm_playback_empty(substream)) {
|
|
|
|
snd_pcm_do_start(substream, SNDRV_PCM_STATE_DRAINING);
|
|
|
|
snd_pcm_post_start(substream, SNDRV_PCM_STATE_DRAINING);
|
2014-12-18 17:02:41 +08:00
|
|
|
} else {
|
|
|
|
runtime->status->state = SNDRV_PCM_STATE_SETUP;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case SNDRV_PCM_STATE_RUNNING:
|
|
|
|
runtime->status->state = SNDRV_PCM_STATE_DRAINING;
|
|
|
|
break;
|
2012-05-21 17:59:57 +08:00
|
|
|
case SNDRV_PCM_STATE_XRUN:
|
|
|
|
runtime->status->state = SNDRV_PCM_STATE_SETUP;
|
|
|
|
break;
|
2005-04-17 06:20:36 +08:00
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
/* stop running stream */
|
|
|
|
if (runtime->status->state == SNDRV_PCM_STATE_RUNNING) {
|
2020-01-31 23:22:14 +08:00
|
|
|
snd_pcm_state_t new_state;
|
|
|
|
|
|
|
|
new_state = snd_pcm_capture_avail(runtime) > 0 ?
|
2005-04-17 06:20:36 +08:00
|
|
|
SNDRV_PCM_STATE_DRAINING : SNDRV_PCM_STATE_SETUP;
|
2007-12-14 19:50:16 +08:00
|
|
|
snd_pcm_do_stop(substream, new_state);
|
|
|
|
snd_pcm_post_stop(substream, new_state);
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
}
|
2014-12-31 22:09:54 +08:00
|
|
|
|
|
|
|
if (runtime->status->state == SNDRV_PCM_STATE_DRAINING &&
|
|
|
|
runtime->trigger_master == substream &&
|
|
|
|
(runtime->hw.info & SNDRV_PCM_INFO_DRAIN_TRIGGER))
|
|
|
|
return substream->ops->trigger(substream,
|
|
|
|
SNDRV_PCM_TRIGGER_DRAIN);
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2020-01-31 23:22:14 +08:00
|
|
|
static void snd_pcm_post_drain_init(struct snd_pcm_substream *substream,
|
|
|
|
snd_pcm_state_t state)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2015-11-29 23:36:40 +08:00
|
|
|
static const struct action_ops snd_pcm_action_drain_init = {
|
2005-04-17 06:20:36 +08:00
|
|
|
.pre_action = snd_pcm_pre_drain_init,
|
|
|
|
.do_action = snd_pcm_do_drain_init,
|
|
|
|
.post_action = snd_pcm_post_drain_init
|
|
|
|
};
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Drain the stream(s).
|
|
|
|
* When the substream is linked, sync until the draining of all playback streams
|
|
|
|
* is finished.
|
|
|
|
* After this call, all streams are supposed to be either SETUP or DRAINING
|
|
|
|
* (capture only) state.
|
|
|
|
*/
|
2009-08-20 22:40:16 +08:00
|
|
|
static int snd_pcm_drain(struct snd_pcm_substream *substream,
|
|
|
|
struct file *file)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2005-11-17 20:59:38 +08:00
|
|
|
struct snd_card *card;
|
|
|
|
struct snd_pcm_runtime *runtime;
|
2007-02-22 19:52:53 +08:00
|
|
|
struct snd_pcm_substream *s;
|
ALSA: pcm: More fine-grained PCM link locking
We have currently two global locks, a rwlock and a rwsem, that are
used for managing linking the PCM streams. Due to these global locks,
once when a linked stream is used, the lock granularity suffers a
lot.
This patch attempts to eliminate the former global lock for atomic
ops. The latter rwsem needs remaining because of the loosy way of the
loop calls in snd_pcm_action_nonatomic(), as well as for avoiding the
deadlock at linking. However, these are used far rarely, actually
only by two actions (prepare and reset), where both are no timing
critical ones. So this can be still seen as a good improvement.
The basic strategy to eliminate the rwlock is to assure group->lock at
adding or removing a stream to / from the group. Since we already
takes the group lock whenever taking the all substream locks under the
group, this shouldn't be a big problem. The reference to group
pointer in snd_pcm_substream object is protected by the stream lock
itself.
However, there are still pitfalls: a race window at re-locking and the
lifecycle of group object. The former is a small race window for
dereferencing the substream group object opened while snd_pcm_action()
performs re-locking to avoid ABBA deadlocks. This includes the unlink
of group during that window, too. And the latter is the kfree
performed after all streams are removed from the group while it's
still dereferenced.
For addressing these corner cases, two new tricks are introduced:
- After re-locking, the group assigned to the stream is checked again;
if the group is changed, we retry the whole procedure.
- Introduce a refcount to snd_pcm_group object, so that it's freed
only when it's empty and really no one refers to it.
(Some readers might wonder why not RCU for the latter. RCU in this
case would cost more than refcounting, unfortunately. We take the
group lock sooner or later, hence the performance improvement by RCU
would be negligible. Meanwhile, because we need to deal with
schedulable context depending on the pcm->nonatomic flag, it'll become
dynamic RCU/SRCU switch, and the grace period may become too long.)
Along with these changes, there are a significant amount of code
refactoring. The complex group re-lock & ref code is factored out to
snd_pcm_stream_group_ref() function, for example.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2019-01-13 16:50:33 +08:00
|
|
|
struct snd_pcm_group *group;
|
2017-06-20 18:06:13 +08:00
|
|
|
wait_queue_entry_t wait;
|
2005-04-17 06:20:36 +08:00
|
|
|
int result = 0;
|
2009-08-20 22:40:16 +08:00
|
|
|
int nonblock = 0;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
card = substream->pcm->card;
|
|
|
|
runtime = substream->runtime;
|
|
|
|
|
|
|
|
if (runtime->status->state == SNDRV_PCM_STATE_OPEN)
|
|
|
|
return -EBADFD;
|
|
|
|
|
2009-08-20 22:40:16 +08:00
|
|
|
if (file) {
|
|
|
|
if (file->f_flags & O_NONBLOCK)
|
|
|
|
nonblock = 1;
|
|
|
|
} else if (substream->f_flags & O_NONBLOCK)
|
|
|
|
nonblock = 1;
|
|
|
|
|
2005-05-31 20:35:31 +08:00
|
|
|
snd_pcm_stream_lock_irq(substream);
|
|
|
|
/* resume pause */
|
2009-09-18 00:46:26 +08:00
|
|
|
if (runtime->status->state == SNDRV_PCM_STATE_PAUSED)
|
2020-01-31 23:22:14 +08:00
|
|
|
snd_pcm_pause(substream, false);
|
2005-05-31 20:35:31 +08:00
|
|
|
|
|
|
|
/* pre-start/stop - all running streams are changed to DRAINING state */
|
2020-01-31 23:22:14 +08:00
|
|
|
result = snd_pcm_action(&snd_pcm_action_drain_init, substream,
|
|
|
|
ACTION_ARG_IGNORE);
|
2009-08-20 22:40:16 +08:00
|
|
|
if (result < 0)
|
|
|
|
goto unlock;
|
|
|
|
/* in non-blocking, we don't wait in ioctl but let caller poll */
|
|
|
|
if (nonblock) {
|
|
|
|
result = -EAGAIN;
|
|
|
|
goto unlock;
|
2005-05-31 20:35:31 +08:00
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
for (;;) {
|
|
|
|
long tout;
|
2009-09-18 00:46:26 +08:00
|
|
|
struct snd_pcm_runtime *to_check;
|
2005-04-17 06:20:36 +08:00
|
|
|
if (signal_pending(current)) {
|
|
|
|
result = -ERESTARTSYS;
|
|
|
|
break;
|
|
|
|
}
|
2009-09-18 00:46:26 +08:00
|
|
|
/* find a substream to drain */
|
|
|
|
to_check = NULL;
|
ALSA: pcm: More fine-grained PCM link locking
We have currently two global locks, a rwlock and a rwsem, that are
used for managing linking the PCM streams. Due to these global locks,
once when a linked stream is used, the lock granularity suffers a
lot.
This patch attempts to eliminate the former global lock for atomic
ops. The latter rwsem needs remaining because of the loosy way of the
loop calls in snd_pcm_action_nonatomic(), as well as for avoiding the
deadlock at linking. However, these are used far rarely, actually
only by two actions (prepare and reset), where both are no timing
critical ones. So this can be still seen as a good improvement.
The basic strategy to eliminate the rwlock is to assure group->lock at
adding or removing a stream to / from the group. Since we already
takes the group lock whenever taking the all substream locks under the
group, this shouldn't be a big problem. The reference to group
pointer in snd_pcm_substream object is protected by the stream lock
itself.
However, there are still pitfalls: a race window at re-locking and the
lifecycle of group object. The former is a small race window for
dereferencing the substream group object opened while snd_pcm_action()
performs re-locking to avoid ABBA deadlocks. This includes the unlink
of group during that window, too. And the latter is the kfree
performed after all streams are removed from the group while it's
still dereferenced.
For addressing these corner cases, two new tricks are introduced:
- After re-locking, the group assigned to the stream is checked again;
if the group is changed, we retry the whole procedure.
- Introduce a refcount to snd_pcm_group object, so that it's freed
only when it's empty and really no one refers to it.
(Some readers might wonder why not RCU for the latter. RCU in this
case would cost more than refcounting, unfortunately. We take the
group lock sooner or later, hence the performance improvement by RCU
would be negligible. Meanwhile, because we need to deal with
schedulable context depending on the pcm->nonatomic flag, it'll become
dynamic RCU/SRCU switch, and the grace period may become too long.)
Along with these changes, there are a significant amount of code
refactoring. The complex group re-lock & ref code is factored out to
snd_pcm_stream_group_ref() function, for example.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2019-01-13 16:50:33 +08:00
|
|
|
group = snd_pcm_stream_group_ref(substream);
|
2009-09-18 00:46:26 +08:00
|
|
|
snd_pcm_group_for_each_entry(s, substream) {
|
|
|
|
if (s->stream != SNDRV_PCM_STREAM_PLAYBACK)
|
|
|
|
continue;
|
|
|
|
runtime = s->runtime;
|
|
|
|
if (runtime->status->state == SNDRV_PCM_STATE_DRAINING) {
|
|
|
|
to_check = runtime;
|
2005-05-31 20:35:31 +08:00
|
|
|
break;
|
2009-09-18 00:46:26 +08:00
|
|
|
}
|
2005-05-31 20:35:31 +08:00
|
|
|
}
|
ALSA: pcm: More fine-grained PCM link locking
We have currently two global locks, a rwlock and a rwsem, that are
used for managing linking the PCM streams. Due to these global locks,
once when a linked stream is used, the lock granularity suffers a
lot.
This patch attempts to eliminate the former global lock for atomic
ops. The latter rwsem needs remaining because of the loosy way of the
loop calls in snd_pcm_action_nonatomic(), as well as for avoiding the
deadlock at linking. However, these are used far rarely, actually
only by two actions (prepare and reset), where both are no timing
critical ones. So this can be still seen as a good improvement.
The basic strategy to eliminate the rwlock is to assure group->lock at
adding or removing a stream to / from the group. Since we already
takes the group lock whenever taking the all substream locks under the
group, this shouldn't be a big problem. The reference to group
pointer in snd_pcm_substream object is protected by the stream lock
itself.
However, there are still pitfalls: a race window at re-locking and the
lifecycle of group object. The former is a small race window for
dereferencing the substream group object opened while snd_pcm_action()
performs re-locking to avoid ABBA deadlocks. This includes the unlink
of group during that window, too. And the latter is the kfree
performed after all streams are removed from the group while it's
still dereferenced.
For addressing these corner cases, two new tricks are introduced:
- After re-locking, the group assigned to the stream is checked again;
if the group is changed, we retry the whole procedure.
- Introduce a refcount to snd_pcm_group object, so that it's freed
only when it's empty and really no one refers to it.
(Some readers might wonder why not RCU for the latter. RCU in this
case would cost more than refcounting, unfortunately. We take the
group lock sooner or later, hence the performance improvement by RCU
would be negligible. Meanwhile, because we need to deal with
schedulable context depending on the pcm->nonatomic flag, it'll become
dynamic RCU/SRCU switch, and the grace period may become too long.)
Along with these changes, there are a significant amount of code
refactoring. The complex group re-lock & ref code is factored out to
snd_pcm_stream_group_ref() function, for example.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2019-01-13 16:50:33 +08:00
|
|
|
snd_pcm_group_unref(group, substream);
|
2009-09-18 00:46:26 +08:00
|
|
|
if (!to_check)
|
|
|
|
break; /* all drained */
|
|
|
|
init_waitqueue_entry(&wait, current);
|
2019-07-29 23:10:36 +08:00
|
|
|
set_current_state(TASK_INTERRUPTIBLE);
|
2009-09-18 00:46:26 +08:00
|
|
|
add_wait_queue(&to_check->sleep, &wait);
|
2005-04-17 06:20:36 +08:00
|
|
|
snd_pcm_stream_unlock_irq(substream);
|
2011-05-26 14:09:38 +08:00
|
|
|
if (runtime->no_period_wakeup)
|
|
|
|
tout = MAX_SCHEDULE_TIMEOUT;
|
|
|
|
else {
|
|
|
|
tout = 10;
|
|
|
|
if (runtime->rate) {
|
|
|
|
long t = runtime->period_size * 2 / runtime->rate;
|
|
|
|
tout = max(t, tout);
|
|
|
|
}
|
|
|
|
tout = msecs_to_jiffies(tout * 1000);
|
|
|
|
}
|
2019-07-29 23:10:36 +08:00
|
|
|
tout = schedule_timeout(tout);
|
ALSA: pcm: More fine-grained PCM link locking
We have currently two global locks, a rwlock and a rwsem, that are
used for managing linking the PCM streams. Due to these global locks,
once when a linked stream is used, the lock granularity suffers a
lot.
This patch attempts to eliminate the former global lock for atomic
ops. The latter rwsem needs remaining because of the loosy way of the
loop calls in snd_pcm_action_nonatomic(), as well as for avoiding the
deadlock at linking. However, these are used far rarely, actually
only by two actions (prepare and reset), where both are no timing
critical ones. So this can be still seen as a good improvement.
The basic strategy to eliminate the rwlock is to assure group->lock at
adding or removing a stream to / from the group. Since we already
takes the group lock whenever taking the all substream locks under the
group, this shouldn't be a big problem. The reference to group
pointer in snd_pcm_substream object is protected by the stream lock
itself.
However, there are still pitfalls: a race window at re-locking and the
lifecycle of group object. The former is a small race window for
dereferencing the substream group object opened while snd_pcm_action()
performs re-locking to avoid ABBA deadlocks. This includes the unlink
of group during that window, too. And the latter is the kfree
performed after all streams are removed from the group while it's
still dereferenced.
For addressing these corner cases, two new tricks are introduced:
- After re-locking, the group assigned to the stream is checked again;
if the group is changed, we retry the whole procedure.
- Introduce a refcount to snd_pcm_group object, so that it's freed
only when it's empty and really no one refers to it.
(Some readers might wonder why not RCU for the latter. RCU in this
case would cost more than refcounting, unfortunately. We take the
group lock sooner or later, hence the performance improvement by RCU
would be negligible. Meanwhile, because we need to deal with
schedulable context depending on the pcm->nonatomic flag, it'll become
dynamic RCU/SRCU switch, and the grace period may become too long.)
Along with these changes, there are a significant amount of code
refactoring. The complex group re-lock & ref code is factored out to
snd_pcm_stream_group_ref() function, for example.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2019-01-13 16:50:33 +08:00
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
snd_pcm_stream_lock_irq(substream);
|
ALSA: pcm: More fine-grained PCM link locking
We have currently two global locks, a rwlock and a rwsem, that are
used for managing linking the PCM streams. Due to these global locks,
once when a linked stream is used, the lock granularity suffers a
lot.
This patch attempts to eliminate the former global lock for atomic
ops. The latter rwsem needs remaining because of the loosy way of the
loop calls in snd_pcm_action_nonatomic(), as well as for avoiding the
deadlock at linking. However, these are used far rarely, actually
only by two actions (prepare and reset), where both are no timing
critical ones. So this can be still seen as a good improvement.
The basic strategy to eliminate the rwlock is to assure group->lock at
adding or removing a stream to / from the group. Since we already
takes the group lock whenever taking the all substream locks under the
group, this shouldn't be a big problem. The reference to group
pointer in snd_pcm_substream object is protected by the stream lock
itself.
However, there are still pitfalls: a race window at re-locking and the
lifecycle of group object. The former is a small race window for
dereferencing the substream group object opened while snd_pcm_action()
performs re-locking to avoid ABBA deadlocks. This includes the unlink
of group during that window, too. And the latter is the kfree
performed after all streams are removed from the group while it's
still dereferenced.
For addressing these corner cases, two new tricks are introduced:
- After re-locking, the group assigned to the stream is checked again;
if the group is changed, we retry the whole procedure.
- Introduce a refcount to snd_pcm_group object, so that it's freed
only when it's empty and really no one refers to it.
(Some readers might wonder why not RCU for the latter. RCU in this
case would cost more than refcounting, unfortunately. We take the
group lock sooner or later, hence the performance improvement by RCU
would be negligible. Meanwhile, because we need to deal with
schedulable context depending on the pcm->nonatomic flag, it'll become
dynamic RCU/SRCU switch, and the grace period may become too long.)
Along with these changes, there are a significant amount of code
refactoring. The complex group re-lock & ref code is factored out to
snd_pcm_stream_group_ref() function, for example.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2019-01-13 16:50:33 +08:00
|
|
|
group = snd_pcm_stream_group_ref(substream);
|
|
|
|
snd_pcm_group_for_each_entry(s, substream) {
|
|
|
|
if (s->runtime == to_check) {
|
|
|
|
remove_wait_queue(&to_check->sleep, &wait);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
snd_pcm_group_unref(group, substream);
|
|
|
|
|
2012-10-16 22:43:39 +08:00
|
|
|
if (card->shutdown) {
|
|
|
|
result = -ENODEV;
|
|
|
|
break;
|
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
if (tout == 0) {
|
|
|
|
if (substream->runtime->status->state == SNDRV_PCM_STATE_SUSPENDED)
|
|
|
|
result = -ESTRPIPE;
|
|
|
|
else {
|
2014-02-05 01:19:48 +08:00
|
|
|
dev_dbg(substream->pcm->card->dev,
|
|
|
|
"playback drain error (DMA or IRQ trouble?)\n");
|
2005-04-17 06:20:36 +08:00
|
|
|
snd_pcm_stop(substream, SNDRV_PCM_STATE_SETUP);
|
|
|
|
result = -EIO;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2005-05-31 20:35:31 +08:00
|
|
|
|
2009-08-20 22:40:16 +08:00
|
|
|
unlock:
|
2005-05-31 20:35:31 +08:00
|
|
|
snd_pcm_stream_unlock_irq(substream);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* drop ioctl
|
|
|
|
*
|
|
|
|
* Immediately put all linked substreams into SETUP state.
|
|
|
|
*/
|
2005-11-17 20:59:38 +08:00
|
|
|
static int snd_pcm_drop(struct snd_pcm_substream *substream)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2005-11-17 20:59:38 +08:00
|
|
|
struct snd_pcm_runtime *runtime;
|
2005-04-17 06:20:36 +08:00
|
|
|
int result = 0;
|
|
|
|
|
2008-08-08 23:09:09 +08:00
|
|
|
if (PCM_RUNTIME_CHECK(substream))
|
|
|
|
return -ENXIO;
|
2005-04-17 06:20:36 +08:00
|
|
|
runtime = substream->runtime;
|
|
|
|
|
2006-11-08 22:41:29 +08:00
|
|
|
if (runtime->status->state == SNDRV_PCM_STATE_OPEN ||
|
2016-05-24 21:08:31 +08:00
|
|
|
runtime->status->state == SNDRV_PCM_STATE_DISCONNECTED)
|
2005-04-17 06:20:36 +08:00
|
|
|
return -EBADFD;
|
|
|
|
|
|
|
|
snd_pcm_stream_lock_irq(substream);
|
|
|
|
/* resume pause */
|
|
|
|
if (runtime->status->state == SNDRV_PCM_STATE_PAUSED)
|
2020-01-31 23:22:14 +08:00
|
|
|
snd_pcm_pause(substream, false);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
snd_pcm_stop(substream, SNDRV_PCM_STATE_SETUP);
|
|
|
|
/* runtime->control->appl_ptr = runtime->status->hw_ptr; */
|
|
|
|
snd_pcm_stream_unlock_irq(substream);
|
2008-09-25 23:51:11 +08:00
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-06-06 02:09:55 +08:00
|
|
|
static bool is_pcm_file(struct file *file)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2013-06-06 02:09:55 +08:00
|
|
|
struct inode *inode = file_inode(file);
|
2019-01-13 16:25:42 +08:00
|
|
|
struct snd_pcm *pcm;
|
2005-11-20 21:06:59 +08:00
|
|
|
unsigned int minor;
|
|
|
|
|
2013-06-06 02:09:55 +08:00
|
|
|
if (!S_ISCHR(inode->i_mode) || imajor(inode) != snd_major)
|
|
|
|
return false;
|
2005-04-17 06:20:36 +08:00
|
|
|
minor = iminor(inode);
|
2019-01-13 16:25:42 +08:00
|
|
|
pcm = snd_lookup_minor_data(minor, SNDRV_DEVICE_TYPE_PCM_PLAYBACK);
|
|
|
|
if (!pcm)
|
|
|
|
pcm = snd_lookup_minor_data(minor, SNDRV_DEVICE_TYPE_PCM_CAPTURE);
|
|
|
|
if (!pcm)
|
|
|
|
return false;
|
|
|
|
snd_card_unref(pcm->card);
|
|
|
|
return true;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* PCM link handling
|
|
|
|
*/
|
2005-11-17 20:59:38 +08:00
|
|
|
static int snd_pcm_link(struct snd_pcm_substream *substream, int fd)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
int res = 0;
|
2005-11-17 20:59:38 +08:00
|
|
|
struct snd_pcm_file *pcm_file;
|
|
|
|
struct snd_pcm_substream *substream1;
|
ALSA: pcm: More fine-grained PCM link locking
We have currently two global locks, a rwlock and a rwsem, that are
used for managing linking the PCM streams. Due to these global locks,
once when a linked stream is used, the lock granularity suffers a
lot.
This patch attempts to eliminate the former global lock for atomic
ops. The latter rwsem needs remaining because of the loosy way of the
loop calls in snd_pcm_action_nonatomic(), as well as for avoiding the
deadlock at linking. However, these are used far rarely, actually
only by two actions (prepare and reset), where both are no timing
critical ones. So this can be still seen as a good improvement.
The basic strategy to eliminate the rwlock is to assure group->lock at
adding or removing a stream to / from the group. Since we already
takes the group lock whenever taking the all substream locks under the
group, this shouldn't be a big problem. The reference to group
pointer in snd_pcm_substream object is protected by the stream lock
itself.
However, there are still pitfalls: a race window at re-locking and the
lifecycle of group object. The former is a small race window for
dereferencing the substream group object opened while snd_pcm_action()
performs re-locking to avoid ABBA deadlocks. This includes the unlink
of group during that window, too. And the latter is the kfree
performed after all streams are removed from the group while it's
still dereferenced.
For addressing these corner cases, two new tricks are introduced:
- After re-locking, the group assigned to the stream is checked again;
if the group is changed, we retry the whole procedure.
- Introduce a refcount to snd_pcm_group object, so that it's freed
only when it's empty and really no one refers to it.
(Some readers might wonder why not RCU for the latter. RCU in this
case would cost more than refcounting, unfortunately. We take the
group lock sooner or later, hence the performance improvement by RCU
would be negligible. Meanwhile, because we need to deal with
schedulable context depending on the pcm->nonatomic flag, it'll become
dynamic RCU/SRCU switch, and the grace period may become too long.)
Along with these changes, there are a significant amount of code
refactoring. The complex group re-lock & ref code is factored out to
snd_pcm_stream_group_ref() function, for example.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2019-01-13 16:50:33 +08:00
|
|
|
struct snd_pcm_group *group, *target_group;
|
|
|
|
bool nonatomic = substream->pcm->nonatomic;
|
2013-06-06 02:09:55 +08:00
|
|
|
struct fd f = fdget(fd);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2013-06-06 02:09:55 +08:00
|
|
|
if (!f.file)
|
2005-04-17 06:20:36 +08:00
|
|
|
return -EBADFD;
|
2013-06-06 02:09:55 +08:00
|
|
|
if (!is_pcm_file(f.file)) {
|
|
|
|
res = -EBADFD;
|
|
|
|
goto _badf;
|
|
|
|
}
|
|
|
|
pcm_file = f.file->private_data;
|
2005-04-17 06:20:36 +08:00
|
|
|
substream1 = pcm_file->substream;
|
2020-06-09 00:50:39 +08:00
|
|
|
|
|
|
|
if (substream == substream1) {
|
|
|
|
res = -EINVAL;
|
|
|
|
goto _badf;
|
|
|
|
}
|
|
|
|
|
2019-01-13 16:35:17 +08:00
|
|
|
group = kzalloc(sizeof(*group), GFP_KERNEL);
|
2012-03-13 22:55:43 +08:00
|
|
|
if (!group) {
|
|
|
|
res = -ENOMEM;
|
|
|
|
goto _nolock;
|
|
|
|
}
|
2019-01-13 16:35:17 +08:00
|
|
|
snd_pcm_group_init(group);
|
ALSA: pcm: More fine-grained PCM link locking
We have currently two global locks, a rwlock and a rwsem, that are
used for managing linking the PCM streams. Due to these global locks,
once when a linked stream is used, the lock granularity suffers a
lot.
This patch attempts to eliminate the former global lock for atomic
ops. The latter rwsem needs remaining because of the loosy way of the
loop calls in snd_pcm_action_nonatomic(), as well as for avoiding the
deadlock at linking. However, these are used far rarely, actually
only by two actions (prepare and reset), where both are no timing
critical ones. So this can be still seen as a good improvement.
The basic strategy to eliminate the rwlock is to assure group->lock at
adding or removing a stream to / from the group. Since we already
takes the group lock whenever taking the all substream locks under the
group, this shouldn't be a big problem. The reference to group
pointer in snd_pcm_substream object is protected by the stream lock
itself.
However, there are still pitfalls: a race window at re-locking and the
lifecycle of group object. The former is a small race window for
dereferencing the substream group object opened while snd_pcm_action()
performs re-locking to avoid ABBA deadlocks. This includes the unlink
of group during that window, too. And the latter is the kfree
performed after all streams are removed from the group while it's
still dereferenced.
For addressing these corner cases, two new tricks are introduced:
- After re-locking, the group assigned to the stream is checked again;
if the group is changed, we retry the whole procedure.
- Introduce a refcount to snd_pcm_group object, so that it's freed
only when it's empty and really no one refers to it.
(Some readers might wonder why not RCU for the latter. RCU in this
case would cost more than refcounting, unfortunately. We take the
group lock sooner or later, hence the performance improvement by RCU
would be negligible. Meanwhile, because we need to deal with
schedulable context depending on the pcm->nonatomic flag, it'll become
dynamic RCU/SRCU switch, and the grace period may become too long.)
Along with these changes, there are a significant amount of code
refactoring. The complex group re-lock & ref code is factored out to
snd_pcm_stream_group_ref() function, for example.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2019-01-13 16:50:33 +08:00
|
|
|
|
2019-01-22 21:29:51 +08:00
|
|
|
down_write(&snd_pcm_link_rwsem);
|
2005-04-17 06:20:36 +08:00
|
|
|
if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN ||
|
2014-08-29 21:32:29 +08:00
|
|
|
substream->runtime->status->state != substream1->runtime->status->state ||
|
|
|
|
substream->pcm->nonatomic != substream1->pcm->nonatomic) {
|
2005-04-17 06:20:36 +08:00
|
|
|
res = -EBADFD;
|
|
|
|
goto _end;
|
|
|
|
}
|
|
|
|
if (snd_pcm_stream_linked(substream1)) {
|
|
|
|
res = -EALREADY;
|
|
|
|
goto _end;
|
|
|
|
}
|
ALSA: pcm: More fine-grained PCM link locking
We have currently two global locks, a rwlock and a rwsem, that are
used for managing linking the PCM streams. Due to these global locks,
once when a linked stream is used, the lock granularity suffers a
lot.
This patch attempts to eliminate the former global lock for atomic
ops. The latter rwsem needs remaining because of the loosy way of the
loop calls in snd_pcm_action_nonatomic(), as well as for avoiding the
deadlock at linking. However, these are used far rarely, actually
only by two actions (prepare and reset), where both are no timing
critical ones. So this can be still seen as a good improvement.
The basic strategy to eliminate the rwlock is to assure group->lock at
adding or removing a stream to / from the group. Since we already
takes the group lock whenever taking the all substream locks under the
group, this shouldn't be a big problem. The reference to group
pointer in snd_pcm_substream object is protected by the stream lock
itself.
However, there are still pitfalls: a race window at re-locking and the
lifecycle of group object. The former is a small race window for
dereferencing the substream group object opened while snd_pcm_action()
performs re-locking to avoid ABBA deadlocks. This includes the unlink
of group during that window, too. And the latter is the kfree
performed after all streams are removed from the group while it's
still dereferenced.
For addressing these corner cases, two new tricks are introduced:
- After re-locking, the group assigned to the stream is checked again;
if the group is changed, we retry the whole procedure.
- Introduce a refcount to snd_pcm_group object, so that it's freed
only when it's empty and really no one refers to it.
(Some readers might wonder why not RCU for the latter. RCU in this
case would cost more than refcounting, unfortunately. We take the
group lock sooner or later, hence the performance improvement by RCU
would be negligible. Meanwhile, because we need to deal with
schedulable context depending on the pcm->nonatomic flag, it'll become
dynamic RCU/SRCU switch, and the grace period may become too long.)
Along with these changes, there are a significant amount of code
refactoring. The complex group re-lock & ref code is factored out to
snd_pcm_stream_group_ref() function, for example.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2019-01-13 16:50:33 +08:00
|
|
|
|
|
|
|
snd_pcm_stream_lock_irq(substream);
|
2005-04-17 06:20:36 +08:00
|
|
|
if (!snd_pcm_stream_linked(substream)) {
|
2019-01-13 16:40:21 +08:00
|
|
|
snd_pcm_group_assign(substream, group);
|
ALSA: pcm: More fine-grained PCM link locking
We have currently two global locks, a rwlock and a rwsem, that are
used for managing linking the PCM streams. Due to these global locks,
once when a linked stream is used, the lock granularity suffers a
lot.
This patch attempts to eliminate the former global lock for atomic
ops. The latter rwsem needs remaining because of the loosy way of the
loop calls in snd_pcm_action_nonatomic(), as well as for avoiding the
deadlock at linking. However, these are used far rarely, actually
only by two actions (prepare and reset), where both are no timing
critical ones. So this can be still seen as a good improvement.
The basic strategy to eliminate the rwlock is to assure group->lock at
adding or removing a stream to / from the group. Since we already
takes the group lock whenever taking the all substream locks under the
group, this shouldn't be a big problem. The reference to group
pointer in snd_pcm_substream object is protected by the stream lock
itself.
However, there are still pitfalls: a race window at re-locking and the
lifecycle of group object. The former is a small race window for
dereferencing the substream group object opened while snd_pcm_action()
performs re-locking to avoid ABBA deadlocks. This includes the unlink
of group during that window, too. And the latter is the kfree
performed after all streams are removed from the group while it's
still dereferenced.
For addressing these corner cases, two new tricks are introduced:
- After re-locking, the group assigned to the stream is checked again;
if the group is changed, we retry the whole procedure.
- Introduce a refcount to snd_pcm_group object, so that it's freed
only when it's empty and really no one refers to it.
(Some readers might wonder why not RCU for the latter. RCU in this
case would cost more than refcounting, unfortunately. We take the
group lock sooner or later, hence the performance improvement by RCU
would be negligible. Meanwhile, because we need to deal with
schedulable context depending on the pcm->nonatomic flag, it'll become
dynamic RCU/SRCU switch, and the grace period may become too long.)
Along with these changes, there are a significant amount of code
refactoring. The complex group re-lock & ref code is factored out to
snd_pcm_stream_group_ref() function, for example.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2019-01-13 16:50:33 +08:00
|
|
|
group = NULL; /* assigned, don't free this one below */
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
ALSA: pcm: More fine-grained PCM link locking
We have currently two global locks, a rwlock and a rwsem, that are
used for managing linking the PCM streams. Due to these global locks,
once when a linked stream is used, the lock granularity suffers a
lot.
This patch attempts to eliminate the former global lock for atomic
ops. The latter rwsem needs remaining because of the loosy way of the
loop calls in snd_pcm_action_nonatomic(), as well as for avoiding the
deadlock at linking. However, these are used far rarely, actually
only by two actions (prepare and reset), where both are no timing
critical ones. So this can be still seen as a good improvement.
The basic strategy to eliminate the rwlock is to assure group->lock at
adding or removing a stream to / from the group. Since we already
takes the group lock whenever taking the all substream locks under the
group, this shouldn't be a big problem. The reference to group
pointer in snd_pcm_substream object is protected by the stream lock
itself.
However, there are still pitfalls: a race window at re-locking and the
lifecycle of group object. The former is a small race window for
dereferencing the substream group object opened while snd_pcm_action()
performs re-locking to avoid ABBA deadlocks. This includes the unlink
of group during that window, too. And the latter is the kfree
performed after all streams are removed from the group while it's
still dereferenced.
For addressing these corner cases, two new tricks are introduced:
- After re-locking, the group assigned to the stream is checked again;
if the group is changed, we retry the whole procedure.
- Introduce a refcount to snd_pcm_group object, so that it's freed
only when it's empty and really no one refers to it.
(Some readers might wonder why not RCU for the latter. RCU in this
case would cost more than refcounting, unfortunately. We take the
group lock sooner or later, hence the performance improvement by RCU
would be negligible. Meanwhile, because we need to deal with
schedulable context depending on the pcm->nonatomic flag, it'll become
dynamic RCU/SRCU switch, and the grace period may become too long.)
Along with these changes, there are a significant amount of code
refactoring. The complex group re-lock & ref code is factored out to
snd_pcm_stream_group_ref() function, for example.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2019-01-13 16:50:33 +08:00
|
|
|
target_group = substream->group;
|
|
|
|
snd_pcm_stream_unlock_irq(substream);
|
|
|
|
|
|
|
|
snd_pcm_group_lock_irq(target_group, nonatomic);
|
2020-06-08 18:06:32 +08:00
|
|
|
snd_pcm_stream_lock_nested(substream1);
|
ALSA: pcm: More fine-grained PCM link locking
We have currently two global locks, a rwlock and a rwsem, that are
used for managing linking the PCM streams. Due to these global locks,
once when a linked stream is used, the lock granularity suffers a
lot.
This patch attempts to eliminate the former global lock for atomic
ops. The latter rwsem needs remaining because of the loosy way of the
loop calls in snd_pcm_action_nonatomic(), as well as for avoiding the
deadlock at linking. However, these are used far rarely, actually
only by two actions (prepare and reset), where both are no timing
critical ones. So this can be still seen as a good improvement.
The basic strategy to eliminate the rwlock is to assure group->lock at
adding or removing a stream to / from the group. Since we already
takes the group lock whenever taking the all substream locks under the
group, this shouldn't be a big problem. The reference to group
pointer in snd_pcm_substream object is protected by the stream lock
itself.
However, there are still pitfalls: a race window at re-locking and the
lifecycle of group object. The former is a small race window for
dereferencing the substream group object opened while snd_pcm_action()
performs re-locking to avoid ABBA deadlocks. This includes the unlink
of group during that window, too. And the latter is the kfree
performed after all streams are removed from the group while it's
still dereferenced.
For addressing these corner cases, two new tricks are introduced:
- After re-locking, the group assigned to the stream is checked again;
if the group is changed, we retry the whole procedure.
- Introduce a refcount to snd_pcm_group object, so that it's freed
only when it's empty and really no one refers to it.
(Some readers might wonder why not RCU for the latter. RCU in this
case would cost more than refcounting, unfortunately. We take the
group lock sooner or later, hence the performance improvement by RCU
would be negligible. Meanwhile, because we need to deal with
schedulable context depending on the pcm->nonatomic flag, it'll become
dynamic RCU/SRCU switch, and the grace period may become too long.)
Along with these changes, there are a significant amount of code
refactoring. The complex group re-lock & ref code is factored out to
snd_pcm_stream_group_ref() function, for example.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2019-01-13 16:50:33 +08:00
|
|
|
snd_pcm_group_assign(substream1, target_group);
|
2019-07-19 16:55:05 +08:00
|
|
|
refcount_inc(&target_group->refs);
|
ALSA: pcm: More fine-grained PCM link locking
We have currently two global locks, a rwlock and a rwsem, that are
used for managing linking the PCM streams. Due to these global locks,
once when a linked stream is used, the lock granularity suffers a
lot.
This patch attempts to eliminate the former global lock for atomic
ops. The latter rwsem needs remaining because of the loosy way of the
loop calls in snd_pcm_action_nonatomic(), as well as for avoiding the
deadlock at linking. However, these are used far rarely, actually
only by two actions (prepare and reset), where both are no timing
critical ones. So this can be still seen as a good improvement.
The basic strategy to eliminate the rwlock is to assure group->lock at
adding or removing a stream to / from the group. Since we already
takes the group lock whenever taking the all substream locks under the
group, this shouldn't be a big problem. The reference to group
pointer in snd_pcm_substream object is protected by the stream lock
itself.
However, there are still pitfalls: a race window at re-locking and the
lifecycle of group object. The former is a small race window for
dereferencing the substream group object opened while snd_pcm_action()
performs re-locking to avoid ABBA deadlocks. This includes the unlink
of group during that window, too. And the latter is the kfree
performed after all streams are removed from the group while it's
still dereferenced.
For addressing these corner cases, two new tricks are introduced:
- After re-locking, the group assigned to the stream is checked again;
if the group is changed, we retry the whole procedure.
- Introduce a refcount to snd_pcm_group object, so that it's freed
only when it's empty and really no one refers to it.
(Some readers might wonder why not RCU for the latter. RCU in this
case would cost more than refcounting, unfortunately. We take the
group lock sooner or later, hence the performance improvement by RCU
would be negligible. Meanwhile, because we need to deal with
schedulable context depending on the pcm->nonatomic flag, it'll become
dynamic RCU/SRCU switch, and the grace period may become too long.)
Along with these changes, there are a significant amount of code
refactoring. The complex group re-lock & ref code is factored out to
snd_pcm_stream_group_ref() function, for example.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2019-01-13 16:50:33 +08:00
|
|
|
snd_pcm_stream_unlock(substream1);
|
|
|
|
snd_pcm_group_unlock_irq(target_group, nonatomic);
|
2005-04-17 06:20:36 +08:00
|
|
|
_end:
|
|
|
|
up_write(&snd_pcm_link_rwsem);
|
2012-03-13 22:55:43 +08:00
|
|
|
_nolock:
|
2013-06-06 02:07:08 +08:00
|
|
|
kfree(group);
|
2013-06-06 02:09:55 +08:00
|
|
|
_badf:
|
|
|
|
fdput(f);
|
2005-04-17 06:20:36 +08:00
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
2005-11-17 20:59:38 +08:00
|
|
|
static void relink_to_local(struct snd_pcm_substream *substream)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2020-06-08 18:06:32 +08:00
|
|
|
snd_pcm_stream_lock_nested(substream);
|
2019-01-13 16:40:21 +08:00
|
|
|
snd_pcm_group_assign(substream, &substream->self_group);
|
ALSA: pcm: More fine-grained PCM link locking
We have currently two global locks, a rwlock and a rwsem, that are
used for managing linking the PCM streams. Due to these global locks,
once when a linked stream is used, the lock granularity suffers a
lot.
This patch attempts to eliminate the former global lock for atomic
ops. The latter rwsem needs remaining because of the loosy way of the
loop calls in snd_pcm_action_nonatomic(), as well as for avoiding the
deadlock at linking. However, these are used far rarely, actually
only by two actions (prepare and reset), where both are no timing
critical ones. So this can be still seen as a good improvement.
The basic strategy to eliminate the rwlock is to assure group->lock at
adding or removing a stream to / from the group. Since we already
takes the group lock whenever taking the all substream locks under the
group, this shouldn't be a big problem. The reference to group
pointer in snd_pcm_substream object is protected by the stream lock
itself.
However, there are still pitfalls: a race window at re-locking and the
lifecycle of group object. The former is a small race window for
dereferencing the substream group object opened while snd_pcm_action()
performs re-locking to avoid ABBA deadlocks. This includes the unlink
of group during that window, too. And the latter is the kfree
performed after all streams are removed from the group while it's
still dereferenced.
For addressing these corner cases, two new tricks are introduced:
- After re-locking, the group assigned to the stream is checked again;
if the group is changed, we retry the whole procedure.
- Introduce a refcount to snd_pcm_group object, so that it's freed
only when it's empty and really no one refers to it.
(Some readers might wonder why not RCU for the latter. RCU in this
case would cost more than refcounting, unfortunately. We take the
group lock sooner or later, hence the performance improvement by RCU
would be negligible. Meanwhile, because we need to deal with
schedulable context depending on the pcm->nonatomic flag, it'll become
dynamic RCU/SRCU switch, and the grace period may become too long.)
Along with these changes, there are a significant amount of code
refactoring. The complex group re-lock & ref code is factored out to
snd_pcm_stream_group_ref() function, for example.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2019-01-13 16:50:33 +08:00
|
|
|
snd_pcm_stream_unlock(substream);
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
2005-11-17 20:59:38 +08:00
|
|
|
static int snd_pcm_unlink(struct snd_pcm_substream *substream)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2019-01-13 16:40:21 +08:00
|
|
|
struct snd_pcm_group *group;
|
ALSA: pcm: More fine-grained PCM link locking
We have currently two global locks, a rwlock and a rwsem, that are
used for managing linking the PCM streams. Due to these global locks,
once when a linked stream is used, the lock granularity suffers a
lot.
This patch attempts to eliminate the former global lock for atomic
ops. The latter rwsem needs remaining because of the loosy way of the
loop calls in snd_pcm_action_nonatomic(), as well as for avoiding the
deadlock at linking. However, these are used far rarely, actually
only by two actions (prepare and reset), where both are no timing
critical ones. So this can be still seen as a good improvement.
The basic strategy to eliminate the rwlock is to assure group->lock at
adding or removing a stream to / from the group. Since we already
takes the group lock whenever taking the all substream locks under the
group, this shouldn't be a big problem. The reference to group
pointer in snd_pcm_substream object is protected by the stream lock
itself.
However, there are still pitfalls: a race window at re-locking and the
lifecycle of group object. The former is a small race window for
dereferencing the substream group object opened while snd_pcm_action()
performs re-locking to avoid ABBA deadlocks. This includes the unlink
of group during that window, too. And the latter is the kfree
performed after all streams are removed from the group while it's
still dereferenced.
For addressing these corner cases, two new tricks are introduced:
- After re-locking, the group assigned to the stream is checked again;
if the group is changed, we retry the whole procedure.
- Introduce a refcount to snd_pcm_group object, so that it's freed
only when it's empty and really no one refers to it.
(Some readers might wonder why not RCU for the latter. RCU in this
case would cost more than refcounting, unfortunately. We take the
group lock sooner or later, hence the performance improvement by RCU
would be negligible. Meanwhile, because we need to deal with
schedulable context depending on the pcm->nonatomic flag, it'll become
dynamic RCU/SRCU switch, and the grace period may become too long.)
Along with these changes, there are a significant amount of code
refactoring. The complex group re-lock & ref code is factored out to
snd_pcm_stream_group_ref() function, for example.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2019-01-13 16:50:33 +08:00
|
|
|
bool nonatomic = substream->pcm->nonatomic;
|
|
|
|
bool do_free = false;
|
2005-04-17 06:20:36 +08:00
|
|
|
int res = 0;
|
|
|
|
|
2019-01-22 21:29:51 +08:00
|
|
|
down_write(&snd_pcm_link_rwsem);
|
ALSA: pcm: More fine-grained PCM link locking
We have currently two global locks, a rwlock and a rwsem, that are
used for managing linking the PCM streams. Due to these global locks,
once when a linked stream is used, the lock granularity suffers a
lot.
This patch attempts to eliminate the former global lock for atomic
ops. The latter rwsem needs remaining because of the loosy way of the
loop calls in snd_pcm_action_nonatomic(), as well as for avoiding the
deadlock at linking. However, these are used far rarely, actually
only by two actions (prepare and reset), where both are no timing
critical ones. So this can be still seen as a good improvement.
The basic strategy to eliminate the rwlock is to assure group->lock at
adding or removing a stream to / from the group. Since we already
takes the group lock whenever taking the all substream locks under the
group, this shouldn't be a big problem. The reference to group
pointer in snd_pcm_substream object is protected by the stream lock
itself.
However, there are still pitfalls: a race window at re-locking and the
lifecycle of group object. The former is a small race window for
dereferencing the substream group object opened while snd_pcm_action()
performs re-locking to avoid ABBA deadlocks. This includes the unlink
of group during that window, too. And the latter is the kfree
performed after all streams are removed from the group while it's
still dereferenced.
For addressing these corner cases, two new tricks are introduced:
- After re-locking, the group assigned to the stream is checked again;
if the group is changed, we retry the whole procedure.
- Introduce a refcount to snd_pcm_group object, so that it's freed
only when it's empty and really no one refers to it.
(Some readers might wonder why not RCU for the latter. RCU in this
case would cost more than refcounting, unfortunately. We take the
group lock sooner or later, hence the performance improvement by RCU
would be negligible. Meanwhile, because we need to deal with
schedulable context depending on the pcm->nonatomic flag, it'll become
dynamic RCU/SRCU switch, and the grace period may become too long.)
Along with these changes, there are a significant amount of code
refactoring. The complex group re-lock & ref code is factored out to
snd_pcm_stream_group_ref() function, for example.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2019-01-13 16:50:33 +08:00
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
if (!snd_pcm_stream_linked(substream)) {
|
|
|
|
res = -EALREADY;
|
|
|
|
goto _end;
|
|
|
|
}
|
2019-01-13 16:40:21 +08:00
|
|
|
|
|
|
|
group = substream->group;
|
ALSA: pcm: More fine-grained PCM link locking
We have currently two global locks, a rwlock and a rwsem, that are
used for managing linking the PCM streams. Due to these global locks,
once when a linked stream is used, the lock granularity suffers a
lot.
This patch attempts to eliminate the former global lock for atomic
ops. The latter rwsem needs remaining because of the loosy way of the
loop calls in snd_pcm_action_nonatomic(), as well as for avoiding the
deadlock at linking. However, these are used far rarely, actually
only by two actions (prepare and reset), where both are no timing
critical ones. So this can be still seen as a good improvement.
The basic strategy to eliminate the rwlock is to assure group->lock at
adding or removing a stream to / from the group. Since we already
takes the group lock whenever taking the all substream locks under the
group, this shouldn't be a big problem. The reference to group
pointer in snd_pcm_substream object is protected by the stream lock
itself.
However, there are still pitfalls: a race window at re-locking and the
lifecycle of group object. The former is a small race window for
dereferencing the substream group object opened while snd_pcm_action()
performs re-locking to avoid ABBA deadlocks. This includes the unlink
of group during that window, too. And the latter is the kfree
performed after all streams are removed from the group while it's
still dereferenced.
For addressing these corner cases, two new tricks are introduced:
- After re-locking, the group assigned to the stream is checked again;
if the group is changed, we retry the whole procedure.
- Introduce a refcount to snd_pcm_group object, so that it's freed
only when it's empty and really no one refers to it.
(Some readers might wonder why not RCU for the latter. RCU in this
case would cost more than refcounting, unfortunately. We take the
group lock sooner or later, hence the performance improvement by RCU
would be negligible. Meanwhile, because we need to deal with
schedulable context depending on the pcm->nonatomic flag, it'll become
dynamic RCU/SRCU switch, and the grace period may become too long.)
Along with these changes, there are a significant amount of code
refactoring. The complex group re-lock & ref code is factored out to
snd_pcm_stream_group_ref() function, for example.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2019-01-13 16:50:33 +08:00
|
|
|
snd_pcm_group_lock_irq(group, nonatomic);
|
2019-01-13 16:40:21 +08:00
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
relink_to_local(substream);
|
2019-07-19 16:55:05 +08:00
|
|
|
refcount_dec(&group->refs);
|
2019-01-13 16:40:21 +08:00
|
|
|
|
|
|
|
/* detach the last stream, too */
|
|
|
|
if (list_is_singular(&group->substreams)) {
|
2019-01-13 17:19:32 +08:00
|
|
|
relink_to_local(list_first_entry(&group->substreams,
|
|
|
|
struct snd_pcm_substream,
|
|
|
|
link_list));
|
2019-07-19 16:55:05 +08:00
|
|
|
do_free = refcount_dec_and_test(&group->refs);
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
2019-01-13 16:40:21 +08:00
|
|
|
|
ALSA: pcm: More fine-grained PCM link locking
We have currently two global locks, a rwlock and a rwsem, that are
used for managing linking the PCM streams. Due to these global locks,
once when a linked stream is used, the lock granularity suffers a
lot.
This patch attempts to eliminate the former global lock for atomic
ops. The latter rwsem needs remaining because of the loosy way of the
loop calls in snd_pcm_action_nonatomic(), as well as for avoiding the
deadlock at linking. However, these are used far rarely, actually
only by two actions (prepare and reset), where both are no timing
critical ones. So this can be still seen as a good improvement.
The basic strategy to eliminate the rwlock is to assure group->lock at
adding or removing a stream to / from the group. Since we already
takes the group lock whenever taking the all substream locks under the
group, this shouldn't be a big problem. The reference to group
pointer in snd_pcm_substream object is protected by the stream lock
itself.
However, there are still pitfalls: a race window at re-locking and the
lifecycle of group object. The former is a small race window for
dereferencing the substream group object opened while snd_pcm_action()
performs re-locking to avoid ABBA deadlocks. This includes the unlink
of group during that window, too. And the latter is the kfree
performed after all streams are removed from the group while it's
still dereferenced.
For addressing these corner cases, two new tricks are introduced:
- After re-locking, the group assigned to the stream is checked again;
if the group is changed, we retry the whole procedure.
- Introduce a refcount to snd_pcm_group object, so that it's freed
only when it's empty and really no one refers to it.
(Some readers might wonder why not RCU for the latter. RCU in this
case would cost more than refcounting, unfortunately. We take the
group lock sooner or later, hence the performance improvement by RCU
would be negligible. Meanwhile, because we need to deal with
schedulable context depending on the pcm->nonatomic flag, it'll become
dynamic RCU/SRCU switch, and the grace period may become too long.)
Along with these changes, there are a significant amount of code
refactoring. The complex group re-lock & ref code is factored out to
snd_pcm_stream_group_ref() function, for example.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2019-01-13 16:50:33 +08:00
|
|
|
snd_pcm_group_unlock_irq(group, nonatomic);
|
|
|
|
if (do_free)
|
|
|
|
kfree(group);
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
_end:
|
|
|
|
up_write(&snd_pcm_link_rwsem);
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* hw configurator
|
|
|
|
*/
|
2005-11-17 20:59:38 +08:00
|
|
|
static int snd_pcm_hw_rule_mul(struct snd_pcm_hw_params *params,
|
|
|
|
struct snd_pcm_hw_rule *rule)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2005-11-17 20:59:38 +08:00
|
|
|
struct snd_interval t;
|
2005-04-17 06:20:36 +08:00
|
|
|
snd_interval_mul(hw_param_interval_c(params, rule->deps[0]),
|
|
|
|
hw_param_interval_c(params, rule->deps[1]), &t);
|
|
|
|
return snd_interval_refine(hw_param_interval(params, rule->var), &t);
|
|
|
|
}
|
|
|
|
|
2005-11-17 20:59:38 +08:00
|
|
|
static int snd_pcm_hw_rule_div(struct snd_pcm_hw_params *params,
|
|
|
|
struct snd_pcm_hw_rule *rule)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2005-11-17 20:59:38 +08:00
|
|
|
struct snd_interval t;
|
2005-04-17 06:20:36 +08:00
|
|
|
snd_interval_div(hw_param_interval_c(params, rule->deps[0]),
|
|
|
|
hw_param_interval_c(params, rule->deps[1]), &t);
|
|
|
|
return snd_interval_refine(hw_param_interval(params, rule->var), &t);
|
|
|
|
}
|
|
|
|
|
2005-11-17 20:59:38 +08:00
|
|
|
static int snd_pcm_hw_rule_muldivk(struct snd_pcm_hw_params *params,
|
|
|
|
struct snd_pcm_hw_rule *rule)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2005-11-17 20:59:38 +08:00
|
|
|
struct snd_interval t;
|
2005-04-17 06:20:36 +08:00
|
|
|
snd_interval_muldivk(hw_param_interval_c(params, rule->deps[0]),
|
|
|
|
hw_param_interval_c(params, rule->deps[1]),
|
|
|
|
(unsigned long) rule->private, &t);
|
|
|
|
return snd_interval_refine(hw_param_interval(params, rule->var), &t);
|
|
|
|
}
|
|
|
|
|
2005-11-17 20:59:38 +08:00
|
|
|
static int snd_pcm_hw_rule_mulkdiv(struct snd_pcm_hw_params *params,
|
|
|
|
struct snd_pcm_hw_rule *rule)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2005-11-17 20:59:38 +08:00
|
|
|
struct snd_interval t;
|
2005-04-17 06:20:36 +08:00
|
|
|
snd_interval_mulkdiv(hw_param_interval_c(params, rule->deps[0]),
|
|
|
|
(unsigned long) rule->private,
|
|
|
|
hw_param_interval_c(params, rule->deps[1]), &t);
|
|
|
|
return snd_interval_refine(hw_param_interval(params, rule->var), &t);
|
|
|
|
}
|
|
|
|
|
2005-11-17 20:59:38 +08:00
|
|
|
static int snd_pcm_hw_rule_format(struct snd_pcm_hw_params *params,
|
|
|
|
struct snd_pcm_hw_rule *rule)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2020-02-07 00:39:42 +08:00
|
|
|
snd_pcm_format_t k;
|
2017-05-17 07:48:20 +08:00
|
|
|
const struct snd_interval *i =
|
|
|
|
hw_param_interval_c(params, rule->deps[0]);
|
2005-11-17 20:59:38 +08:00
|
|
|
struct snd_mask m;
|
|
|
|
struct snd_mask *mask = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);
|
2005-04-17 06:20:36 +08:00
|
|
|
snd_mask_any(&m);
|
2020-02-07 00:39:42 +08:00
|
|
|
pcm_for_each_format(k) {
|
2005-04-17 06:20:36 +08:00
|
|
|
int bits;
|
2020-02-07 00:39:42 +08:00
|
|
|
if (!snd_mask_test_format(mask, k))
|
2005-04-17 06:20:36 +08:00
|
|
|
continue;
|
|
|
|
bits = snd_pcm_format_physical_width(k);
|
|
|
|
if (bits <= 0)
|
|
|
|
continue; /* ignore invalid formats */
|
|
|
|
if ((unsigned)bits < i->min || (unsigned)bits > i->max)
|
2020-02-07 00:39:42 +08:00
|
|
|
snd_mask_reset(&m, (__force unsigned)k);
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
return snd_mask_refine(mask, &m);
|
|
|
|
}
|
|
|
|
|
2005-11-17 20:59:38 +08:00
|
|
|
static int snd_pcm_hw_rule_sample_bits(struct snd_pcm_hw_params *params,
|
|
|
|
struct snd_pcm_hw_rule *rule)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2005-11-17 20:59:38 +08:00
|
|
|
struct snd_interval t;
|
2020-02-07 00:39:42 +08:00
|
|
|
snd_pcm_format_t k;
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
t.min = UINT_MAX;
|
|
|
|
t.max = 0;
|
|
|
|
t.openmin = 0;
|
|
|
|
t.openmax = 0;
|
2020-02-07 00:39:42 +08:00
|
|
|
pcm_for_each_format(k) {
|
2005-04-17 06:20:36 +08:00
|
|
|
int bits;
|
2020-02-07 00:39:42 +08:00
|
|
|
if (!snd_mask_test_format(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), k))
|
2005-04-17 06:20:36 +08:00
|
|
|
continue;
|
|
|
|
bits = snd_pcm_format_physical_width(k);
|
|
|
|
if (bits <= 0)
|
|
|
|
continue; /* ignore invalid formats */
|
|
|
|
if (t.min > (unsigned)bits)
|
|
|
|
t.min = bits;
|
|
|
|
if (t.max < (unsigned)bits)
|
|
|
|
t.max = bits;
|
|
|
|
}
|
|
|
|
t.integer = 1;
|
|
|
|
return snd_interval_refine(hw_param_interval(params, rule->var), &t);
|
|
|
|
}
|
|
|
|
|
|
|
|
#if SNDRV_PCM_RATE_5512 != 1 << 0 || SNDRV_PCM_RATE_192000 != 1 << 12
|
|
|
|
#error "Change this table"
|
|
|
|
#endif
|
|
|
|
|
2017-05-17 07:48:17 +08:00
|
|
|
static const unsigned int rates[] = {
|
|
|
|
5512, 8000, 11025, 16000, 22050, 32000, 44100,
|
2019-08-22 17:56:50 +08:00
|
|
|
48000, 64000, 88200, 96000, 176400, 192000, 352800, 384000
|
2017-05-17 07:48:17 +08:00
|
|
|
};
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2007-08-13 23:38:54 +08:00
|
|
|
const struct snd_pcm_hw_constraint_list snd_pcm_known_rates = {
|
|
|
|
.count = ARRAY_SIZE(rates),
|
|
|
|
.list = rates,
|
|
|
|
};
|
|
|
|
|
2005-11-17 20:59:38 +08:00
|
|
|
static int snd_pcm_hw_rule_rate(struct snd_pcm_hw_params *params,
|
|
|
|
struct snd_pcm_hw_rule *rule)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2005-11-17 20:59:38 +08:00
|
|
|
struct snd_pcm_hardware *hw = rule->private;
|
2005-04-17 06:20:36 +08:00
|
|
|
return snd_interval_list(hw_param_interval(params, rule->var),
|
2007-08-13 23:38:54 +08:00
|
|
|
snd_pcm_known_rates.count,
|
|
|
|
snd_pcm_known_rates.list, hw->rates);
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
2005-11-17 20:59:38 +08:00
|
|
|
static int snd_pcm_hw_rule_buffer_bytes_max(struct snd_pcm_hw_params *params,
|
|
|
|
struct snd_pcm_hw_rule *rule)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2005-11-17 20:59:38 +08:00
|
|
|
struct snd_interval t;
|
|
|
|
struct snd_pcm_substream *substream = rule->private;
|
2005-04-17 06:20:36 +08:00
|
|
|
t.min = 0;
|
|
|
|
t.max = substream->buffer_bytes_max;
|
|
|
|
t.openmin = 0;
|
|
|
|
t.openmax = 0;
|
|
|
|
t.integer = 1;
|
|
|
|
return snd_interval_refine(hw_param_interval(params, rule->var), &t);
|
|
|
|
}
|
|
|
|
|
2020-01-17 00:28:25 +08:00
|
|
|
static int snd_pcm_hw_constraints_init(struct snd_pcm_substream *substream)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2005-11-17 20:59:38 +08:00
|
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
|
|
|
struct snd_pcm_hw_constraints *constrs = &runtime->hw_constraints;
|
2005-04-17 06:20:36 +08:00
|
|
|
int k, err;
|
|
|
|
|
|
|
|
for (k = SNDRV_PCM_HW_PARAM_FIRST_MASK; k <= SNDRV_PCM_HW_PARAM_LAST_MASK; k++) {
|
|
|
|
snd_mask_any(constrs_mask(constrs, k));
|
|
|
|
}
|
|
|
|
|
|
|
|
for (k = SNDRV_PCM_HW_PARAM_FIRST_INTERVAL; k <= SNDRV_PCM_HW_PARAM_LAST_INTERVAL; k++) {
|
|
|
|
snd_interval_any(constrs_interval(constrs, k));
|
|
|
|
}
|
|
|
|
|
|
|
|
snd_interval_setinteger(constrs_interval(constrs, SNDRV_PCM_HW_PARAM_CHANNELS));
|
|
|
|
snd_interval_setinteger(constrs_interval(constrs, SNDRV_PCM_HW_PARAM_BUFFER_SIZE));
|
|
|
|
snd_interval_setinteger(constrs_interval(constrs, SNDRV_PCM_HW_PARAM_BUFFER_BYTES));
|
|
|
|
snd_interval_setinteger(constrs_interval(constrs, SNDRV_PCM_HW_PARAM_SAMPLE_BITS));
|
|
|
|
snd_interval_setinteger(constrs_interval(constrs, SNDRV_PCM_HW_PARAM_FRAME_BITS));
|
|
|
|
|
|
|
|
err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_FORMAT,
|
|
|
|
snd_pcm_hw_rule_format, NULL,
|
|
|
|
SNDRV_PCM_HW_PARAM_SAMPLE_BITS, -1);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_SAMPLE_BITS,
|
|
|
|
snd_pcm_hw_rule_sample_bits, NULL,
|
|
|
|
SNDRV_PCM_HW_PARAM_FORMAT,
|
|
|
|
SNDRV_PCM_HW_PARAM_SAMPLE_BITS, -1);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_SAMPLE_BITS,
|
|
|
|
snd_pcm_hw_rule_div, NULL,
|
|
|
|
SNDRV_PCM_HW_PARAM_FRAME_BITS, SNDRV_PCM_HW_PARAM_CHANNELS, -1);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_FRAME_BITS,
|
|
|
|
snd_pcm_hw_rule_mul, NULL,
|
|
|
|
SNDRV_PCM_HW_PARAM_SAMPLE_BITS, SNDRV_PCM_HW_PARAM_CHANNELS, -1);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_FRAME_BITS,
|
|
|
|
snd_pcm_hw_rule_mulkdiv, (void*) 8,
|
|
|
|
SNDRV_PCM_HW_PARAM_PERIOD_BYTES, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, -1);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_FRAME_BITS,
|
|
|
|
snd_pcm_hw_rule_mulkdiv, (void*) 8,
|
|
|
|
SNDRV_PCM_HW_PARAM_BUFFER_BYTES, SNDRV_PCM_HW_PARAM_BUFFER_SIZE, -1);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
|
|
|
|
snd_pcm_hw_rule_div, NULL,
|
|
|
|
SNDRV_PCM_HW_PARAM_FRAME_BITS, SNDRV_PCM_HW_PARAM_SAMPLE_BITS, -1);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
|
|
|
|
snd_pcm_hw_rule_mulkdiv, (void*) 1000000,
|
|
|
|
SNDRV_PCM_HW_PARAM_PERIOD_SIZE, SNDRV_PCM_HW_PARAM_PERIOD_TIME, -1);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
|
|
|
|
snd_pcm_hw_rule_mulkdiv, (void*) 1000000,
|
|
|
|
SNDRV_PCM_HW_PARAM_BUFFER_SIZE, SNDRV_PCM_HW_PARAM_BUFFER_TIME, -1);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_PERIODS,
|
|
|
|
snd_pcm_hw_rule_div, NULL,
|
|
|
|
SNDRV_PCM_HW_PARAM_BUFFER_SIZE, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, -1);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_SIZE,
|
|
|
|
snd_pcm_hw_rule_div, NULL,
|
|
|
|
SNDRV_PCM_HW_PARAM_BUFFER_SIZE, SNDRV_PCM_HW_PARAM_PERIODS, -1);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_SIZE,
|
|
|
|
snd_pcm_hw_rule_mulkdiv, (void*) 8,
|
|
|
|
SNDRV_PCM_HW_PARAM_PERIOD_BYTES, SNDRV_PCM_HW_PARAM_FRAME_BITS, -1);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_SIZE,
|
|
|
|
snd_pcm_hw_rule_muldivk, (void*) 1000000,
|
|
|
|
SNDRV_PCM_HW_PARAM_PERIOD_TIME, SNDRV_PCM_HW_PARAM_RATE, -1);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_SIZE,
|
|
|
|
snd_pcm_hw_rule_mul, NULL,
|
|
|
|
SNDRV_PCM_HW_PARAM_PERIOD_SIZE, SNDRV_PCM_HW_PARAM_PERIODS, -1);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_SIZE,
|
|
|
|
snd_pcm_hw_rule_mulkdiv, (void*) 8,
|
|
|
|
SNDRV_PCM_HW_PARAM_BUFFER_BYTES, SNDRV_PCM_HW_PARAM_FRAME_BITS, -1);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_SIZE,
|
|
|
|
snd_pcm_hw_rule_muldivk, (void*) 1000000,
|
|
|
|
SNDRV_PCM_HW_PARAM_BUFFER_TIME, SNDRV_PCM_HW_PARAM_RATE, -1);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES,
|
|
|
|
snd_pcm_hw_rule_muldivk, (void*) 8,
|
|
|
|
SNDRV_PCM_HW_PARAM_PERIOD_SIZE, SNDRV_PCM_HW_PARAM_FRAME_BITS, -1);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_BYTES,
|
|
|
|
snd_pcm_hw_rule_muldivk, (void*) 8,
|
|
|
|
SNDRV_PCM_HW_PARAM_BUFFER_SIZE, SNDRV_PCM_HW_PARAM_FRAME_BITS, -1);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_TIME,
|
|
|
|
snd_pcm_hw_rule_mulkdiv, (void*) 1000000,
|
|
|
|
SNDRV_PCM_HW_PARAM_PERIOD_SIZE, SNDRV_PCM_HW_PARAM_RATE, -1);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_TIME,
|
|
|
|
snd_pcm_hw_rule_mulkdiv, (void*) 1000000,
|
|
|
|
SNDRV_PCM_HW_PARAM_BUFFER_SIZE, SNDRV_PCM_HW_PARAM_RATE, -1);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2020-01-17 00:28:25 +08:00
|
|
|
static int snd_pcm_hw_constraints_complete(struct snd_pcm_substream *substream)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2005-11-17 20:59:38 +08:00
|
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
|
|
|
struct snd_pcm_hardware *hw = &runtime->hw;
|
2005-04-17 06:20:36 +08:00
|
|
|
int err;
|
|
|
|
unsigned int mask = 0;
|
|
|
|
|
|
|
|
if (hw->info & SNDRV_PCM_INFO_INTERLEAVED)
|
2020-02-07 00:39:43 +08:00
|
|
|
mask |= PARAM_MASK_BIT(SNDRV_PCM_ACCESS_RW_INTERLEAVED);
|
2005-04-17 06:20:36 +08:00
|
|
|
if (hw->info & SNDRV_PCM_INFO_NONINTERLEAVED)
|
2020-02-07 00:39:43 +08:00
|
|
|
mask |= PARAM_MASK_BIT(SNDRV_PCM_ACCESS_RW_NONINTERLEAVED);
|
2014-10-22 18:04:46 +08:00
|
|
|
if (hw_support_mmap(substream)) {
|
2005-04-17 06:20:36 +08:00
|
|
|
if (hw->info & SNDRV_PCM_INFO_INTERLEAVED)
|
2020-02-07 00:39:43 +08:00
|
|
|
mask |= PARAM_MASK_BIT(SNDRV_PCM_ACCESS_MMAP_INTERLEAVED);
|
2005-04-17 06:20:36 +08:00
|
|
|
if (hw->info & SNDRV_PCM_INFO_NONINTERLEAVED)
|
2020-02-07 00:39:43 +08:00
|
|
|
mask |= PARAM_MASK_BIT(SNDRV_PCM_ACCESS_MMAP_NONINTERLEAVED);
|
2005-04-17 06:20:36 +08:00
|
|
|
if (hw->info & SNDRV_PCM_INFO_COMPLEX)
|
2020-02-07 00:39:43 +08:00
|
|
|
mask |= PARAM_MASK_BIT(SNDRV_PCM_ACCESS_MMAP_COMPLEX);
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
err = snd_pcm_hw_constraint_mask(runtime, SNDRV_PCM_HW_PARAM_ACCESS, mask);
|
2008-08-08 23:09:09 +08:00
|
|
|
if (err < 0)
|
|
|
|
return err;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
err = snd_pcm_hw_constraint_mask64(runtime, SNDRV_PCM_HW_PARAM_FORMAT, hw->formats);
|
2008-08-08 23:09:09 +08:00
|
|
|
if (err < 0)
|
|
|
|
return err;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2020-02-07 00:39:43 +08:00
|
|
|
err = snd_pcm_hw_constraint_mask(runtime, SNDRV_PCM_HW_PARAM_SUBFORMAT,
|
|
|
|
PARAM_MASK_BIT(SNDRV_PCM_SUBFORMAT_STD));
|
2008-08-08 23:09:09 +08:00
|
|
|
if (err < 0)
|
|
|
|
return err;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
err = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_CHANNELS,
|
|
|
|
hw->channels_min, hw->channels_max);
|
2008-08-08 23:09:09 +08:00
|
|
|
if (err < 0)
|
|
|
|
return err;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
err = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_RATE,
|
|
|
|
hw->rate_min, hw->rate_max);
|
2009-12-24 08:17:46 +08:00
|
|
|
if (err < 0)
|
|
|
|
return err;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
err = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIOD_BYTES,
|
|
|
|
hw->period_bytes_min, hw->period_bytes_max);
|
2009-12-24 08:17:46 +08:00
|
|
|
if (err < 0)
|
|
|
|
return err;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
err = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIODS,
|
|
|
|
hw->periods_min, hw->periods_max);
|
2008-08-08 23:09:09 +08:00
|
|
|
if (err < 0)
|
|
|
|
return err;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
err = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_BYTES,
|
|
|
|
hw->period_bytes_min, hw->buffer_bytes_max);
|
2008-08-08 23:09:09 +08:00
|
|
|
if (err < 0)
|
|
|
|
return err;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_BYTES,
|
|
|
|
snd_pcm_hw_rule_buffer_bytes_max, substream,
|
|
|
|
SNDRV_PCM_HW_PARAM_BUFFER_BYTES, -1);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
|
|
|
|
/* FIXME: remove */
|
|
|
|
if (runtime->dma_bytes) {
|
|
|
|
err = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 0, runtime->dma_bytes);
|
2008-08-08 23:09:09 +08:00
|
|
|
if (err < 0)
|
2012-11-21 17:06:55 +08:00
|
|
|
return err;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!(hw->rates & (SNDRV_PCM_RATE_KNOT | SNDRV_PCM_RATE_CONTINUOUS))) {
|
|
|
|
err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
|
|
|
|
snd_pcm_hw_rule_rate, hw,
|
|
|
|
SNDRV_PCM_HW_PARAM_RATE, -1);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* FIXME: this belong to lowlevel */
|
|
|
|
snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIOD_SIZE);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2006-03-27 22:40:49 +08:00
|
|
|
static void pcm_release_private(struct snd_pcm_substream *substream)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2018-11-29 15:02:49 +08:00
|
|
|
if (snd_pcm_stream_linked(substream))
|
|
|
|
snd_pcm_unlink(substream);
|
2006-03-27 22:40:49 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void snd_pcm_release_substream(struct snd_pcm_substream *substream)
|
|
|
|
{
|
2006-04-28 21:13:41 +08:00
|
|
|
substream->ref_count--;
|
|
|
|
if (substream->ref_count > 0)
|
|
|
|
return;
|
|
|
|
|
2006-03-27 22:40:49 +08:00
|
|
|
snd_pcm_drop(substream);
|
|
|
|
if (substream->hw_opened) {
|
2020-02-13 14:03:49 +08:00
|
|
|
if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN)
|
|
|
|
do_hw_free(substream);
|
2005-04-17 06:20:36 +08:00
|
|
|
substream->ops->close(substream);
|
2006-03-27 22:40:49 +08:00
|
|
|
substream->hw_opened = 0;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
2020-02-12 07:34:15 +08:00
|
|
|
if (cpu_latency_qos_request_active(&substream->latency_pm_qos_req))
|
|
|
|
cpu_latency_qos_remove_request(&substream->latency_pm_qos_req);
|
2006-04-07 01:47:42 +08:00
|
|
|
if (substream->pcm_release) {
|
|
|
|
substream->pcm_release(substream);
|
|
|
|
substream->pcm_release = NULL;
|
|
|
|
}
|
2006-03-27 22:40:49 +08:00
|
|
|
snd_pcm_detach_substream(substream);
|
|
|
|
}
|
2006-04-28 21:13:40 +08:00
|
|
|
EXPORT_SYMBOL(snd_pcm_release_substream);
|
|
|
|
|
2006-03-27 22:40:49 +08:00
|
|
|
int snd_pcm_open_substream(struct snd_pcm *pcm, int stream,
|
|
|
|
struct file *file,
|
|
|
|
struct snd_pcm_substream **rsubstream)
|
|
|
|
{
|
|
|
|
struct snd_pcm_substream *substream;
|
|
|
|
int err;
|
|
|
|
|
|
|
|
err = snd_pcm_attach_substream(pcm, stream, file, &substream);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
2006-04-28 21:13:41 +08:00
|
|
|
if (substream->ref_count > 1) {
|
|
|
|
*rsubstream = substream;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2006-03-27 22:40:49 +08:00
|
|
|
err = snd_pcm_hw_constraints_init(substream);
|
|
|
|
if (err < 0) {
|
2014-02-05 01:19:48 +08:00
|
|
|
pcm_dbg(pcm, "snd_pcm_hw_constraints_init failed\n");
|
2006-03-27 22:40:49 +08:00
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((err = substream->ops->open(substream)) < 0)
|
|
|
|
goto error;
|
|
|
|
|
|
|
|
substream->hw_opened = 1;
|
|
|
|
|
|
|
|
err = snd_pcm_hw_constraints_complete(substream);
|
|
|
|
if (err < 0) {
|
2014-02-05 01:19:48 +08:00
|
|
|
pcm_dbg(pcm, "snd_pcm_hw_constraints_complete failed\n");
|
2006-03-27 22:40:49 +08:00
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
|
|
|
*rsubstream = substream;
|
2005-04-17 06:20:36 +08:00
|
|
|
return 0;
|
2006-03-27 22:40:49 +08:00
|
|
|
|
|
|
|
error:
|
|
|
|
snd_pcm_release_substream(substream);
|
|
|
|
return err;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
2006-04-28 21:13:40 +08:00
|
|
|
EXPORT_SYMBOL(snd_pcm_open_substream);
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
static int snd_pcm_open_file(struct file *file,
|
2005-11-17 20:59:38 +08:00
|
|
|
struct snd_pcm *pcm,
|
2011-10-10 10:31:48 +08:00
|
|
|
int stream)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2005-11-17 20:59:38 +08:00
|
|
|
struct snd_pcm_file *pcm_file;
|
|
|
|
struct snd_pcm_substream *substream;
|
2006-03-27 22:40:49 +08:00
|
|
|
int err;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2006-03-27 22:40:49 +08:00
|
|
|
err = snd_pcm_open_substream(pcm, stream, file, &substream);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
|
2006-07-31 22:51:51 +08:00
|
|
|
pcm_file = kzalloc(sizeof(*pcm_file), GFP_KERNEL);
|
|
|
|
if (pcm_file == NULL) {
|
|
|
|
snd_pcm_release_substream(substream);
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
pcm_file->substream = substream;
|
2019-01-23 19:47:34 +08:00
|
|
|
if (substream->ref_count == 1)
|
2006-04-28 21:13:41 +08:00
|
|
|
substream->pcm_release = pcm_release_private;
|
2005-04-17 06:20:36 +08:00
|
|
|
file->private_data = pcm_file;
|
2011-10-10 10:31:48 +08:00
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2005-11-20 21:06:59 +08:00
|
|
|
static int snd_pcm_playback_open(struct inode *inode, struct file *file)
|
|
|
|
{
|
|
|
|
struct snd_pcm *pcm;
|
2010-04-13 17:49:04 +08:00
|
|
|
int err = nonseekable_open(inode, file);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
2005-11-20 21:06:59 +08:00
|
|
|
pcm = snd_lookup_minor_data(iminor(inode),
|
|
|
|
SNDRV_DEVICE_TYPE_PCM_PLAYBACK);
|
2012-10-16 19:05:59 +08:00
|
|
|
err = snd_pcm_open(file, pcm, SNDRV_PCM_STREAM_PLAYBACK);
|
2012-11-08 21:36:18 +08:00
|
|
|
if (pcm)
|
|
|
|
snd_card_unref(pcm->card);
|
2012-10-16 19:05:59 +08:00
|
|
|
return err;
|
2005-11-20 21:06:59 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static int snd_pcm_capture_open(struct inode *inode, struct file *file)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2005-11-17 20:59:38 +08:00
|
|
|
struct snd_pcm *pcm;
|
2010-04-13 17:49:04 +08:00
|
|
|
int err = nonseekable_open(inode, file);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
2005-11-20 21:06:59 +08:00
|
|
|
pcm = snd_lookup_minor_data(iminor(inode),
|
|
|
|
SNDRV_DEVICE_TYPE_PCM_CAPTURE);
|
2012-10-16 19:05:59 +08:00
|
|
|
err = snd_pcm_open(file, pcm, SNDRV_PCM_STREAM_CAPTURE);
|
2012-11-08 21:36:18 +08:00
|
|
|
if (pcm)
|
|
|
|
snd_card_unref(pcm->card);
|
2012-10-16 19:05:59 +08:00
|
|
|
return err;
|
2005-11-20 21:06:59 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static int snd_pcm_open(struct file *file, struct snd_pcm *pcm, int stream)
|
|
|
|
{
|
|
|
|
int err;
|
2017-06-20 18:06:13 +08:00
|
|
|
wait_queue_entry_t wait;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
if (pcm == NULL) {
|
|
|
|
err = -ENODEV;
|
|
|
|
goto __error1;
|
|
|
|
}
|
|
|
|
err = snd_card_file_add(pcm->card, file);
|
|
|
|
if (err < 0)
|
|
|
|
goto __error1;
|
|
|
|
if (!try_module_get(pcm->card->module)) {
|
|
|
|
err = -EFAULT;
|
|
|
|
goto __error2;
|
|
|
|
}
|
|
|
|
init_waitqueue_entry(&wait, current);
|
|
|
|
add_wait_queue(&pcm->open_wait, &wait);
|
2006-01-16 23:29:08 +08:00
|
|
|
mutex_lock(&pcm->open_mutex);
|
2005-04-17 06:20:36 +08:00
|
|
|
while (1) {
|
2011-10-10 10:31:48 +08:00
|
|
|
err = snd_pcm_open_file(file, pcm, stream);
|
2005-04-17 06:20:36 +08:00
|
|
|
if (err >= 0)
|
|
|
|
break;
|
|
|
|
if (err == -EAGAIN) {
|
|
|
|
if (file->f_flags & O_NONBLOCK) {
|
|
|
|
err = -EBUSY;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
} else
|
|
|
|
break;
|
|
|
|
set_current_state(TASK_INTERRUPTIBLE);
|
2006-01-16 23:29:08 +08:00
|
|
|
mutex_unlock(&pcm->open_mutex);
|
2005-04-17 06:20:36 +08:00
|
|
|
schedule();
|
2006-01-16 23:29:08 +08:00
|
|
|
mutex_lock(&pcm->open_mutex);
|
2012-10-16 22:43:39 +08:00
|
|
|
if (pcm->card->shutdown) {
|
|
|
|
err = -ENODEV;
|
|
|
|
break;
|
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
if (signal_pending(current)) {
|
|
|
|
err = -ERESTARTSYS;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
remove_wait_queue(&pcm->open_wait, &wait);
|
2006-01-16 23:29:08 +08:00
|
|
|
mutex_unlock(&pcm->open_mutex);
|
2005-04-17 06:20:36 +08:00
|
|
|
if (err < 0)
|
|
|
|
goto __error;
|
|
|
|
return err;
|
|
|
|
|
|
|
|
__error:
|
|
|
|
module_put(pcm->card->module);
|
|
|
|
__error2:
|
|
|
|
snd_card_file_remove(pcm->card, file);
|
|
|
|
__error1:
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int snd_pcm_release(struct inode *inode, struct file *file)
|
|
|
|
{
|
2005-11-17 20:59:38 +08:00
|
|
|
struct snd_pcm *pcm;
|
|
|
|
struct snd_pcm_substream *substream;
|
|
|
|
struct snd_pcm_file *pcm_file;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
pcm_file = file->private_data;
|
|
|
|
substream = pcm_file->substream;
|
2008-08-08 23:09:09 +08:00
|
|
|
if (snd_BUG_ON(!substream))
|
|
|
|
return -ENXIO;
|
2005-04-17 06:20:36 +08:00
|
|
|
pcm = substream->pcm;
|
2006-01-16 23:29:08 +08:00
|
|
|
mutex_lock(&pcm->open_mutex);
|
2006-03-27 22:40:49 +08:00
|
|
|
snd_pcm_release_substream(substream);
|
2006-07-31 22:51:51 +08:00
|
|
|
kfree(pcm_file);
|
2006-01-16 23:29:08 +08:00
|
|
|
mutex_unlock(&pcm->open_mutex);
|
2005-04-17 06:20:36 +08:00
|
|
|
wake_up(&pcm->open_wait);
|
|
|
|
module_put(pcm->card->module);
|
|
|
|
snd_card_file_remove(pcm->card, file);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2017-05-20 02:04:23 +08:00
|
|
|
/* check and update PCM state; return 0 or a negative error
|
|
|
|
* call this inside PCM lock
|
|
|
|
*/
|
|
|
|
static int do_pcm_hwsync(struct snd_pcm_substream *substream)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2017-05-20 02:04:23 +08:00
|
|
|
switch (substream->runtime->status->state) {
|
2005-04-17 06:20:36 +08:00
|
|
|
case SNDRV_PCM_STATE_DRAINING:
|
2017-05-20 02:04:23 +08:00
|
|
|
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
|
|
|
|
return -EBADFD;
|
2020-07-09 04:32:36 +08:00
|
|
|
fallthrough;
|
2017-05-20 02:04:23 +08:00
|
|
|
case SNDRV_PCM_STATE_RUNNING:
|
|
|
|
return snd_pcm_update_hw_ptr(substream);
|
|
|
|
case SNDRV_PCM_STATE_PREPARED:
|
|
|
|
case SNDRV_PCM_STATE_PAUSED:
|
|
|
|
return 0;
|
2009-08-03 00:14:44 +08:00
|
|
|
case SNDRV_PCM_STATE_SUSPENDED:
|
2017-05-20 02:04:23 +08:00
|
|
|
return -ESTRPIPE;
|
|
|
|
case SNDRV_PCM_STATE_XRUN:
|
|
|
|
return -EPIPE;
|
2005-04-17 06:20:36 +08:00
|
|
|
default:
|
2017-05-20 02:04:23 +08:00
|
|
|
return -EBADFD;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
2017-05-20 02:04:23 +08:00
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2017-05-20 02:22:33 +08:00
|
|
|
/* increase the appl_ptr; returns the processed frames or a negative error */
|
2017-05-20 02:16:44 +08:00
|
|
|
static snd_pcm_sframes_t forward_appl_ptr(struct snd_pcm_substream *substream,
|
|
|
|
snd_pcm_uframes_t frames,
|
|
|
|
snd_pcm_sframes_t avail)
|
|
|
|
{
|
|
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
|
|
|
snd_pcm_sframes_t appl_ptr;
|
2017-05-20 02:22:33 +08:00
|
|
|
int ret;
|
2017-05-20 02:16:44 +08:00
|
|
|
|
|
|
|
if (avail <= 0)
|
|
|
|
return 0;
|
|
|
|
if (frames > (snd_pcm_uframes_t)avail)
|
|
|
|
frames = avail;
|
|
|
|
appl_ptr = runtime->control->appl_ptr + frames;
|
|
|
|
if (appl_ptr >= (snd_pcm_sframes_t)runtime->boundary)
|
|
|
|
appl_ptr -= runtime->boundary;
|
2017-06-12 08:41:44 +08:00
|
|
|
ret = pcm_lib_apply_appl_ptr(substream, appl_ptr);
|
2017-05-20 02:22:33 +08:00
|
|
|
return ret < 0 ? ret : frames;
|
2017-05-20 02:16:44 +08:00
|
|
|
}
|
|
|
|
|
ALSA: pcm: Workaround for weird PulseAudio behavior on rewind error
The commit 9027c4639ef1 ("ALSA: pcm: Call ack() whenever appl_ptr is
updated") introduced the possible error code returned from the PCM
rewind ioctl. Basically the change was for handling the indirect PCM
more correctly, but ironically, it caused rather a side-effect:
PulseAudio gets pissed off when receiving an error from rewind, throws
everything away and stops processing further, resulting in the
silence.
It's clearly a failure in the application side, so the best would be
to fix that bug in PA. OTOH, PA is mostly the only user of the rewind
feature, so it's not good to slap the sole customer.
This patch tries to mitigate the situation: instead of returning an
error, now the rewind ioctl returns zero when the driver can't rewind.
It indicates that no rewind was performed, so the behavior is
consistent, at least.
Fixes: 9027c4639ef1 ("ALSA: pcm: Call ack() whenever appl_ptr is updated")
Cc: <stable@vger.kernel.org>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2018-01-03 22:16:30 +08:00
|
|
|
/* decrease the appl_ptr; returns the processed frames or zero for error */
|
2017-05-20 02:16:44 +08:00
|
|
|
static snd_pcm_sframes_t rewind_appl_ptr(struct snd_pcm_substream *substream,
|
|
|
|
snd_pcm_uframes_t frames,
|
|
|
|
snd_pcm_sframes_t avail)
|
|
|
|
{
|
|
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
|
|
|
snd_pcm_sframes_t appl_ptr;
|
2017-05-20 02:22:33 +08:00
|
|
|
int ret;
|
2017-05-20 02:16:44 +08:00
|
|
|
|
|
|
|
if (avail <= 0)
|
|
|
|
return 0;
|
|
|
|
if (frames > (snd_pcm_uframes_t)avail)
|
|
|
|
frames = avail;
|
2005-04-17 06:20:36 +08:00
|
|
|
appl_ptr = runtime->control->appl_ptr - frames;
|
|
|
|
if (appl_ptr < 0)
|
|
|
|
appl_ptr += runtime->boundary;
|
2017-06-12 08:41:44 +08:00
|
|
|
ret = pcm_lib_apply_appl_ptr(substream, appl_ptr);
|
ALSA: pcm: Workaround for weird PulseAudio behavior on rewind error
The commit 9027c4639ef1 ("ALSA: pcm: Call ack() whenever appl_ptr is
updated") introduced the possible error code returned from the PCM
rewind ioctl. Basically the change was for handling the indirect PCM
more correctly, but ironically, it caused rather a side-effect:
PulseAudio gets pissed off when receiving an error from rewind, throws
everything away and stops processing further, resulting in the
silence.
It's clearly a failure in the application side, so the best would be
to fix that bug in PA. OTOH, PA is mostly the only user of the rewind
feature, so it's not good to slap the sole customer.
This patch tries to mitigate the situation: instead of returning an
error, now the rewind ioctl returns zero when the driver can't rewind.
It indicates that no rewind was performed, so the behavior is
consistent, at least.
Fixes: 9027c4639ef1 ("ALSA: pcm: Call ack() whenever appl_ptr is updated")
Cc: <stable@vger.kernel.org>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2018-01-03 22:16:30 +08:00
|
|
|
/* NOTE: we return zero for errors because PulseAudio gets depressed
|
|
|
|
* upon receiving an error from rewind ioctl and stops processing
|
|
|
|
* any longer. Returning zero means that no rewind is done, so
|
|
|
|
* it's not absolutely wrong to answer like that.
|
|
|
|
*/
|
|
|
|
return ret < 0 ? 0 : frames;
|
2017-05-20 02:16:44 +08:00
|
|
|
}
|
|
|
|
|
2018-04-11 23:56:52 +08:00
|
|
|
static snd_pcm_sframes_t snd_pcm_rewind(struct snd_pcm_substream *substream,
|
|
|
|
snd_pcm_uframes_t frames)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
snd_pcm_sframes_t ret;
|
|
|
|
|
|
|
|
if (frames == 0)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
snd_pcm_stream_lock_irq(substream);
|
2017-05-20 02:04:23 +08:00
|
|
|
ret = do_pcm_hwsync(substream);
|
2017-05-20 02:16:44 +08:00
|
|
|
if (!ret)
|
|
|
|
ret = rewind_appl_ptr(substream, frames,
|
2018-04-11 23:56:52 +08:00
|
|
|
snd_pcm_hw_avail(substream));
|
2005-04-17 06:20:36 +08:00
|
|
|
snd_pcm_stream_unlock_irq(substream);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2018-04-11 23:56:52 +08:00
|
|
|
static snd_pcm_sframes_t snd_pcm_forward(struct snd_pcm_substream *substream,
|
|
|
|
snd_pcm_uframes_t frames)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
snd_pcm_sframes_t ret;
|
|
|
|
|
|
|
|
if (frames == 0)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
snd_pcm_stream_lock_irq(substream);
|
2017-05-20 02:04:23 +08:00
|
|
|
ret = do_pcm_hwsync(substream);
|
2017-05-20 02:16:44 +08:00
|
|
|
if (!ret)
|
|
|
|
ret = forward_appl_ptr(substream, frames,
|
2018-04-11 23:56:52 +08:00
|
|
|
snd_pcm_avail(substream));
|
2005-04-17 06:20:36 +08:00
|
|
|
snd_pcm_stream_unlock_irq(substream);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2005-11-17 20:59:38 +08:00
|
|
|
static int snd_pcm_hwsync(struct snd_pcm_substream *substream)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
int err;
|
|
|
|
|
|
|
|
snd_pcm_stream_lock_irq(substream);
|
2017-05-20 02:04:23 +08:00
|
|
|
err = do_pcm_hwsync(substream);
|
2005-04-17 06:20:36 +08:00
|
|
|
snd_pcm_stream_unlock_irq(substream);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2018-04-21 12:20:46 +08:00
|
|
|
static int snd_pcm_delay(struct snd_pcm_substream *substream,
|
|
|
|
snd_pcm_sframes_t *delay)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
int err;
|
|
|
|
snd_pcm_sframes_t n = 0;
|
|
|
|
|
|
|
|
snd_pcm_stream_lock_irq(substream);
|
2017-05-20 02:04:23 +08:00
|
|
|
err = do_pcm_hwsync(substream);
|
2018-04-12 00:07:27 +08:00
|
|
|
if (!err)
|
|
|
|
n = snd_pcm_calc_delay(substream);
|
2005-04-17 06:20:36 +08:00
|
|
|
snd_pcm_stream_unlock_irq(substream);
|
2018-04-21 12:20:46 +08:00
|
|
|
if (!err)
|
|
|
|
*delay = n;
|
|
|
|
return err;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
2005-11-17 20:59:38 +08:00
|
|
|
static int snd_pcm_sync_ptr(struct snd_pcm_substream *substream,
|
|
|
|
struct snd_pcm_sync_ptr __user *_sync_ptr)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2005-11-17 20:59:38 +08:00
|
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
|
|
|
struct snd_pcm_sync_ptr sync_ptr;
|
|
|
|
volatile struct snd_pcm_mmap_status *status;
|
|
|
|
volatile struct snd_pcm_mmap_control *control;
|
2005-04-17 06:20:36 +08:00
|
|
|
int err;
|
|
|
|
|
|
|
|
memset(&sync_ptr, 0, sizeof(sync_ptr));
|
|
|
|
if (get_user(sync_ptr.flags, (unsigned __user *)&(_sync_ptr->flags)))
|
|
|
|
return -EFAULT;
|
2005-11-17 20:59:38 +08:00
|
|
|
if (copy_from_user(&sync_ptr.c.control, &(_sync_ptr->c.control), sizeof(struct snd_pcm_mmap_control)))
|
2005-04-17 06:20:36 +08:00
|
|
|
return -EFAULT;
|
|
|
|
status = runtime->status;
|
|
|
|
control = runtime->control;
|
|
|
|
if (sync_ptr.flags & SNDRV_PCM_SYNC_PTR_HWSYNC) {
|
|
|
|
err = snd_pcm_hwsync(substream);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
snd_pcm_stream_lock_irq(substream);
|
2017-05-20 02:22:33 +08:00
|
|
|
if (!(sync_ptr.flags & SNDRV_PCM_SYNC_PTR_APPL)) {
|
2017-06-12 08:41:44 +08:00
|
|
|
err = pcm_lib_apply_appl_ptr(substream,
|
|
|
|
sync_ptr.c.control.appl_ptr);
|
2017-05-20 02:22:33 +08:00
|
|
|
if (err < 0) {
|
|
|
|
snd_pcm_stream_unlock_irq(substream);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
} else {
|
2005-04-17 06:20:36 +08:00
|
|
|
sync_ptr.c.control.appl_ptr = control->appl_ptr;
|
2017-05-20 02:22:33 +08:00
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
if (!(sync_ptr.flags & SNDRV_PCM_SYNC_PTR_AVAIL_MIN))
|
|
|
|
control->avail_min = sync_ptr.c.control.avail_min;
|
|
|
|
else
|
|
|
|
sync_ptr.c.control.avail_min = control->avail_min;
|
|
|
|
sync_ptr.s.status.state = status->state;
|
|
|
|
sync_ptr.s.status.hw_ptr = status->hw_ptr;
|
|
|
|
sync_ptr.s.status.tstamp = status->tstamp;
|
|
|
|
sync_ptr.s.status.suspended_state = status->suspended_state;
|
2018-04-21 20:57:40 +08:00
|
|
|
sync_ptr.s.status.audio_tstamp = status->audio_tstamp;
|
2005-04-17 06:20:36 +08:00
|
|
|
snd_pcm_stream_unlock_irq(substream);
|
|
|
|
if (copy_to_user(_sync_ptr, &sync_ptr, sizeof(sync_ptr)))
|
|
|
|
return -EFAULT;
|
|
|
|
return 0;
|
|
|
|
}
|
2007-12-13 17:19:42 +08:00
|
|
|
|
2018-04-24 20:06:14 +08:00
|
|
|
struct snd_pcm_mmap_status32 {
|
2020-01-31 23:22:14 +08:00
|
|
|
snd_pcm_state_t state;
|
2018-04-24 20:06:14 +08:00
|
|
|
s32 pad1;
|
|
|
|
u32 hw_ptr;
|
ALSA: add new 32-bit layout for snd_pcm_mmap_status/control
The snd_pcm_mmap_status and snd_pcm_mmap_control interfaces are one of the
trickiest areas to get right when moving to 64-bit time_t in user space.
The snd_pcm_mmap_status structure layout is incompatible with user space
that uses a 64-bit time_t, so we need a new layout for it. Since the
SNDRV_PCM_IOCTL_SYNC_PTR ioctl combines it with snd_pcm_mmap_control
into snd_pcm_sync_ptr, we need to change those two as well.
Both structures are also exported via an mmap() operation on certain
architectures, and this suffers from incompatibility between 32-bit
and 64-bit user space. As we have to change both structures anyway,
this is a good opportunity to fix the mmap() problem as well, so let's
standardize on the existing 64-bit layout of the structure where possible.
The downside is that we lose mmap() support for existing 32-bit x86 and
powerpc applications, adding that would introduce very noticeable runtime
overhead and complexity. My assumption here is that not too many people
will miss the removed feature, given that:
- Almost all x86 and powerpc users these days are on 64-bit kernels,
the majority of today's 32-bit users are on architectures that never
supported mmap (ARM, MIPS, ...).
- It never worked in compat mode (it was intentionally disabled there)
- The application already needs to work with a fallback to
SNDRV_PCM_IOCTL_SYNC_PTR, which will keep working with both the old
and new structure layout.
Both the ioctl() and mmap() based interfaces are changed at the same
time, as they are based on the same structures. Unlike other interfaces,
we change the uapi header to export both the traditional structure and
a version that is portable between 32-bit and 64-bit user space code
and that corresponds to the existing 64-bit layout. We further check the
__USE_TIME_BITS64 macro that will be defined by future C library versions
whenever we use the new time_t definition, so any existing user space
source code will not see any changes until it gets rebuilt against a new
C library. However, the new structures are all visible in addition to the
old ones, allowing applications to explicitly request the new structures.
In order to detect the difference between the old snd_pcm_mmap_status and
the new __snd_pcm_mmap_status64 structure from the ioctl command number,
we rely on one quirk in the structure definition: snd_pcm_mmap_status
must be aligned to alignof(time_t), which leads the compiler to insert
four bytes of padding in struct snd_pcm_sync_ptr after 'flags' and a
corresponding change in the size of snd_pcm_sync_ptr itself. On x86-32
(and only there), the compiler doesn't use 64-bit alignment in structure,
so I'm adding an explicit pad in the structure that has no effect on the
existing 64-bit architectures but ensures that the layout matches for x86.
The snd_pcm_uframes_t type compatibility requires another hack: we can't
easily make that 64 bit wide, so I leave the type as 'unsigned long',
but add padding before and after it, to ensure that the data is properly
aligned to the respective 64-bit field in the in-kernel structure.
For the SNDRV_PCM_MMAP_OFFSET_STATUS/CONTROL constants that are used
as the virtual file offset in the mmap() function, we also have to
introduce new constants that depend on hte __USE_TIME_BITS64 macro:
The existing macros are renamed to SNDRV_PCM_MMAP_OFFSET_STATUS_OLD
and SNDRV_PCM_MMAP_OFFSET_CONTROL_OLD, they continue to work fine on
64-bit architectures, but stop working on native 32-bit user space.
The replacement _NEW constants are now used by default for user space
built with __USE_TIME_BITS64, those now work on all new kernels for x86,
ppc and alpha (32 and 64 bit, native and compat). It might be a good idea
for a future alsa-lib to support both the _OLD and _NEW macros and use
the corresponding structures directly. Unmodified alsa-lib source code
will retain the current behavior, so it will no longer be able to use
mmap() for the status/control structures on 32-bit systems, until either
the C library gets updated to 64-bit time_t or alsa-lib gets updated to
support both mmap() layouts.
Co-developed-with: Baolin Wang <baolin.wang@linaro.org>
Signed-off-by: Baolin Wang <baolin.wang@linaro.org>
Signed-off-by: Arnd Bergmann <arnd@arndb.de>
2018-04-24 20:06:15 +08:00
|
|
|
s32 tstamp_sec;
|
|
|
|
s32 tstamp_nsec;
|
2020-01-31 23:22:14 +08:00
|
|
|
snd_pcm_state_t suspended_state;
|
ALSA: add new 32-bit layout for snd_pcm_mmap_status/control
The snd_pcm_mmap_status and snd_pcm_mmap_control interfaces are one of the
trickiest areas to get right when moving to 64-bit time_t in user space.
The snd_pcm_mmap_status structure layout is incompatible with user space
that uses a 64-bit time_t, so we need a new layout for it. Since the
SNDRV_PCM_IOCTL_SYNC_PTR ioctl combines it with snd_pcm_mmap_control
into snd_pcm_sync_ptr, we need to change those two as well.
Both structures are also exported via an mmap() operation on certain
architectures, and this suffers from incompatibility between 32-bit
and 64-bit user space. As we have to change both structures anyway,
this is a good opportunity to fix the mmap() problem as well, so let's
standardize on the existing 64-bit layout of the structure where possible.
The downside is that we lose mmap() support for existing 32-bit x86 and
powerpc applications, adding that would introduce very noticeable runtime
overhead and complexity. My assumption here is that not too many people
will miss the removed feature, given that:
- Almost all x86 and powerpc users these days are on 64-bit kernels,
the majority of today's 32-bit users are on architectures that never
supported mmap (ARM, MIPS, ...).
- It never worked in compat mode (it was intentionally disabled there)
- The application already needs to work with a fallback to
SNDRV_PCM_IOCTL_SYNC_PTR, which will keep working with both the old
and new structure layout.
Both the ioctl() and mmap() based interfaces are changed at the same
time, as they are based on the same structures. Unlike other interfaces,
we change the uapi header to export both the traditional structure and
a version that is portable between 32-bit and 64-bit user space code
and that corresponds to the existing 64-bit layout. We further check the
__USE_TIME_BITS64 macro that will be defined by future C library versions
whenever we use the new time_t definition, so any existing user space
source code will not see any changes until it gets rebuilt against a new
C library. However, the new structures are all visible in addition to the
old ones, allowing applications to explicitly request the new structures.
In order to detect the difference between the old snd_pcm_mmap_status and
the new __snd_pcm_mmap_status64 structure from the ioctl command number,
we rely on one quirk in the structure definition: snd_pcm_mmap_status
must be aligned to alignof(time_t), which leads the compiler to insert
four bytes of padding in struct snd_pcm_sync_ptr after 'flags' and a
corresponding change in the size of snd_pcm_sync_ptr itself. On x86-32
(and only there), the compiler doesn't use 64-bit alignment in structure,
so I'm adding an explicit pad in the structure that has no effect on the
existing 64-bit architectures but ensures that the layout matches for x86.
The snd_pcm_uframes_t type compatibility requires another hack: we can't
easily make that 64 bit wide, so I leave the type as 'unsigned long',
but add padding before and after it, to ensure that the data is properly
aligned to the respective 64-bit field in the in-kernel structure.
For the SNDRV_PCM_MMAP_OFFSET_STATUS/CONTROL constants that are used
as the virtual file offset in the mmap() function, we also have to
introduce new constants that depend on hte __USE_TIME_BITS64 macro:
The existing macros are renamed to SNDRV_PCM_MMAP_OFFSET_STATUS_OLD
and SNDRV_PCM_MMAP_OFFSET_CONTROL_OLD, they continue to work fine on
64-bit architectures, but stop working on native 32-bit user space.
The replacement _NEW constants are now used by default for user space
built with __USE_TIME_BITS64, those now work on all new kernels for x86,
ppc and alpha (32 and 64 bit, native and compat). It might be a good idea
for a future alsa-lib to support both the _OLD and _NEW macros and use
the corresponding structures directly. Unmodified alsa-lib source code
will retain the current behavior, so it will no longer be able to use
mmap() for the status/control structures on 32-bit systems, until either
the C library gets updated to 64-bit time_t or alsa-lib gets updated to
support both mmap() layouts.
Co-developed-with: Baolin Wang <baolin.wang@linaro.org>
Signed-off-by: Baolin Wang <baolin.wang@linaro.org>
Signed-off-by: Arnd Bergmann <arnd@arndb.de>
2018-04-24 20:06:15 +08:00
|
|
|
s32 audio_tstamp_sec;
|
|
|
|
s32 audio_tstamp_nsec;
|
2018-04-24 20:06:14 +08:00
|
|
|
} __attribute__((packed));
|
|
|
|
|
|
|
|
struct snd_pcm_mmap_control32 {
|
|
|
|
u32 appl_ptr;
|
|
|
|
u32 avail_min;
|
|
|
|
};
|
|
|
|
|
|
|
|
struct snd_pcm_sync_ptr32 {
|
|
|
|
u32 flags;
|
|
|
|
union {
|
|
|
|
struct snd_pcm_mmap_status32 status;
|
|
|
|
unsigned char reserved[64];
|
|
|
|
} s;
|
|
|
|
union {
|
|
|
|
struct snd_pcm_mmap_control32 control;
|
|
|
|
unsigned char reserved[64];
|
|
|
|
} c;
|
|
|
|
} __attribute__((packed));
|
|
|
|
|
|
|
|
/* recalcuate the boundary within 32bit */
|
|
|
|
static snd_pcm_uframes_t recalculate_boundary(struct snd_pcm_runtime *runtime)
|
|
|
|
{
|
|
|
|
snd_pcm_uframes_t boundary;
|
|
|
|
|
|
|
|
if (! runtime->buffer_size)
|
|
|
|
return 0;
|
|
|
|
boundary = runtime->buffer_size;
|
|
|
|
while (boundary * 2 <= 0x7fffffffUL - runtime->buffer_size)
|
|
|
|
boundary *= 2;
|
|
|
|
return boundary;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int snd_pcm_ioctl_sync_ptr_compat(struct snd_pcm_substream *substream,
|
|
|
|
struct snd_pcm_sync_ptr32 __user *src)
|
|
|
|
{
|
|
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
|
|
|
volatile struct snd_pcm_mmap_status *status;
|
|
|
|
volatile struct snd_pcm_mmap_control *control;
|
|
|
|
u32 sflags;
|
|
|
|
struct snd_pcm_mmap_control scontrol;
|
|
|
|
struct snd_pcm_mmap_status sstatus;
|
|
|
|
snd_pcm_uframes_t boundary;
|
|
|
|
int err;
|
|
|
|
|
|
|
|
if (snd_BUG_ON(!runtime))
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
if (get_user(sflags, &src->flags) ||
|
|
|
|
get_user(scontrol.appl_ptr, &src->c.control.appl_ptr) ||
|
|
|
|
get_user(scontrol.avail_min, &src->c.control.avail_min))
|
|
|
|
return -EFAULT;
|
|
|
|
if (sflags & SNDRV_PCM_SYNC_PTR_HWSYNC) {
|
|
|
|
err = snd_pcm_hwsync(substream);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
status = runtime->status;
|
|
|
|
control = runtime->control;
|
|
|
|
boundary = recalculate_boundary(runtime);
|
|
|
|
if (! boundary)
|
|
|
|
boundary = 0x7fffffff;
|
|
|
|
snd_pcm_stream_lock_irq(substream);
|
|
|
|
/* FIXME: we should consider the boundary for the sync from app */
|
|
|
|
if (!(sflags & SNDRV_PCM_SYNC_PTR_APPL))
|
|
|
|
control->appl_ptr = scontrol.appl_ptr;
|
|
|
|
else
|
|
|
|
scontrol.appl_ptr = control->appl_ptr % boundary;
|
|
|
|
if (!(sflags & SNDRV_PCM_SYNC_PTR_AVAIL_MIN))
|
|
|
|
control->avail_min = scontrol.avail_min;
|
|
|
|
else
|
|
|
|
scontrol.avail_min = control->avail_min;
|
|
|
|
sstatus.state = status->state;
|
|
|
|
sstatus.hw_ptr = status->hw_ptr % boundary;
|
|
|
|
sstatus.tstamp = status->tstamp;
|
|
|
|
sstatus.suspended_state = status->suspended_state;
|
|
|
|
sstatus.audio_tstamp = status->audio_tstamp;
|
|
|
|
snd_pcm_stream_unlock_irq(substream);
|
|
|
|
if (put_user(sstatus.state, &src->s.status.state) ||
|
|
|
|
put_user(sstatus.hw_ptr, &src->s.status.hw_ptr) ||
|
ALSA: add new 32-bit layout for snd_pcm_mmap_status/control
The snd_pcm_mmap_status and snd_pcm_mmap_control interfaces are one of the
trickiest areas to get right when moving to 64-bit time_t in user space.
The snd_pcm_mmap_status structure layout is incompatible with user space
that uses a 64-bit time_t, so we need a new layout for it. Since the
SNDRV_PCM_IOCTL_SYNC_PTR ioctl combines it with snd_pcm_mmap_control
into snd_pcm_sync_ptr, we need to change those two as well.
Both structures are also exported via an mmap() operation on certain
architectures, and this suffers from incompatibility between 32-bit
and 64-bit user space. As we have to change both structures anyway,
this is a good opportunity to fix the mmap() problem as well, so let's
standardize on the existing 64-bit layout of the structure where possible.
The downside is that we lose mmap() support for existing 32-bit x86 and
powerpc applications, adding that would introduce very noticeable runtime
overhead and complexity. My assumption here is that not too many people
will miss the removed feature, given that:
- Almost all x86 and powerpc users these days are on 64-bit kernels,
the majority of today's 32-bit users are on architectures that never
supported mmap (ARM, MIPS, ...).
- It never worked in compat mode (it was intentionally disabled there)
- The application already needs to work with a fallback to
SNDRV_PCM_IOCTL_SYNC_PTR, which will keep working with both the old
and new structure layout.
Both the ioctl() and mmap() based interfaces are changed at the same
time, as they are based on the same structures. Unlike other interfaces,
we change the uapi header to export both the traditional structure and
a version that is portable between 32-bit and 64-bit user space code
and that corresponds to the existing 64-bit layout. We further check the
__USE_TIME_BITS64 macro that will be defined by future C library versions
whenever we use the new time_t definition, so any existing user space
source code will not see any changes until it gets rebuilt against a new
C library. However, the new structures are all visible in addition to the
old ones, allowing applications to explicitly request the new structures.
In order to detect the difference between the old snd_pcm_mmap_status and
the new __snd_pcm_mmap_status64 structure from the ioctl command number,
we rely on one quirk in the structure definition: snd_pcm_mmap_status
must be aligned to alignof(time_t), which leads the compiler to insert
four bytes of padding in struct snd_pcm_sync_ptr after 'flags' and a
corresponding change in the size of snd_pcm_sync_ptr itself. On x86-32
(and only there), the compiler doesn't use 64-bit alignment in structure,
so I'm adding an explicit pad in the structure that has no effect on the
existing 64-bit architectures but ensures that the layout matches for x86.
The snd_pcm_uframes_t type compatibility requires another hack: we can't
easily make that 64 bit wide, so I leave the type as 'unsigned long',
but add padding before and after it, to ensure that the data is properly
aligned to the respective 64-bit field in the in-kernel structure.
For the SNDRV_PCM_MMAP_OFFSET_STATUS/CONTROL constants that are used
as the virtual file offset in the mmap() function, we also have to
introduce new constants that depend on hte __USE_TIME_BITS64 macro:
The existing macros are renamed to SNDRV_PCM_MMAP_OFFSET_STATUS_OLD
and SNDRV_PCM_MMAP_OFFSET_CONTROL_OLD, they continue to work fine on
64-bit architectures, but stop working on native 32-bit user space.
The replacement _NEW constants are now used by default for user space
built with __USE_TIME_BITS64, those now work on all new kernels for x86,
ppc and alpha (32 and 64 bit, native and compat). It might be a good idea
for a future alsa-lib to support both the _OLD and _NEW macros and use
the corresponding structures directly. Unmodified alsa-lib source code
will retain the current behavior, so it will no longer be able to use
mmap() for the status/control structures on 32-bit systems, until either
the C library gets updated to 64-bit time_t or alsa-lib gets updated to
support both mmap() layouts.
Co-developed-with: Baolin Wang <baolin.wang@linaro.org>
Signed-off-by: Baolin Wang <baolin.wang@linaro.org>
Signed-off-by: Arnd Bergmann <arnd@arndb.de>
2018-04-24 20:06:15 +08:00
|
|
|
put_user(sstatus.tstamp.tv_sec, &src->s.status.tstamp_sec) ||
|
|
|
|
put_user(sstatus.tstamp.tv_nsec, &src->s.status.tstamp_nsec) ||
|
2018-04-24 20:06:14 +08:00
|
|
|
put_user(sstatus.suspended_state, &src->s.status.suspended_state) ||
|
ALSA: add new 32-bit layout for snd_pcm_mmap_status/control
The snd_pcm_mmap_status and snd_pcm_mmap_control interfaces are one of the
trickiest areas to get right when moving to 64-bit time_t in user space.
The snd_pcm_mmap_status structure layout is incompatible with user space
that uses a 64-bit time_t, so we need a new layout for it. Since the
SNDRV_PCM_IOCTL_SYNC_PTR ioctl combines it with snd_pcm_mmap_control
into snd_pcm_sync_ptr, we need to change those two as well.
Both structures are also exported via an mmap() operation on certain
architectures, and this suffers from incompatibility between 32-bit
and 64-bit user space. As we have to change both structures anyway,
this is a good opportunity to fix the mmap() problem as well, so let's
standardize on the existing 64-bit layout of the structure where possible.
The downside is that we lose mmap() support for existing 32-bit x86 and
powerpc applications, adding that would introduce very noticeable runtime
overhead and complexity. My assumption here is that not too many people
will miss the removed feature, given that:
- Almost all x86 and powerpc users these days are on 64-bit kernels,
the majority of today's 32-bit users are on architectures that never
supported mmap (ARM, MIPS, ...).
- It never worked in compat mode (it was intentionally disabled there)
- The application already needs to work with a fallback to
SNDRV_PCM_IOCTL_SYNC_PTR, which will keep working with both the old
and new structure layout.
Both the ioctl() and mmap() based interfaces are changed at the same
time, as they are based on the same structures. Unlike other interfaces,
we change the uapi header to export both the traditional structure and
a version that is portable between 32-bit and 64-bit user space code
and that corresponds to the existing 64-bit layout. We further check the
__USE_TIME_BITS64 macro that will be defined by future C library versions
whenever we use the new time_t definition, so any existing user space
source code will not see any changes until it gets rebuilt against a new
C library. However, the new structures are all visible in addition to the
old ones, allowing applications to explicitly request the new structures.
In order to detect the difference between the old snd_pcm_mmap_status and
the new __snd_pcm_mmap_status64 structure from the ioctl command number,
we rely on one quirk in the structure definition: snd_pcm_mmap_status
must be aligned to alignof(time_t), which leads the compiler to insert
four bytes of padding in struct snd_pcm_sync_ptr after 'flags' and a
corresponding change in the size of snd_pcm_sync_ptr itself. On x86-32
(and only there), the compiler doesn't use 64-bit alignment in structure,
so I'm adding an explicit pad in the structure that has no effect on the
existing 64-bit architectures but ensures that the layout matches for x86.
The snd_pcm_uframes_t type compatibility requires another hack: we can't
easily make that 64 bit wide, so I leave the type as 'unsigned long',
but add padding before and after it, to ensure that the data is properly
aligned to the respective 64-bit field in the in-kernel structure.
For the SNDRV_PCM_MMAP_OFFSET_STATUS/CONTROL constants that are used
as the virtual file offset in the mmap() function, we also have to
introduce new constants that depend on hte __USE_TIME_BITS64 macro:
The existing macros are renamed to SNDRV_PCM_MMAP_OFFSET_STATUS_OLD
and SNDRV_PCM_MMAP_OFFSET_CONTROL_OLD, they continue to work fine on
64-bit architectures, but stop working on native 32-bit user space.
The replacement _NEW constants are now used by default for user space
built with __USE_TIME_BITS64, those now work on all new kernels for x86,
ppc and alpha (32 and 64 bit, native and compat). It might be a good idea
for a future alsa-lib to support both the _OLD and _NEW macros and use
the corresponding structures directly. Unmodified alsa-lib source code
will retain the current behavior, so it will no longer be able to use
mmap() for the status/control structures on 32-bit systems, until either
the C library gets updated to 64-bit time_t or alsa-lib gets updated to
support both mmap() layouts.
Co-developed-with: Baolin Wang <baolin.wang@linaro.org>
Signed-off-by: Baolin Wang <baolin.wang@linaro.org>
Signed-off-by: Arnd Bergmann <arnd@arndb.de>
2018-04-24 20:06:15 +08:00
|
|
|
put_user(sstatus.audio_tstamp.tv_sec, &src->s.status.audio_tstamp_sec) ||
|
|
|
|
put_user(sstatus.audio_tstamp.tv_nsec, &src->s.status.audio_tstamp_nsec) ||
|
2018-04-24 20:06:14 +08:00
|
|
|
put_user(scontrol.appl_ptr, &src->c.control.appl_ptr) ||
|
|
|
|
put_user(scontrol.avail_min, &src->c.control.avail_min))
|
|
|
|
return -EFAULT;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
ALSA: add new 32-bit layout for snd_pcm_mmap_status/control
The snd_pcm_mmap_status and snd_pcm_mmap_control interfaces are one of the
trickiest areas to get right when moving to 64-bit time_t in user space.
The snd_pcm_mmap_status structure layout is incompatible with user space
that uses a 64-bit time_t, so we need a new layout for it. Since the
SNDRV_PCM_IOCTL_SYNC_PTR ioctl combines it with snd_pcm_mmap_control
into snd_pcm_sync_ptr, we need to change those two as well.
Both structures are also exported via an mmap() operation on certain
architectures, and this suffers from incompatibility between 32-bit
and 64-bit user space. As we have to change both structures anyway,
this is a good opportunity to fix the mmap() problem as well, so let's
standardize on the existing 64-bit layout of the structure where possible.
The downside is that we lose mmap() support for existing 32-bit x86 and
powerpc applications, adding that would introduce very noticeable runtime
overhead and complexity. My assumption here is that not too many people
will miss the removed feature, given that:
- Almost all x86 and powerpc users these days are on 64-bit kernels,
the majority of today's 32-bit users are on architectures that never
supported mmap (ARM, MIPS, ...).
- It never worked in compat mode (it was intentionally disabled there)
- The application already needs to work with a fallback to
SNDRV_PCM_IOCTL_SYNC_PTR, which will keep working with both the old
and new structure layout.
Both the ioctl() and mmap() based interfaces are changed at the same
time, as they are based on the same structures. Unlike other interfaces,
we change the uapi header to export both the traditional structure and
a version that is portable between 32-bit and 64-bit user space code
and that corresponds to the existing 64-bit layout. We further check the
__USE_TIME_BITS64 macro that will be defined by future C library versions
whenever we use the new time_t definition, so any existing user space
source code will not see any changes until it gets rebuilt against a new
C library. However, the new structures are all visible in addition to the
old ones, allowing applications to explicitly request the new structures.
In order to detect the difference between the old snd_pcm_mmap_status and
the new __snd_pcm_mmap_status64 structure from the ioctl command number,
we rely on one quirk in the structure definition: snd_pcm_mmap_status
must be aligned to alignof(time_t), which leads the compiler to insert
four bytes of padding in struct snd_pcm_sync_ptr after 'flags' and a
corresponding change in the size of snd_pcm_sync_ptr itself. On x86-32
(and only there), the compiler doesn't use 64-bit alignment in structure,
so I'm adding an explicit pad in the structure that has no effect on the
existing 64-bit architectures but ensures that the layout matches for x86.
The snd_pcm_uframes_t type compatibility requires another hack: we can't
easily make that 64 bit wide, so I leave the type as 'unsigned long',
but add padding before and after it, to ensure that the data is properly
aligned to the respective 64-bit field in the in-kernel structure.
For the SNDRV_PCM_MMAP_OFFSET_STATUS/CONTROL constants that are used
as the virtual file offset in the mmap() function, we also have to
introduce new constants that depend on hte __USE_TIME_BITS64 macro:
The existing macros are renamed to SNDRV_PCM_MMAP_OFFSET_STATUS_OLD
and SNDRV_PCM_MMAP_OFFSET_CONTROL_OLD, they continue to work fine on
64-bit architectures, but stop working on native 32-bit user space.
The replacement _NEW constants are now used by default for user space
built with __USE_TIME_BITS64, those now work on all new kernels for x86,
ppc and alpha (32 and 64 bit, native and compat). It might be a good idea
for a future alsa-lib to support both the _OLD and _NEW macros and use
the corresponding structures directly. Unmodified alsa-lib source code
will retain the current behavior, so it will no longer be able to use
mmap() for the status/control structures on 32-bit systems, until either
the C library gets updated to 64-bit time_t or alsa-lib gets updated to
support both mmap() layouts.
Co-developed-with: Baolin Wang <baolin.wang@linaro.org>
Signed-off-by: Baolin Wang <baolin.wang@linaro.org>
Signed-off-by: Arnd Bergmann <arnd@arndb.de>
2018-04-24 20:06:15 +08:00
|
|
|
#define __SNDRV_PCM_IOCTL_SYNC_PTR32 _IOWR('A', 0x23, struct snd_pcm_sync_ptr32)
|
2018-04-24 20:06:14 +08:00
|
|
|
|
2007-12-13 17:19:42 +08:00
|
|
|
static int snd_pcm_tstamp(struct snd_pcm_substream *substream, int __user *_arg)
|
|
|
|
{
|
|
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
|
|
|
int arg;
|
|
|
|
|
|
|
|
if (get_user(arg, _arg))
|
|
|
|
return -EFAULT;
|
|
|
|
if (arg < 0 || arg > SNDRV_PCM_TSTAMP_TYPE_LAST)
|
|
|
|
return -EINVAL;
|
2014-07-10 15:40:38 +08:00
|
|
|
runtime->tstamp_type = arg;
|
2007-12-13 17:19:42 +08:00
|
|
|
return 0;
|
|
|
|
}
|
2017-08-30 21:39:32 +08:00
|
|
|
|
|
|
|
static int snd_pcm_xferi_frames_ioctl(struct snd_pcm_substream *substream,
|
|
|
|
struct snd_xferi __user *_xferi)
|
|
|
|
{
|
|
|
|
struct snd_xferi xferi;
|
|
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
|
|
|
snd_pcm_sframes_t result;
|
|
|
|
|
|
|
|
if (runtime->status->state == SNDRV_PCM_STATE_OPEN)
|
|
|
|
return -EBADFD;
|
|
|
|
if (put_user(0, &_xferi->result))
|
|
|
|
return -EFAULT;
|
|
|
|
if (copy_from_user(&xferi, _xferi, sizeof(xferi)))
|
|
|
|
return -EFAULT;
|
|
|
|
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
|
|
|
result = snd_pcm_lib_write(substream, xferi.buf, xferi.frames);
|
|
|
|
else
|
|
|
|
result = snd_pcm_lib_read(substream, xferi.buf, xferi.frames);
|
2020-03-20 06:08:59 +08:00
|
|
|
if (put_user(result, &_xferi->result))
|
|
|
|
return -EFAULT;
|
2017-08-30 21:39:32 +08:00
|
|
|
return result < 0 ? result : 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int snd_pcm_xfern_frames_ioctl(struct snd_pcm_substream *substream,
|
|
|
|
struct snd_xfern __user *_xfern)
|
|
|
|
{
|
|
|
|
struct snd_xfern xfern;
|
|
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
|
|
|
void *bufs;
|
|
|
|
snd_pcm_sframes_t result;
|
|
|
|
|
|
|
|
if (runtime->status->state == SNDRV_PCM_STATE_OPEN)
|
|
|
|
return -EBADFD;
|
|
|
|
if (runtime->channels > 128)
|
|
|
|
return -EINVAL;
|
|
|
|
if (put_user(0, &_xfern->result))
|
|
|
|
return -EFAULT;
|
|
|
|
if (copy_from_user(&xfern, _xfern, sizeof(xfern)))
|
|
|
|
return -EFAULT;
|
|
|
|
|
|
|
|
bufs = memdup_user(xfern.bufs, sizeof(void *) * runtime->channels);
|
|
|
|
if (IS_ERR(bufs))
|
|
|
|
return PTR_ERR(bufs);
|
|
|
|
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
|
|
|
result = snd_pcm_lib_writev(substream, bufs, xfern.frames);
|
|
|
|
else
|
|
|
|
result = snd_pcm_lib_readv(substream, bufs, xfern.frames);
|
|
|
|
kfree(bufs);
|
2020-03-20 06:08:59 +08:00
|
|
|
if (put_user(result, &_xfern->result))
|
|
|
|
return -EFAULT;
|
2017-08-30 21:39:32 +08:00
|
|
|
return result < 0 ? result : 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int snd_pcm_rewind_ioctl(struct snd_pcm_substream *substream,
|
|
|
|
snd_pcm_uframes_t __user *_frames)
|
|
|
|
{
|
|
|
|
snd_pcm_uframes_t frames;
|
|
|
|
snd_pcm_sframes_t result;
|
|
|
|
|
|
|
|
if (get_user(frames, _frames))
|
|
|
|
return -EFAULT;
|
|
|
|
if (put_user(0, _frames))
|
|
|
|
return -EFAULT;
|
2018-04-11 23:56:52 +08:00
|
|
|
result = snd_pcm_rewind(substream, frames);
|
2020-03-20 06:08:59 +08:00
|
|
|
if (put_user(result, _frames))
|
|
|
|
return -EFAULT;
|
2017-08-30 21:39:32 +08:00
|
|
|
return result < 0 ? result : 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int snd_pcm_forward_ioctl(struct snd_pcm_substream *substream,
|
|
|
|
snd_pcm_uframes_t __user *_frames)
|
|
|
|
{
|
|
|
|
snd_pcm_uframes_t frames;
|
|
|
|
snd_pcm_sframes_t result;
|
|
|
|
|
|
|
|
if (get_user(frames, _frames))
|
|
|
|
return -EFAULT;
|
|
|
|
if (put_user(0, _frames))
|
|
|
|
return -EFAULT;
|
2018-04-11 23:56:52 +08:00
|
|
|
result = snd_pcm_forward(substream, frames);
|
2020-03-20 06:08:59 +08:00
|
|
|
if (put_user(result, _frames))
|
|
|
|
return -EFAULT;
|
2017-08-30 21:39:32 +08:00
|
|
|
return result < 0 ? result : 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int snd_pcm_common_ioctl(struct file *file,
|
2006-04-28 21:13:41 +08:00
|
|
|
struct snd_pcm_substream *substream,
|
2005-04-17 06:20:36 +08:00
|
|
|
unsigned int cmd, void __user *arg)
|
|
|
|
{
|
ALSA: pcm: Add an ioctl to specify the supported protocol version
We have an ioctl to inform the PCM protocol version the running kernel
supports, but there is no way to know which protocol version the
user-space can understand. This lack of information caused headaches
in the past when we tried to extend the ABI. For example, because we
couldn't guarantee the validity of the reserved bytes, we had to
introduce a new ioctl SNDRV_PCM_IOCTL_STATUS_EXT for assigning a few
new fields in the formerly reserved bits. If we could know that it's
a new alsa-lib, we could assume the availability of the new fields,
thus we could have reused the existing SNDRV_PCM_IOCTL_STATUS.
In order to improve the ABI extensibility, this patch adds a new ioctl
for user-space to inform its supporting protocol version to the
kernel. By reporting the supported protocol from user-space, the
kernel can judge which feature should be provided and which not.
With the addition of the new ioctl, the PCM protocol version is bumped
to 2.0.14, too. User-space checks the kernel protocol version via
SNDRV_PCM_INFO_PVERSION, then it sets the supported version back via
SNDRV_PCM_INFO_USER_PVERSION.
Reviewed-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2017-06-20 05:11:54 +08:00
|
|
|
struct snd_pcm_file *pcm_file = file->private_data;
|
2017-08-30 22:13:25 +08:00
|
|
|
int res;
|
|
|
|
|
2017-08-30 21:39:32 +08:00
|
|
|
if (PCM_RUNTIME_CHECK(substream))
|
|
|
|
return -ENXIO;
|
|
|
|
|
2017-08-30 22:13:25 +08:00
|
|
|
res = snd_power_wait(substream->pcm->card, SNDRV_CTL_POWER_D0);
|
|
|
|
if (res < 0)
|
|
|
|
return res;
|
ALSA: pcm: Add an ioctl to specify the supported protocol version
We have an ioctl to inform the PCM protocol version the running kernel
supports, but there is no way to know which protocol version the
user-space can understand. This lack of information caused headaches
in the past when we tried to extend the ABI. For example, because we
couldn't guarantee the validity of the reserved bytes, we had to
introduce a new ioctl SNDRV_PCM_IOCTL_STATUS_EXT for assigning a few
new fields in the formerly reserved bits. If we could know that it's
a new alsa-lib, we could assume the availability of the new fields,
thus we could have reused the existing SNDRV_PCM_IOCTL_STATUS.
In order to improve the ABI extensibility, this patch adds a new ioctl
for user-space to inform its supporting protocol version to the
kernel. By reporting the supported protocol from user-space, the
kernel can judge which feature should be provided and which not.
With the addition of the new ioctl, the PCM protocol version is bumped
to 2.0.14, too. User-space checks the kernel protocol version via
SNDRV_PCM_INFO_PVERSION, then it sets the supported version back via
SNDRV_PCM_INFO_USER_PVERSION.
Reviewed-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2017-06-20 05:11:54 +08:00
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
switch (cmd) {
|
|
|
|
case SNDRV_PCM_IOCTL_PVERSION:
|
|
|
|
return put_user(SNDRV_PCM_VERSION, (int __user *)arg) ? -EFAULT : 0;
|
|
|
|
case SNDRV_PCM_IOCTL_INFO:
|
|
|
|
return snd_pcm_info_user(substream, arg);
|
2007-12-17 16:02:22 +08:00
|
|
|
case SNDRV_PCM_IOCTL_TSTAMP: /* just for compatibility */
|
|
|
|
return 0;
|
2007-12-13 17:19:42 +08:00
|
|
|
case SNDRV_PCM_IOCTL_TTSTAMP:
|
|
|
|
return snd_pcm_tstamp(substream, arg);
|
ALSA: pcm: Add an ioctl to specify the supported protocol version
We have an ioctl to inform the PCM protocol version the running kernel
supports, but there is no way to know which protocol version the
user-space can understand. This lack of information caused headaches
in the past when we tried to extend the ABI. For example, because we
couldn't guarantee the validity of the reserved bytes, we had to
introduce a new ioctl SNDRV_PCM_IOCTL_STATUS_EXT for assigning a few
new fields in the formerly reserved bits. If we could know that it's
a new alsa-lib, we could assume the availability of the new fields,
thus we could have reused the existing SNDRV_PCM_IOCTL_STATUS.
In order to improve the ABI extensibility, this patch adds a new ioctl
for user-space to inform its supporting protocol version to the
kernel. By reporting the supported protocol from user-space, the
kernel can judge which feature should be provided and which not.
With the addition of the new ioctl, the PCM protocol version is bumped
to 2.0.14, too. User-space checks the kernel protocol version via
SNDRV_PCM_INFO_PVERSION, then it sets the supported version back via
SNDRV_PCM_INFO_USER_PVERSION.
Reviewed-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2017-06-20 05:11:54 +08:00
|
|
|
case SNDRV_PCM_IOCTL_USER_PVERSION:
|
|
|
|
if (get_user(pcm_file->user_pversion,
|
|
|
|
(unsigned int __user *)arg))
|
|
|
|
return -EFAULT;
|
|
|
|
return 0;
|
2005-04-17 06:20:36 +08:00
|
|
|
case SNDRV_PCM_IOCTL_HW_REFINE:
|
|
|
|
return snd_pcm_hw_refine_user(substream, arg);
|
|
|
|
case SNDRV_PCM_IOCTL_HW_PARAMS:
|
|
|
|
return snd_pcm_hw_params_user(substream, arg);
|
|
|
|
case SNDRV_PCM_IOCTL_HW_FREE:
|
|
|
|
return snd_pcm_hw_free(substream);
|
|
|
|
case SNDRV_PCM_IOCTL_SW_PARAMS:
|
|
|
|
return snd_pcm_sw_params_user(substream, arg);
|
2018-04-24 20:06:11 +08:00
|
|
|
case SNDRV_PCM_IOCTL_STATUS32:
|
|
|
|
return snd_pcm_status_user32(substream, arg, false);
|
|
|
|
case SNDRV_PCM_IOCTL_STATUS_EXT32:
|
|
|
|
return snd_pcm_status_user32(substream, arg, true);
|
|
|
|
case SNDRV_PCM_IOCTL_STATUS64:
|
|
|
|
return snd_pcm_status_user64(substream, arg, false);
|
|
|
|
case SNDRV_PCM_IOCTL_STATUS_EXT64:
|
|
|
|
return snd_pcm_status_user64(substream, arg, true);
|
2005-04-17 06:20:36 +08:00
|
|
|
case SNDRV_PCM_IOCTL_CHANNEL_INFO:
|
|
|
|
return snd_pcm_channel_info_user(substream, arg);
|
|
|
|
case SNDRV_PCM_IOCTL_PREPARE:
|
2006-04-28 21:13:41 +08:00
|
|
|
return snd_pcm_prepare(substream, file);
|
2005-04-17 06:20:36 +08:00
|
|
|
case SNDRV_PCM_IOCTL_RESET:
|
|
|
|
return snd_pcm_reset(substream);
|
|
|
|
case SNDRV_PCM_IOCTL_START:
|
2017-05-10 20:33:44 +08:00
|
|
|
return snd_pcm_start_lock_irq(substream);
|
2005-04-17 06:20:36 +08:00
|
|
|
case SNDRV_PCM_IOCTL_LINK:
|
|
|
|
return snd_pcm_link(substream, (int)(unsigned long) arg);
|
|
|
|
case SNDRV_PCM_IOCTL_UNLINK:
|
|
|
|
return snd_pcm_unlink(substream);
|
|
|
|
case SNDRV_PCM_IOCTL_RESUME:
|
|
|
|
return snd_pcm_resume(substream);
|
|
|
|
case SNDRV_PCM_IOCTL_XRUN:
|
|
|
|
return snd_pcm_xrun(substream);
|
|
|
|
case SNDRV_PCM_IOCTL_HWSYNC:
|
|
|
|
return snd_pcm_hwsync(substream);
|
|
|
|
case SNDRV_PCM_IOCTL_DELAY:
|
2017-05-10 20:33:44 +08:00
|
|
|
{
|
2018-04-21 12:20:46 +08:00
|
|
|
snd_pcm_sframes_t delay;
|
2017-05-10 20:33:44 +08:00
|
|
|
snd_pcm_sframes_t __user *res = arg;
|
2018-04-21 12:20:46 +08:00
|
|
|
int err;
|
2017-05-10 20:33:44 +08:00
|
|
|
|
2018-04-21 12:20:46 +08:00
|
|
|
err = snd_pcm_delay(substream, &delay);
|
|
|
|
if (err)
|
|
|
|
return err;
|
2017-05-10 20:33:44 +08:00
|
|
|
if (put_user(delay, res))
|
|
|
|
return -EFAULT;
|
|
|
|
return 0;
|
|
|
|
}
|
ALSA: add new 32-bit layout for snd_pcm_mmap_status/control
The snd_pcm_mmap_status and snd_pcm_mmap_control interfaces are one of the
trickiest areas to get right when moving to 64-bit time_t in user space.
The snd_pcm_mmap_status structure layout is incompatible with user space
that uses a 64-bit time_t, so we need a new layout for it. Since the
SNDRV_PCM_IOCTL_SYNC_PTR ioctl combines it with snd_pcm_mmap_control
into snd_pcm_sync_ptr, we need to change those two as well.
Both structures are also exported via an mmap() operation on certain
architectures, and this suffers from incompatibility between 32-bit
and 64-bit user space. As we have to change both structures anyway,
this is a good opportunity to fix the mmap() problem as well, so let's
standardize on the existing 64-bit layout of the structure where possible.
The downside is that we lose mmap() support for existing 32-bit x86 and
powerpc applications, adding that would introduce very noticeable runtime
overhead and complexity. My assumption here is that not too many people
will miss the removed feature, given that:
- Almost all x86 and powerpc users these days are on 64-bit kernels,
the majority of today's 32-bit users are on architectures that never
supported mmap (ARM, MIPS, ...).
- It never worked in compat mode (it was intentionally disabled there)
- The application already needs to work with a fallback to
SNDRV_PCM_IOCTL_SYNC_PTR, which will keep working with both the old
and new structure layout.
Both the ioctl() and mmap() based interfaces are changed at the same
time, as they are based on the same structures. Unlike other interfaces,
we change the uapi header to export both the traditional structure and
a version that is portable between 32-bit and 64-bit user space code
and that corresponds to the existing 64-bit layout. We further check the
__USE_TIME_BITS64 macro that will be defined by future C library versions
whenever we use the new time_t definition, so any existing user space
source code will not see any changes until it gets rebuilt against a new
C library. However, the new structures are all visible in addition to the
old ones, allowing applications to explicitly request the new structures.
In order to detect the difference between the old snd_pcm_mmap_status and
the new __snd_pcm_mmap_status64 structure from the ioctl command number,
we rely on one quirk in the structure definition: snd_pcm_mmap_status
must be aligned to alignof(time_t), which leads the compiler to insert
four bytes of padding in struct snd_pcm_sync_ptr after 'flags' and a
corresponding change in the size of snd_pcm_sync_ptr itself. On x86-32
(and only there), the compiler doesn't use 64-bit alignment in structure,
so I'm adding an explicit pad in the structure that has no effect on the
existing 64-bit architectures but ensures that the layout matches for x86.
The snd_pcm_uframes_t type compatibility requires another hack: we can't
easily make that 64 bit wide, so I leave the type as 'unsigned long',
but add padding before and after it, to ensure that the data is properly
aligned to the respective 64-bit field in the in-kernel structure.
For the SNDRV_PCM_MMAP_OFFSET_STATUS/CONTROL constants that are used
as the virtual file offset in the mmap() function, we also have to
introduce new constants that depend on hte __USE_TIME_BITS64 macro:
The existing macros are renamed to SNDRV_PCM_MMAP_OFFSET_STATUS_OLD
and SNDRV_PCM_MMAP_OFFSET_CONTROL_OLD, they continue to work fine on
64-bit architectures, but stop working on native 32-bit user space.
The replacement _NEW constants are now used by default for user space
built with __USE_TIME_BITS64, those now work on all new kernels for x86,
ppc and alpha (32 and 64 bit, native and compat). It might be a good idea
for a future alsa-lib to support both the _OLD and _NEW macros and use
the corresponding structures directly. Unmodified alsa-lib source code
will retain the current behavior, so it will no longer be able to use
mmap() for the status/control structures on 32-bit systems, until either
the C library gets updated to 64-bit time_t or alsa-lib gets updated to
support both mmap() layouts.
Co-developed-with: Baolin Wang <baolin.wang@linaro.org>
Signed-off-by: Baolin Wang <baolin.wang@linaro.org>
Signed-off-by: Arnd Bergmann <arnd@arndb.de>
2018-04-24 20:06:15 +08:00
|
|
|
case __SNDRV_PCM_IOCTL_SYNC_PTR32:
|
|
|
|
return snd_pcm_ioctl_sync_ptr_compat(substream, arg);
|
|
|
|
case __SNDRV_PCM_IOCTL_SYNC_PTR64:
|
2005-04-17 06:20:36 +08:00
|
|
|
return snd_pcm_sync_ptr(substream, arg);
|
2005-12-01 17:51:58 +08:00
|
|
|
#ifdef CONFIG_SND_SUPPORT_OLD_API
|
2005-04-17 06:20:36 +08:00
|
|
|
case SNDRV_PCM_IOCTL_HW_REFINE_OLD:
|
|
|
|
return snd_pcm_hw_refine_old_user(substream, arg);
|
|
|
|
case SNDRV_PCM_IOCTL_HW_PARAMS_OLD:
|
|
|
|
return snd_pcm_hw_params_old_user(substream, arg);
|
2005-12-01 17:51:58 +08:00
|
|
|
#endif
|
2005-04-17 06:20:36 +08:00
|
|
|
case SNDRV_PCM_IOCTL_DRAIN:
|
2009-08-20 22:40:16 +08:00
|
|
|
return snd_pcm_drain(substream, file);
|
2005-04-17 06:20:36 +08:00
|
|
|
case SNDRV_PCM_IOCTL_DROP:
|
|
|
|
return snd_pcm_drop(substream);
|
2006-02-21 21:14:50 +08:00
|
|
|
case SNDRV_PCM_IOCTL_PAUSE:
|
2020-01-31 23:22:14 +08:00
|
|
|
return snd_pcm_pause_lock_irq(substream, (unsigned long)arg);
|
2005-04-17 06:20:36 +08:00
|
|
|
case SNDRV_PCM_IOCTL_WRITEI_FRAMES:
|
|
|
|
case SNDRV_PCM_IOCTL_READI_FRAMES:
|
2017-08-30 21:39:32 +08:00
|
|
|
return snd_pcm_xferi_frames_ioctl(substream, arg);
|
|
|
|
case SNDRV_PCM_IOCTL_WRITEN_FRAMES:
|
2005-04-17 06:20:36 +08:00
|
|
|
case SNDRV_PCM_IOCTL_READN_FRAMES:
|
2017-08-30 21:39:32 +08:00
|
|
|
return snd_pcm_xfern_frames_ioctl(substream, arg);
|
2005-04-17 06:20:36 +08:00
|
|
|
case SNDRV_PCM_IOCTL_REWIND:
|
2017-08-30 21:39:32 +08:00
|
|
|
return snd_pcm_rewind_ioctl(substream, arg);
|
2005-04-17 06:20:36 +08:00
|
|
|
case SNDRV_PCM_IOCTL_FORWARD:
|
2017-08-30 21:39:32 +08:00
|
|
|
return snd_pcm_forward_ioctl(substream, arg);
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
2017-08-30 21:39:32 +08:00
|
|
|
pcm_dbg(substream->pcm, "unknown ioctl = 0x%x\n", cmd);
|
|
|
|
return -ENOTTY;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
2017-08-30 21:39:32 +08:00
|
|
|
static long snd_pcm_ioctl(struct file *file, unsigned int cmd,
|
|
|
|
unsigned long arg)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2005-11-17 20:59:38 +08:00
|
|
|
struct snd_pcm_file *pcm_file;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
pcm_file = file->private_data;
|
|
|
|
|
|
|
|
if (((cmd >> 8) & 0xff) != 'A')
|
|
|
|
return -ENOTTY;
|
|
|
|
|
2017-08-30 21:39:32 +08:00
|
|
|
return snd_pcm_common_ioctl(file, pcm_file->substream, cmd,
|
|
|
|
(void __user *)arg);
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
2017-05-10 20:33:44 +08:00
|
|
|
/**
|
|
|
|
* snd_pcm_kernel_ioctl - Execute PCM ioctl in the kernel-space
|
|
|
|
* @substream: PCM substream
|
|
|
|
* @cmd: IOCTL cmd
|
|
|
|
* @arg: IOCTL argument
|
|
|
|
*
|
|
|
|
* The function is provided primarily for OSS layer and USB gadget drivers,
|
|
|
|
* and it allows only the limited set of ioctls (hw_params, sw_params,
|
|
|
|
* prepare, start, drain, drop, forward).
|
|
|
|
*/
|
2006-03-27 22:22:45 +08:00
|
|
|
int snd_pcm_kernel_ioctl(struct snd_pcm_substream *substream,
|
|
|
|
unsigned int cmd, void *arg)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2017-05-10 20:33:44 +08:00
|
|
|
snd_pcm_uframes_t *frames = arg;
|
|
|
|
snd_pcm_sframes_t result;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2017-05-10 20:33:44 +08:00
|
|
|
switch (cmd) {
|
|
|
|
case SNDRV_PCM_IOCTL_FORWARD:
|
|
|
|
{
|
|
|
|
/* provided only for OSS; capture-only and no value returned */
|
|
|
|
if (substream->stream != SNDRV_PCM_STREAM_CAPTURE)
|
|
|
|
return -EINVAL;
|
2018-04-11 23:56:52 +08:00
|
|
|
result = snd_pcm_forward(substream, *frames);
|
2017-05-10 20:33:44 +08:00
|
|
|
return result < 0 ? result : 0;
|
|
|
|
}
|
|
|
|
case SNDRV_PCM_IOCTL_HW_PARAMS:
|
|
|
|
return snd_pcm_hw_params(substream, arg);
|
|
|
|
case SNDRV_PCM_IOCTL_SW_PARAMS:
|
|
|
|
return snd_pcm_sw_params(substream, arg);
|
|
|
|
case SNDRV_PCM_IOCTL_PREPARE:
|
|
|
|
return snd_pcm_prepare(substream, NULL);
|
|
|
|
case SNDRV_PCM_IOCTL_START:
|
|
|
|
return snd_pcm_start_lock_irq(substream);
|
|
|
|
case SNDRV_PCM_IOCTL_DRAIN:
|
2017-08-30 22:13:25 +08:00
|
|
|
return snd_pcm_drain(substream, NULL);
|
2017-05-10 20:33:44 +08:00
|
|
|
case SNDRV_PCM_IOCTL_DROP:
|
|
|
|
return snd_pcm_drop(substream);
|
|
|
|
case SNDRV_PCM_IOCTL_DELAY:
|
2018-04-21 12:20:46 +08:00
|
|
|
return snd_pcm_delay(substream, frames);
|
2005-04-17 06:20:36 +08:00
|
|
|
default:
|
2017-05-10 20:33:44 +08:00
|
|
|
return -EINVAL;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
}
|
2006-04-28 21:13:40 +08:00
|
|
|
EXPORT_SYMBOL(snd_pcm_kernel_ioctl);
|
|
|
|
|
2005-11-17 20:59:38 +08:00
|
|
|
static ssize_t snd_pcm_read(struct file *file, char __user *buf, size_t count,
|
|
|
|
loff_t * offset)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2005-11-17 20:59:38 +08:00
|
|
|
struct snd_pcm_file *pcm_file;
|
|
|
|
struct snd_pcm_substream *substream;
|
|
|
|
struct snd_pcm_runtime *runtime;
|
2005-04-17 06:20:36 +08:00
|
|
|
snd_pcm_sframes_t result;
|
|
|
|
|
|
|
|
pcm_file = file->private_data;
|
|
|
|
substream = pcm_file->substream;
|
2008-08-08 23:09:09 +08:00
|
|
|
if (PCM_RUNTIME_CHECK(substream))
|
|
|
|
return -ENXIO;
|
2005-04-17 06:20:36 +08:00
|
|
|
runtime = substream->runtime;
|
|
|
|
if (runtime->status->state == SNDRV_PCM_STATE_OPEN)
|
|
|
|
return -EBADFD;
|
|
|
|
if (!frame_aligned(runtime, count))
|
|
|
|
return -EINVAL;
|
|
|
|
count = bytes_to_frames(runtime, count);
|
|
|
|
result = snd_pcm_lib_read(substream, buf, count);
|
|
|
|
if (result > 0)
|
|
|
|
result = frames_to_bytes(runtime, result);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2005-11-17 20:59:38 +08:00
|
|
|
static ssize_t snd_pcm_write(struct file *file, const char __user *buf,
|
|
|
|
size_t count, loff_t * offset)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2005-11-17 20:59:38 +08:00
|
|
|
struct snd_pcm_file *pcm_file;
|
|
|
|
struct snd_pcm_substream *substream;
|
|
|
|
struct snd_pcm_runtime *runtime;
|
2005-04-17 06:20:36 +08:00
|
|
|
snd_pcm_sframes_t result;
|
|
|
|
|
|
|
|
pcm_file = file->private_data;
|
|
|
|
substream = pcm_file->substream;
|
2008-08-08 23:09:09 +08:00
|
|
|
if (PCM_RUNTIME_CHECK(substream))
|
|
|
|
return -ENXIO;
|
2005-04-17 06:20:36 +08:00
|
|
|
runtime = substream->runtime;
|
2008-08-08 23:09:09 +08:00
|
|
|
if (runtime->status->state == SNDRV_PCM_STATE_OPEN)
|
|
|
|
return -EBADFD;
|
|
|
|
if (!frame_aligned(runtime, count))
|
|
|
|
return -EINVAL;
|
2005-04-17 06:20:36 +08:00
|
|
|
count = bytes_to_frames(runtime, count);
|
|
|
|
result = snd_pcm_lib_write(substream, buf, count);
|
|
|
|
if (result > 0)
|
|
|
|
result = frames_to_bytes(runtime, result);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2015-04-04 12:19:32 +08:00
|
|
|
static ssize_t snd_pcm_readv(struct kiocb *iocb, struct iov_iter *to)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2005-11-17 20:59:38 +08:00
|
|
|
struct snd_pcm_file *pcm_file;
|
|
|
|
struct snd_pcm_substream *substream;
|
|
|
|
struct snd_pcm_runtime *runtime;
|
2005-04-17 06:20:36 +08:00
|
|
|
snd_pcm_sframes_t result;
|
|
|
|
unsigned long i;
|
|
|
|
void __user **bufs;
|
|
|
|
snd_pcm_uframes_t frames;
|
|
|
|
|
2006-10-01 14:28:47 +08:00
|
|
|
pcm_file = iocb->ki_filp->private_data;
|
2005-04-17 06:20:36 +08:00
|
|
|
substream = pcm_file->substream;
|
2008-08-08 23:09:09 +08:00
|
|
|
if (PCM_RUNTIME_CHECK(substream))
|
|
|
|
return -ENXIO;
|
2005-04-17 06:20:36 +08:00
|
|
|
runtime = substream->runtime;
|
|
|
|
if (runtime->status->state == SNDRV_PCM_STATE_OPEN)
|
|
|
|
return -EBADFD;
|
2015-04-04 12:19:32 +08:00
|
|
|
if (!iter_is_iovec(to))
|
|
|
|
return -EINVAL;
|
|
|
|
if (to->nr_segs > 1024 || to->nr_segs != runtime->channels)
|
2005-04-17 06:20:36 +08:00
|
|
|
return -EINVAL;
|
2015-04-04 12:19:32 +08:00
|
|
|
if (!frame_aligned(runtime, to->iov->iov_len))
|
2005-04-17 06:20:36 +08:00
|
|
|
return -EINVAL;
|
2015-04-04 12:19:32 +08:00
|
|
|
frames = bytes_to_samples(runtime, to->iov->iov_len);
|
treewide: kmalloc() -> kmalloc_array()
The kmalloc() function has a 2-factor argument form, kmalloc_array(). This
patch replaces cases of:
kmalloc(a * b, gfp)
with:
kmalloc_array(a * b, gfp)
as well as handling cases of:
kmalloc(a * b * c, gfp)
with:
kmalloc(array3_size(a, b, c), gfp)
as it's slightly less ugly than:
kmalloc_array(array_size(a, b), c, gfp)
This does, however, attempt to ignore constant size factors like:
kmalloc(4 * 1024, gfp)
though any constants defined via macros get caught up in the conversion.
Any factors with a sizeof() of "unsigned char", "char", and "u8" were
dropped, since they're redundant.
The tools/ directory was manually excluded, since it has its own
implementation of kmalloc().
The Coccinelle script used for this was:
// Fix redundant parens around sizeof().
@@
type TYPE;
expression THING, E;
@@
(
kmalloc(
- (sizeof(TYPE)) * E
+ sizeof(TYPE) * E
, ...)
|
kmalloc(
- (sizeof(THING)) * E
+ sizeof(THING) * E
, ...)
)
// Drop single-byte sizes and redundant parens.
@@
expression COUNT;
typedef u8;
typedef __u8;
@@
(
kmalloc(
- sizeof(u8) * (COUNT)
+ COUNT
, ...)
|
kmalloc(
- sizeof(__u8) * (COUNT)
+ COUNT
, ...)
|
kmalloc(
- sizeof(char) * (COUNT)
+ COUNT
, ...)
|
kmalloc(
- sizeof(unsigned char) * (COUNT)
+ COUNT
, ...)
|
kmalloc(
- sizeof(u8) * COUNT
+ COUNT
, ...)
|
kmalloc(
- sizeof(__u8) * COUNT
+ COUNT
, ...)
|
kmalloc(
- sizeof(char) * COUNT
+ COUNT
, ...)
|
kmalloc(
- sizeof(unsigned char) * COUNT
+ COUNT
, ...)
)
// 2-factor product with sizeof(type/expression) and identifier or constant.
@@
type TYPE;
expression THING;
identifier COUNT_ID;
constant COUNT_CONST;
@@
(
- kmalloc
+ kmalloc_array
(
- sizeof(TYPE) * (COUNT_ID)
+ COUNT_ID, sizeof(TYPE)
, ...)
|
- kmalloc
+ kmalloc_array
(
- sizeof(TYPE) * COUNT_ID
+ COUNT_ID, sizeof(TYPE)
, ...)
|
- kmalloc
+ kmalloc_array
(
- sizeof(TYPE) * (COUNT_CONST)
+ COUNT_CONST, sizeof(TYPE)
, ...)
|
- kmalloc
+ kmalloc_array
(
- sizeof(TYPE) * COUNT_CONST
+ COUNT_CONST, sizeof(TYPE)
, ...)
|
- kmalloc
+ kmalloc_array
(
- sizeof(THING) * (COUNT_ID)
+ COUNT_ID, sizeof(THING)
, ...)
|
- kmalloc
+ kmalloc_array
(
- sizeof(THING) * COUNT_ID
+ COUNT_ID, sizeof(THING)
, ...)
|
- kmalloc
+ kmalloc_array
(
- sizeof(THING) * (COUNT_CONST)
+ COUNT_CONST, sizeof(THING)
, ...)
|
- kmalloc
+ kmalloc_array
(
- sizeof(THING) * COUNT_CONST
+ COUNT_CONST, sizeof(THING)
, ...)
)
// 2-factor product, only identifiers.
@@
identifier SIZE, COUNT;
@@
- kmalloc
+ kmalloc_array
(
- SIZE * COUNT
+ COUNT, SIZE
, ...)
// 3-factor product with 1 sizeof(type) or sizeof(expression), with
// redundant parens removed.
@@
expression THING;
identifier STRIDE, COUNT;
type TYPE;
@@
(
kmalloc(
- sizeof(TYPE) * (COUNT) * (STRIDE)
+ array3_size(COUNT, STRIDE, sizeof(TYPE))
, ...)
|
kmalloc(
- sizeof(TYPE) * (COUNT) * STRIDE
+ array3_size(COUNT, STRIDE, sizeof(TYPE))
, ...)
|
kmalloc(
- sizeof(TYPE) * COUNT * (STRIDE)
+ array3_size(COUNT, STRIDE, sizeof(TYPE))
, ...)
|
kmalloc(
- sizeof(TYPE) * COUNT * STRIDE
+ array3_size(COUNT, STRIDE, sizeof(TYPE))
, ...)
|
kmalloc(
- sizeof(THING) * (COUNT) * (STRIDE)
+ array3_size(COUNT, STRIDE, sizeof(THING))
, ...)
|
kmalloc(
- sizeof(THING) * (COUNT) * STRIDE
+ array3_size(COUNT, STRIDE, sizeof(THING))
, ...)
|
kmalloc(
- sizeof(THING) * COUNT * (STRIDE)
+ array3_size(COUNT, STRIDE, sizeof(THING))
, ...)
|
kmalloc(
- sizeof(THING) * COUNT * STRIDE
+ array3_size(COUNT, STRIDE, sizeof(THING))
, ...)
)
// 3-factor product with 2 sizeof(variable), with redundant parens removed.
@@
expression THING1, THING2;
identifier COUNT;
type TYPE1, TYPE2;
@@
(
kmalloc(
- sizeof(TYPE1) * sizeof(TYPE2) * COUNT
+ array3_size(COUNT, sizeof(TYPE1), sizeof(TYPE2))
, ...)
|
kmalloc(
- sizeof(TYPE1) * sizeof(THING2) * (COUNT)
+ array3_size(COUNT, sizeof(TYPE1), sizeof(TYPE2))
, ...)
|
kmalloc(
- sizeof(THING1) * sizeof(THING2) * COUNT
+ array3_size(COUNT, sizeof(THING1), sizeof(THING2))
, ...)
|
kmalloc(
- sizeof(THING1) * sizeof(THING2) * (COUNT)
+ array3_size(COUNT, sizeof(THING1), sizeof(THING2))
, ...)
|
kmalloc(
- sizeof(TYPE1) * sizeof(THING2) * COUNT
+ array3_size(COUNT, sizeof(TYPE1), sizeof(THING2))
, ...)
|
kmalloc(
- sizeof(TYPE1) * sizeof(THING2) * (COUNT)
+ array3_size(COUNT, sizeof(TYPE1), sizeof(THING2))
, ...)
)
// 3-factor product, only identifiers, with redundant parens removed.
@@
identifier STRIDE, SIZE, COUNT;
@@
(
kmalloc(
- (COUNT) * STRIDE * SIZE
+ array3_size(COUNT, STRIDE, SIZE)
, ...)
|
kmalloc(
- COUNT * (STRIDE) * SIZE
+ array3_size(COUNT, STRIDE, SIZE)
, ...)
|
kmalloc(
- COUNT * STRIDE * (SIZE)
+ array3_size(COUNT, STRIDE, SIZE)
, ...)
|
kmalloc(
- (COUNT) * (STRIDE) * SIZE
+ array3_size(COUNT, STRIDE, SIZE)
, ...)
|
kmalloc(
- COUNT * (STRIDE) * (SIZE)
+ array3_size(COUNT, STRIDE, SIZE)
, ...)
|
kmalloc(
- (COUNT) * STRIDE * (SIZE)
+ array3_size(COUNT, STRIDE, SIZE)
, ...)
|
kmalloc(
- (COUNT) * (STRIDE) * (SIZE)
+ array3_size(COUNT, STRIDE, SIZE)
, ...)
|
kmalloc(
- COUNT * STRIDE * SIZE
+ array3_size(COUNT, STRIDE, SIZE)
, ...)
)
// Any remaining multi-factor products, first at least 3-factor products,
// when they're not all constants...
@@
expression E1, E2, E3;
constant C1, C2, C3;
@@
(
kmalloc(C1 * C2 * C3, ...)
|
kmalloc(
- (E1) * E2 * E3
+ array3_size(E1, E2, E3)
, ...)
|
kmalloc(
- (E1) * (E2) * E3
+ array3_size(E1, E2, E3)
, ...)
|
kmalloc(
- (E1) * (E2) * (E3)
+ array3_size(E1, E2, E3)
, ...)
|
kmalloc(
- E1 * E2 * E3
+ array3_size(E1, E2, E3)
, ...)
)
// And then all remaining 2 factors products when they're not all constants,
// keeping sizeof() as the second factor argument.
@@
expression THING, E1, E2;
type TYPE;
constant C1, C2, C3;
@@
(
kmalloc(sizeof(THING) * C2, ...)
|
kmalloc(sizeof(TYPE) * C2, ...)
|
kmalloc(C1 * C2 * C3, ...)
|
kmalloc(C1 * C2, ...)
|
- kmalloc
+ kmalloc_array
(
- sizeof(TYPE) * (E2)
+ E2, sizeof(TYPE)
, ...)
|
- kmalloc
+ kmalloc_array
(
- sizeof(TYPE) * E2
+ E2, sizeof(TYPE)
, ...)
|
- kmalloc
+ kmalloc_array
(
- sizeof(THING) * (E2)
+ E2, sizeof(THING)
, ...)
|
- kmalloc
+ kmalloc_array
(
- sizeof(THING) * E2
+ E2, sizeof(THING)
, ...)
|
- kmalloc
+ kmalloc_array
(
- (E1) * E2
+ E1, E2
, ...)
|
- kmalloc
+ kmalloc_array
(
- (E1) * (E2)
+ E1, E2
, ...)
|
- kmalloc
+ kmalloc_array
(
- E1 * E2
+ E1, E2
, ...)
)
Signed-off-by: Kees Cook <keescook@chromium.org>
2018-06-13 04:55:00 +08:00
|
|
|
bufs = kmalloc_array(to->nr_segs, sizeof(void *), GFP_KERNEL);
|
2005-04-17 06:20:36 +08:00
|
|
|
if (bufs == NULL)
|
|
|
|
return -ENOMEM;
|
2015-04-04 12:19:32 +08:00
|
|
|
for (i = 0; i < to->nr_segs; ++i)
|
|
|
|
bufs[i] = to->iov[i].iov_base;
|
2005-04-17 06:20:36 +08:00
|
|
|
result = snd_pcm_lib_readv(substream, bufs, frames);
|
|
|
|
if (result > 0)
|
|
|
|
result = frames_to_bytes(runtime, result);
|
|
|
|
kfree(bufs);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2015-04-04 12:19:32 +08:00
|
|
|
static ssize_t snd_pcm_writev(struct kiocb *iocb, struct iov_iter *from)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2005-11-17 20:59:38 +08:00
|
|
|
struct snd_pcm_file *pcm_file;
|
|
|
|
struct snd_pcm_substream *substream;
|
|
|
|
struct snd_pcm_runtime *runtime;
|
2005-04-17 06:20:36 +08:00
|
|
|
snd_pcm_sframes_t result;
|
|
|
|
unsigned long i;
|
|
|
|
void __user **bufs;
|
|
|
|
snd_pcm_uframes_t frames;
|
|
|
|
|
2006-10-01 14:28:47 +08:00
|
|
|
pcm_file = iocb->ki_filp->private_data;
|
2005-04-17 06:20:36 +08:00
|
|
|
substream = pcm_file->substream;
|
2008-08-08 23:09:09 +08:00
|
|
|
if (PCM_RUNTIME_CHECK(substream))
|
|
|
|
return -ENXIO;
|
2005-04-17 06:20:36 +08:00
|
|
|
runtime = substream->runtime;
|
2008-08-08 23:09:09 +08:00
|
|
|
if (runtime->status->state == SNDRV_PCM_STATE_OPEN)
|
|
|
|
return -EBADFD;
|
2015-04-04 12:19:32 +08:00
|
|
|
if (!iter_is_iovec(from))
|
|
|
|
return -EINVAL;
|
|
|
|
if (from->nr_segs > 128 || from->nr_segs != runtime->channels ||
|
|
|
|
!frame_aligned(runtime, from->iov->iov_len))
|
2008-08-08 23:09:09 +08:00
|
|
|
return -EINVAL;
|
2015-04-04 12:19:32 +08:00
|
|
|
frames = bytes_to_samples(runtime, from->iov->iov_len);
|
treewide: kmalloc() -> kmalloc_array()
The kmalloc() function has a 2-factor argument form, kmalloc_array(). This
patch replaces cases of:
kmalloc(a * b, gfp)
with:
kmalloc_array(a * b, gfp)
as well as handling cases of:
kmalloc(a * b * c, gfp)
with:
kmalloc(array3_size(a, b, c), gfp)
as it's slightly less ugly than:
kmalloc_array(array_size(a, b), c, gfp)
This does, however, attempt to ignore constant size factors like:
kmalloc(4 * 1024, gfp)
though any constants defined via macros get caught up in the conversion.
Any factors with a sizeof() of "unsigned char", "char", and "u8" were
dropped, since they're redundant.
The tools/ directory was manually excluded, since it has its own
implementation of kmalloc().
The Coccinelle script used for this was:
// Fix redundant parens around sizeof().
@@
type TYPE;
expression THING, E;
@@
(
kmalloc(
- (sizeof(TYPE)) * E
+ sizeof(TYPE) * E
, ...)
|
kmalloc(
- (sizeof(THING)) * E
+ sizeof(THING) * E
, ...)
)
// Drop single-byte sizes and redundant parens.
@@
expression COUNT;
typedef u8;
typedef __u8;
@@
(
kmalloc(
- sizeof(u8) * (COUNT)
+ COUNT
, ...)
|
kmalloc(
- sizeof(__u8) * (COUNT)
+ COUNT
, ...)
|
kmalloc(
- sizeof(char) * (COUNT)
+ COUNT
, ...)
|
kmalloc(
- sizeof(unsigned char) * (COUNT)
+ COUNT
, ...)
|
kmalloc(
- sizeof(u8) * COUNT
+ COUNT
, ...)
|
kmalloc(
- sizeof(__u8) * COUNT
+ COUNT
, ...)
|
kmalloc(
- sizeof(char) * COUNT
+ COUNT
, ...)
|
kmalloc(
- sizeof(unsigned char) * COUNT
+ COUNT
, ...)
)
// 2-factor product with sizeof(type/expression) and identifier or constant.
@@
type TYPE;
expression THING;
identifier COUNT_ID;
constant COUNT_CONST;
@@
(
- kmalloc
+ kmalloc_array
(
- sizeof(TYPE) * (COUNT_ID)
+ COUNT_ID, sizeof(TYPE)
, ...)
|
- kmalloc
+ kmalloc_array
(
- sizeof(TYPE) * COUNT_ID
+ COUNT_ID, sizeof(TYPE)
, ...)
|
- kmalloc
+ kmalloc_array
(
- sizeof(TYPE) * (COUNT_CONST)
+ COUNT_CONST, sizeof(TYPE)
, ...)
|
- kmalloc
+ kmalloc_array
(
- sizeof(TYPE) * COUNT_CONST
+ COUNT_CONST, sizeof(TYPE)
, ...)
|
- kmalloc
+ kmalloc_array
(
- sizeof(THING) * (COUNT_ID)
+ COUNT_ID, sizeof(THING)
, ...)
|
- kmalloc
+ kmalloc_array
(
- sizeof(THING) * COUNT_ID
+ COUNT_ID, sizeof(THING)
, ...)
|
- kmalloc
+ kmalloc_array
(
- sizeof(THING) * (COUNT_CONST)
+ COUNT_CONST, sizeof(THING)
, ...)
|
- kmalloc
+ kmalloc_array
(
- sizeof(THING) * COUNT_CONST
+ COUNT_CONST, sizeof(THING)
, ...)
)
// 2-factor product, only identifiers.
@@
identifier SIZE, COUNT;
@@
- kmalloc
+ kmalloc_array
(
- SIZE * COUNT
+ COUNT, SIZE
, ...)
// 3-factor product with 1 sizeof(type) or sizeof(expression), with
// redundant parens removed.
@@
expression THING;
identifier STRIDE, COUNT;
type TYPE;
@@
(
kmalloc(
- sizeof(TYPE) * (COUNT) * (STRIDE)
+ array3_size(COUNT, STRIDE, sizeof(TYPE))
, ...)
|
kmalloc(
- sizeof(TYPE) * (COUNT) * STRIDE
+ array3_size(COUNT, STRIDE, sizeof(TYPE))
, ...)
|
kmalloc(
- sizeof(TYPE) * COUNT * (STRIDE)
+ array3_size(COUNT, STRIDE, sizeof(TYPE))
, ...)
|
kmalloc(
- sizeof(TYPE) * COUNT * STRIDE
+ array3_size(COUNT, STRIDE, sizeof(TYPE))
, ...)
|
kmalloc(
- sizeof(THING) * (COUNT) * (STRIDE)
+ array3_size(COUNT, STRIDE, sizeof(THING))
, ...)
|
kmalloc(
- sizeof(THING) * (COUNT) * STRIDE
+ array3_size(COUNT, STRIDE, sizeof(THING))
, ...)
|
kmalloc(
- sizeof(THING) * COUNT * (STRIDE)
+ array3_size(COUNT, STRIDE, sizeof(THING))
, ...)
|
kmalloc(
- sizeof(THING) * COUNT * STRIDE
+ array3_size(COUNT, STRIDE, sizeof(THING))
, ...)
)
// 3-factor product with 2 sizeof(variable), with redundant parens removed.
@@
expression THING1, THING2;
identifier COUNT;
type TYPE1, TYPE2;
@@
(
kmalloc(
- sizeof(TYPE1) * sizeof(TYPE2) * COUNT
+ array3_size(COUNT, sizeof(TYPE1), sizeof(TYPE2))
, ...)
|
kmalloc(
- sizeof(TYPE1) * sizeof(THING2) * (COUNT)
+ array3_size(COUNT, sizeof(TYPE1), sizeof(TYPE2))
, ...)
|
kmalloc(
- sizeof(THING1) * sizeof(THING2) * COUNT
+ array3_size(COUNT, sizeof(THING1), sizeof(THING2))
, ...)
|
kmalloc(
- sizeof(THING1) * sizeof(THING2) * (COUNT)
+ array3_size(COUNT, sizeof(THING1), sizeof(THING2))
, ...)
|
kmalloc(
- sizeof(TYPE1) * sizeof(THING2) * COUNT
+ array3_size(COUNT, sizeof(TYPE1), sizeof(THING2))
, ...)
|
kmalloc(
- sizeof(TYPE1) * sizeof(THING2) * (COUNT)
+ array3_size(COUNT, sizeof(TYPE1), sizeof(THING2))
, ...)
)
// 3-factor product, only identifiers, with redundant parens removed.
@@
identifier STRIDE, SIZE, COUNT;
@@
(
kmalloc(
- (COUNT) * STRIDE * SIZE
+ array3_size(COUNT, STRIDE, SIZE)
, ...)
|
kmalloc(
- COUNT * (STRIDE) * SIZE
+ array3_size(COUNT, STRIDE, SIZE)
, ...)
|
kmalloc(
- COUNT * STRIDE * (SIZE)
+ array3_size(COUNT, STRIDE, SIZE)
, ...)
|
kmalloc(
- (COUNT) * (STRIDE) * SIZE
+ array3_size(COUNT, STRIDE, SIZE)
, ...)
|
kmalloc(
- COUNT * (STRIDE) * (SIZE)
+ array3_size(COUNT, STRIDE, SIZE)
, ...)
|
kmalloc(
- (COUNT) * STRIDE * (SIZE)
+ array3_size(COUNT, STRIDE, SIZE)
, ...)
|
kmalloc(
- (COUNT) * (STRIDE) * (SIZE)
+ array3_size(COUNT, STRIDE, SIZE)
, ...)
|
kmalloc(
- COUNT * STRIDE * SIZE
+ array3_size(COUNT, STRIDE, SIZE)
, ...)
)
// Any remaining multi-factor products, first at least 3-factor products,
// when they're not all constants...
@@
expression E1, E2, E3;
constant C1, C2, C3;
@@
(
kmalloc(C1 * C2 * C3, ...)
|
kmalloc(
- (E1) * E2 * E3
+ array3_size(E1, E2, E3)
, ...)
|
kmalloc(
- (E1) * (E2) * E3
+ array3_size(E1, E2, E3)
, ...)
|
kmalloc(
- (E1) * (E2) * (E3)
+ array3_size(E1, E2, E3)
, ...)
|
kmalloc(
- E1 * E2 * E3
+ array3_size(E1, E2, E3)
, ...)
)
// And then all remaining 2 factors products when they're not all constants,
// keeping sizeof() as the second factor argument.
@@
expression THING, E1, E2;
type TYPE;
constant C1, C2, C3;
@@
(
kmalloc(sizeof(THING) * C2, ...)
|
kmalloc(sizeof(TYPE) * C2, ...)
|
kmalloc(C1 * C2 * C3, ...)
|
kmalloc(C1 * C2, ...)
|
- kmalloc
+ kmalloc_array
(
- sizeof(TYPE) * (E2)
+ E2, sizeof(TYPE)
, ...)
|
- kmalloc
+ kmalloc_array
(
- sizeof(TYPE) * E2
+ E2, sizeof(TYPE)
, ...)
|
- kmalloc
+ kmalloc_array
(
- sizeof(THING) * (E2)
+ E2, sizeof(THING)
, ...)
|
- kmalloc
+ kmalloc_array
(
- sizeof(THING) * E2
+ E2, sizeof(THING)
, ...)
|
- kmalloc
+ kmalloc_array
(
- (E1) * E2
+ E1, E2
, ...)
|
- kmalloc
+ kmalloc_array
(
- (E1) * (E2)
+ E1, E2
, ...)
|
- kmalloc
+ kmalloc_array
(
- E1 * E2
+ E1, E2
, ...)
)
Signed-off-by: Kees Cook <keescook@chromium.org>
2018-06-13 04:55:00 +08:00
|
|
|
bufs = kmalloc_array(from->nr_segs, sizeof(void *), GFP_KERNEL);
|
2005-04-17 06:20:36 +08:00
|
|
|
if (bufs == NULL)
|
|
|
|
return -ENOMEM;
|
2015-04-04 12:19:32 +08:00
|
|
|
for (i = 0; i < from->nr_segs; ++i)
|
|
|
|
bufs[i] = from->iov[i].iov_base;
|
2005-04-17 06:20:36 +08:00
|
|
|
result = snd_pcm_lib_writev(substream, bufs, frames);
|
|
|
|
if (result > 0)
|
|
|
|
result = frames_to_bytes(runtime, result);
|
|
|
|
kfree(bufs);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2018-04-12 00:05:00 +08:00
|
|
|
static __poll_t snd_pcm_poll(struct file *file, poll_table *wait)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2005-11-17 20:59:38 +08:00
|
|
|
struct snd_pcm_file *pcm_file;
|
|
|
|
struct snd_pcm_substream *substream;
|
|
|
|
struct snd_pcm_runtime *runtime;
|
2018-04-12 00:05:00 +08:00
|
|
|
__poll_t mask, ok;
|
2005-04-17 06:20:36 +08:00
|
|
|
snd_pcm_uframes_t avail;
|
|
|
|
|
|
|
|
pcm_file = file->private_data;
|
|
|
|
|
|
|
|
substream = pcm_file->substream;
|
2018-04-12 00:05:00 +08:00
|
|
|
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
|
|
|
ok = EPOLLOUT | EPOLLWRNORM;
|
|
|
|
else
|
|
|
|
ok = EPOLLIN | EPOLLRDNORM;
|
2008-08-08 23:09:09 +08:00
|
|
|
if (PCM_RUNTIME_CHECK(substream))
|
2018-04-12 00:05:00 +08:00
|
|
|
return ok | EPOLLERR;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
runtime = substream->runtime;
|
|
|
|
poll_wait(file, &runtime->sleep, wait);
|
|
|
|
|
2018-04-12 00:05:00 +08:00
|
|
|
mask = 0;
|
2005-04-17 06:20:36 +08:00
|
|
|
snd_pcm_stream_lock_irq(substream);
|
2018-04-12 00:05:00 +08:00
|
|
|
avail = snd_pcm_avail(substream);
|
2005-04-17 06:20:36 +08:00
|
|
|
switch (runtime->status->state) {
|
|
|
|
case SNDRV_PCM_STATE_RUNNING:
|
|
|
|
case SNDRV_PCM_STATE_PREPARED:
|
|
|
|
case SNDRV_PCM_STATE_PAUSED:
|
2018-04-12 00:05:00 +08:00
|
|
|
if (avail >= runtime->control->avail_min)
|
|
|
|
mask = ok;
|
2005-04-17 06:20:36 +08:00
|
|
|
break;
|
|
|
|
case SNDRV_PCM_STATE_DRAINING:
|
2018-04-12 00:05:00 +08:00
|
|
|
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
|
|
|
|
mask = ok;
|
|
|
|
if (!avail)
|
|
|
|
mask |= EPOLLERR;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
2018-04-12 00:05:00 +08:00
|
|
|
break;
|
2005-04-17 06:20:36 +08:00
|
|
|
default:
|
2018-04-12 00:05:00 +08:00
|
|
|
mask = ok | EPOLLERR;
|
2005-04-17 06:20:36 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
snd_pcm_stream_unlock_irq(substream);
|
|
|
|
return mask;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* mmap support
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Only on coherent architectures, we can mmap the status and the control records
|
|
|
|
* for effcient data transfer. On others, we have to use HWSYNC ioctl...
|
|
|
|
*/
|
|
|
|
#if defined(CONFIG_X86) || defined(CONFIG_PPC) || defined(CONFIG_ALPHA)
|
|
|
|
/*
|
|
|
|
* mmap status record
|
|
|
|
*/
|
2018-04-25 12:20:29 +08:00
|
|
|
static vm_fault_t snd_pcm_mmap_status_fault(struct vm_fault *vmf)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2017-02-25 06:56:41 +08:00
|
|
|
struct snd_pcm_substream *substream = vmf->vma->vm_private_data;
|
2005-11-17 20:59:38 +08:00
|
|
|
struct snd_pcm_runtime *runtime;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
if (substream == NULL)
|
2007-12-13 23:15:00 +08:00
|
|
|
return VM_FAULT_SIGBUS;
|
2005-04-17 06:20:36 +08:00
|
|
|
runtime = substream->runtime;
|
2007-12-13 23:15:00 +08:00
|
|
|
vmf->page = virt_to_page(runtime->status);
|
|
|
|
get_page(vmf->page);
|
|
|
|
return 0;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
2009-09-28 02:29:37 +08:00
|
|
|
static const struct vm_operations_struct snd_pcm_vm_ops_status =
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2007-12-13 23:15:00 +08:00
|
|
|
.fault = snd_pcm_mmap_status_fault,
|
2005-04-17 06:20:36 +08:00
|
|
|
};
|
|
|
|
|
2005-11-17 20:59:38 +08:00
|
|
|
static int snd_pcm_mmap_status(struct snd_pcm_substream *substream, struct file *file,
|
2005-04-17 06:20:36 +08:00
|
|
|
struct vm_area_struct *area)
|
|
|
|
{
|
|
|
|
long size;
|
|
|
|
if (!(area->vm_flags & VM_READ))
|
|
|
|
return -EINVAL;
|
|
|
|
size = area->vm_end - area->vm_start;
|
2005-11-17 20:59:38 +08:00
|
|
|
if (size != PAGE_ALIGN(sizeof(struct snd_pcm_mmap_status)))
|
2005-04-17 06:20:36 +08:00
|
|
|
return -EINVAL;
|
|
|
|
area->vm_ops = &snd_pcm_vm_ops_status;
|
|
|
|
area->vm_private_data = substream;
|
mm: kill vma flag VM_RESERVED and mm->reserved_vm counter
A long time ago, in v2.4, VM_RESERVED kept swapout process off VMA,
currently it lost original meaning but still has some effects:
| effect | alternative flags
-+------------------------+---------------------------------------------
1| account as reserved_vm | VM_IO
2| skip in core dump | VM_IO, VM_DONTDUMP
3| do not merge or expand | VM_IO, VM_DONTEXPAND, VM_HUGETLB, VM_PFNMAP
4| do not mlock | VM_IO, VM_DONTEXPAND, VM_HUGETLB, VM_PFNMAP
This patch removes reserved_vm counter from mm_struct. Seems like nobody
cares about it, it does not exported into userspace directly, it only
reduces total_vm showed in proc.
Thus VM_RESERVED can be replaced with VM_IO or pair VM_DONTEXPAND | VM_DONTDUMP.
remap_pfn_range() and io_remap_pfn_range() set VM_IO|VM_DONTEXPAND|VM_DONTDUMP.
remap_vmalloc_range() set VM_DONTEXPAND | VM_DONTDUMP.
[akpm@linux-foundation.org: drivers/vfio/pci/vfio_pci.c fixup]
Signed-off-by: Konstantin Khlebnikov <khlebnikov@openvz.org>
Cc: Alexander Viro <viro@zeniv.linux.org.uk>
Cc: Carsten Otte <cotte@de.ibm.com>
Cc: Chris Metcalf <cmetcalf@tilera.com>
Cc: Cyrill Gorcunov <gorcunov@openvz.org>
Cc: Eric Paris <eparis@redhat.com>
Cc: H. Peter Anvin <hpa@zytor.com>
Cc: Hugh Dickins <hughd@google.com>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: James Morris <james.l.morris@oracle.com>
Cc: Jason Baron <jbaron@redhat.com>
Cc: Kentaro Takeda <takedakn@nttdata.co.jp>
Cc: Matt Helsley <matthltc@us.ibm.com>
Cc: Nick Piggin <npiggin@kernel.dk>
Cc: Oleg Nesterov <oleg@redhat.com>
Cc: Peter Zijlstra <a.p.zijlstra@chello.nl>
Cc: Robert Richter <robert.richter@amd.com>
Cc: Suresh Siddha <suresh.b.siddha@intel.com>
Cc: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Cc: Venkatesh Pallipadi <venki@google.com>
Acked-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2012-10-09 07:29:02 +08:00
|
|
|
area->vm_flags |= VM_DONTEXPAND | VM_DONTDUMP;
|
2005-04-17 06:20:36 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* mmap control record
|
|
|
|
*/
|
2018-04-25 12:20:29 +08:00
|
|
|
static vm_fault_t snd_pcm_mmap_control_fault(struct vm_fault *vmf)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2017-02-25 06:56:41 +08:00
|
|
|
struct snd_pcm_substream *substream = vmf->vma->vm_private_data;
|
2005-11-17 20:59:38 +08:00
|
|
|
struct snd_pcm_runtime *runtime;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
if (substream == NULL)
|
2007-12-13 23:15:00 +08:00
|
|
|
return VM_FAULT_SIGBUS;
|
2005-04-17 06:20:36 +08:00
|
|
|
runtime = substream->runtime;
|
2007-12-13 23:15:00 +08:00
|
|
|
vmf->page = virt_to_page(runtime->control);
|
|
|
|
get_page(vmf->page);
|
|
|
|
return 0;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
2009-09-28 02:29:37 +08:00
|
|
|
static const struct vm_operations_struct snd_pcm_vm_ops_control =
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2007-12-13 23:15:00 +08:00
|
|
|
.fault = snd_pcm_mmap_control_fault,
|
2005-04-17 06:20:36 +08:00
|
|
|
};
|
|
|
|
|
2005-11-17 20:59:38 +08:00
|
|
|
static int snd_pcm_mmap_control(struct snd_pcm_substream *substream, struct file *file,
|
2005-04-17 06:20:36 +08:00
|
|
|
struct vm_area_struct *area)
|
|
|
|
{
|
|
|
|
long size;
|
|
|
|
if (!(area->vm_flags & VM_READ))
|
|
|
|
return -EINVAL;
|
|
|
|
size = area->vm_end - area->vm_start;
|
2005-11-17 20:59:38 +08:00
|
|
|
if (size != PAGE_ALIGN(sizeof(struct snd_pcm_mmap_control)))
|
2005-04-17 06:20:36 +08:00
|
|
|
return -EINVAL;
|
|
|
|
area->vm_ops = &snd_pcm_vm_ops_control;
|
|
|
|
area->vm_private_data = substream;
|
mm: kill vma flag VM_RESERVED and mm->reserved_vm counter
A long time ago, in v2.4, VM_RESERVED kept swapout process off VMA,
currently it lost original meaning but still has some effects:
| effect | alternative flags
-+------------------------+---------------------------------------------
1| account as reserved_vm | VM_IO
2| skip in core dump | VM_IO, VM_DONTDUMP
3| do not merge or expand | VM_IO, VM_DONTEXPAND, VM_HUGETLB, VM_PFNMAP
4| do not mlock | VM_IO, VM_DONTEXPAND, VM_HUGETLB, VM_PFNMAP
This patch removes reserved_vm counter from mm_struct. Seems like nobody
cares about it, it does not exported into userspace directly, it only
reduces total_vm showed in proc.
Thus VM_RESERVED can be replaced with VM_IO or pair VM_DONTEXPAND | VM_DONTDUMP.
remap_pfn_range() and io_remap_pfn_range() set VM_IO|VM_DONTEXPAND|VM_DONTDUMP.
remap_vmalloc_range() set VM_DONTEXPAND | VM_DONTDUMP.
[akpm@linux-foundation.org: drivers/vfio/pci/vfio_pci.c fixup]
Signed-off-by: Konstantin Khlebnikov <khlebnikov@openvz.org>
Cc: Alexander Viro <viro@zeniv.linux.org.uk>
Cc: Carsten Otte <cotte@de.ibm.com>
Cc: Chris Metcalf <cmetcalf@tilera.com>
Cc: Cyrill Gorcunov <gorcunov@openvz.org>
Cc: Eric Paris <eparis@redhat.com>
Cc: H. Peter Anvin <hpa@zytor.com>
Cc: Hugh Dickins <hughd@google.com>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: James Morris <james.l.morris@oracle.com>
Cc: Jason Baron <jbaron@redhat.com>
Cc: Kentaro Takeda <takedakn@nttdata.co.jp>
Cc: Matt Helsley <matthltc@us.ibm.com>
Cc: Nick Piggin <npiggin@kernel.dk>
Cc: Oleg Nesterov <oleg@redhat.com>
Cc: Peter Zijlstra <a.p.zijlstra@chello.nl>
Cc: Robert Richter <robert.richter@amd.com>
Cc: Suresh Siddha <suresh.b.siddha@intel.com>
Cc: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Cc: Venkatesh Pallipadi <venki@google.com>
Acked-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2012-10-09 07:29:02 +08:00
|
|
|
area->vm_flags |= VM_DONTEXPAND | VM_DONTDUMP;
|
2005-04-17 06:20:36 +08:00
|
|
|
return 0;
|
|
|
|
}
|
ALSA: pcm: Add the explicit appl_ptr sync support
Currently x86 platforms use the PCM status/control mmaps for
transferring the PCM status and appl_ptr between kernel and
user-spaces. The mmap is a most efficient way of communication, but
it has a drawback per its nature, namely, it can't notify the change
explicitly to kernel.
The lack of appl_ptr update notification is a problem on a few
existing drivers, but it's mostly a small issue and negligible.
However, a new type of driver that uses DSP for a deep buffer
management requires the exact position of appl_ptr for calculating the
buffer prefetch size, and the asynchronous appl_ptr update between
kernel and user-spaces becomes a significant problem for it.
How can we enforce user-space to report the appl_ptr update? The way
is relatively simple. Just by disabling the PCM control mmap, the
user-space is supposed to fall back to the mode using SYNC_PTR ioctl,
and the kernel gets control over that. This fallback mode is used in
all non-x86 platforms as default, and also in the 32bit compatible
model on all platforms including x86. It's been implemented already
over a decade, so we can say it's fairly safe and stably working.
With the help of the knowledge above, this patch introduces a new PCM
info flag SNDRV_PCM_INFO_SYNC_APPLPTR for achieving the appl_ptr sync
from user-space. When a driver sets this flag at open, the PCM status
/ control mmap is disabled, which effectively switches to SYNC_PTR
mode in user-space side.
In this version, both PCM status and control mmaps are disabled
although only the latter, control mmap, is the target. It's because
the current alsa-lib implementation supposes that both status and
control mmaps are always coupled, thus it handles a fatal error when
only one of them fails.
Of course, the disablement of the status/control mmaps may bring a
slight performance overhead. Thus, as of now, this should be used
only for the dedicated devices that deserves.
Note that the disablement of mmap is a sort of workaround. In the
later patch, we'll introduce the way to identify the protocol version
alsa-lib supports, and keep mmap working while the sync_ptr is
performed together.
Reviewed-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2017-06-20 04:39:18 +08:00
|
|
|
|
|
|
|
static bool pcm_status_mmap_allowed(struct snd_pcm_file *pcm_file)
|
|
|
|
{
|
2017-06-27 17:54:37 +08:00
|
|
|
/* See pcm_control_mmap_allowed() below.
|
|
|
|
* Since older alsa-lib requires both status and control mmaps to be
|
|
|
|
* coupled, we have to disable the status mmap for old alsa-lib, too.
|
|
|
|
*/
|
|
|
|
if (pcm_file->user_pversion < SNDRV_PROTOCOL_VERSION(2, 0, 14) &&
|
|
|
|
(pcm_file->substream->runtime->hw.info & SNDRV_PCM_INFO_SYNC_APPLPTR))
|
|
|
|
return false;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool pcm_control_mmap_allowed(struct snd_pcm_file *pcm_file)
|
|
|
|
{
|
|
|
|
if (pcm_file->no_compat_mmap)
|
|
|
|
return false;
|
|
|
|
/* Disallow the control mmap when SYNC_APPLPTR flag is set;
|
ALSA: pcm: Add the explicit appl_ptr sync support
Currently x86 platforms use the PCM status/control mmaps for
transferring the PCM status and appl_ptr between kernel and
user-spaces. The mmap is a most efficient way of communication, but
it has a drawback per its nature, namely, it can't notify the change
explicitly to kernel.
The lack of appl_ptr update notification is a problem on a few
existing drivers, but it's mostly a small issue and negligible.
However, a new type of driver that uses DSP for a deep buffer
management requires the exact position of appl_ptr for calculating the
buffer prefetch size, and the asynchronous appl_ptr update between
kernel and user-spaces becomes a significant problem for it.
How can we enforce user-space to report the appl_ptr update? The way
is relatively simple. Just by disabling the PCM control mmap, the
user-space is supposed to fall back to the mode using SYNC_PTR ioctl,
and the kernel gets control over that. This fallback mode is used in
all non-x86 platforms as default, and also in the 32bit compatible
model on all platforms including x86. It's been implemented already
over a decade, so we can say it's fairly safe and stably working.
With the help of the knowledge above, this patch introduces a new PCM
info flag SNDRV_PCM_INFO_SYNC_APPLPTR for achieving the appl_ptr sync
from user-space. When a driver sets this flag at open, the PCM status
/ control mmap is disabled, which effectively switches to SYNC_PTR
mode in user-space side.
In this version, both PCM status and control mmaps are disabled
although only the latter, control mmap, is the target. It's because
the current alsa-lib implementation supposes that both status and
control mmaps are always coupled, thus it handles a fatal error when
only one of them fails.
Of course, the disablement of the status/control mmaps may bring a
slight performance overhead. Thus, as of now, this should be used
only for the dedicated devices that deserves.
Note that the disablement of mmap is a sort of workaround. In the
later patch, we'll introduce the way to identify the protocol version
alsa-lib supports, and keep mmap working while the sync_ptr is
performed together.
Reviewed-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2017-06-20 04:39:18 +08:00
|
|
|
* it enforces the user-space to fall back to snd_pcm_sync_ptr(),
|
|
|
|
* thus it effectively assures the manual update of appl_ptr.
|
|
|
|
*/
|
|
|
|
if (pcm_file->substream->runtime->hw.info & SNDRV_PCM_INFO_SYNC_APPLPTR)
|
|
|
|
return false;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
#else /* ! coherent mmap */
|
|
|
|
/*
|
|
|
|
* don't support mmap for status and control records.
|
|
|
|
*/
|
ALSA: pcm: Add the explicit appl_ptr sync support
Currently x86 platforms use the PCM status/control mmaps for
transferring the PCM status and appl_ptr between kernel and
user-spaces. The mmap is a most efficient way of communication, but
it has a drawback per its nature, namely, it can't notify the change
explicitly to kernel.
The lack of appl_ptr update notification is a problem on a few
existing drivers, but it's mostly a small issue and negligible.
However, a new type of driver that uses DSP for a deep buffer
management requires the exact position of appl_ptr for calculating the
buffer prefetch size, and the asynchronous appl_ptr update between
kernel and user-spaces becomes a significant problem for it.
How can we enforce user-space to report the appl_ptr update? The way
is relatively simple. Just by disabling the PCM control mmap, the
user-space is supposed to fall back to the mode using SYNC_PTR ioctl,
and the kernel gets control over that. This fallback mode is used in
all non-x86 platforms as default, and also in the 32bit compatible
model on all platforms including x86. It's been implemented already
over a decade, so we can say it's fairly safe and stably working.
With the help of the knowledge above, this patch introduces a new PCM
info flag SNDRV_PCM_INFO_SYNC_APPLPTR for achieving the appl_ptr sync
from user-space. When a driver sets this flag at open, the PCM status
/ control mmap is disabled, which effectively switches to SYNC_PTR
mode in user-space side.
In this version, both PCM status and control mmaps are disabled
although only the latter, control mmap, is the target. It's because
the current alsa-lib implementation supposes that both status and
control mmaps are always coupled, thus it handles a fatal error when
only one of them fails.
Of course, the disablement of the status/control mmaps may bring a
slight performance overhead. Thus, as of now, this should be used
only for the dedicated devices that deserves.
Note that the disablement of mmap is a sort of workaround. In the
later patch, we'll introduce the way to identify the protocol version
alsa-lib supports, and keep mmap working while the sync_ptr is
performed together.
Reviewed-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2017-06-20 04:39:18 +08:00
|
|
|
#define pcm_status_mmap_allowed(pcm_file) false
|
2017-06-27 17:54:37 +08:00
|
|
|
#define pcm_control_mmap_allowed(pcm_file) false
|
ALSA: pcm: Add the explicit appl_ptr sync support
Currently x86 platforms use the PCM status/control mmaps for
transferring the PCM status and appl_ptr between kernel and
user-spaces. The mmap is a most efficient way of communication, but
it has a drawback per its nature, namely, it can't notify the change
explicitly to kernel.
The lack of appl_ptr update notification is a problem on a few
existing drivers, but it's mostly a small issue and negligible.
However, a new type of driver that uses DSP for a deep buffer
management requires the exact position of appl_ptr for calculating the
buffer prefetch size, and the asynchronous appl_ptr update between
kernel and user-spaces becomes a significant problem for it.
How can we enforce user-space to report the appl_ptr update? The way
is relatively simple. Just by disabling the PCM control mmap, the
user-space is supposed to fall back to the mode using SYNC_PTR ioctl,
and the kernel gets control over that. This fallback mode is used in
all non-x86 platforms as default, and also in the 32bit compatible
model on all platforms including x86. It's been implemented already
over a decade, so we can say it's fairly safe and stably working.
With the help of the knowledge above, this patch introduces a new PCM
info flag SNDRV_PCM_INFO_SYNC_APPLPTR for achieving the appl_ptr sync
from user-space. When a driver sets this flag at open, the PCM status
/ control mmap is disabled, which effectively switches to SYNC_PTR
mode in user-space side.
In this version, both PCM status and control mmaps are disabled
although only the latter, control mmap, is the target. It's because
the current alsa-lib implementation supposes that both status and
control mmaps are always coupled, thus it handles a fatal error when
only one of them fails.
Of course, the disablement of the status/control mmaps may bring a
slight performance overhead. Thus, as of now, this should be used
only for the dedicated devices that deserves.
Note that the disablement of mmap is a sort of workaround. In the
later patch, we'll introduce the way to identify the protocol version
alsa-lib supports, and keep mmap working while the sync_ptr is
performed together.
Reviewed-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2017-06-20 04:39:18 +08:00
|
|
|
|
2005-11-17 20:59:38 +08:00
|
|
|
static int snd_pcm_mmap_status(struct snd_pcm_substream *substream, struct file *file,
|
2005-04-17 06:20:36 +08:00
|
|
|
struct vm_area_struct *area)
|
|
|
|
{
|
|
|
|
return -ENXIO;
|
|
|
|
}
|
2005-11-17 20:59:38 +08:00
|
|
|
static int snd_pcm_mmap_control(struct snd_pcm_substream *substream, struct file *file,
|
2005-04-17 06:20:36 +08:00
|
|
|
struct vm_area_struct *area)
|
|
|
|
{
|
|
|
|
return -ENXIO;
|
|
|
|
}
|
|
|
|
#endif /* coherent mmap */
|
|
|
|
|
2009-11-26 19:43:39 +08:00
|
|
|
static inline struct page *
|
|
|
|
snd_pcm_default_page_ops(struct snd_pcm_substream *substream, unsigned long ofs)
|
|
|
|
{
|
|
|
|
void *vaddr = substream->runtime->dma_area + ofs;
|
2019-11-05 16:01:37 +08:00
|
|
|
|
|
|
|
switch (substream->dma_buffer.dev.type) {
|
|
|
|
#ifdef CONFIG_SND_DMA_SGBUF
|
|
|
|
case SNDRV_DMA_TYPE_DEV_SG:
|
|
|
|
case SNDRV_DMA_TYPE_DEV_UC_SG:
|
|
|
|
return snd_pcm_sgbuf_ops_page(substream, ofs);
|
|
|
|
#endif /* CONFIG_SND_DMA_SGBUF */
|
|
|
|
case SNDRV_DMA_TYPE_VMALLOC:
|
|
|
|
return vmalloc_to_page(vaddr);
|
|
|
|
default:
|
|
|
|
return virt_to_page(vaddr);
|
|
|
|
}
|
2009-11-26 19:43:39 +08:00
|
|
|
}
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
/*
|
2007-12-13 23:15:00 +08:00
|
|
|
* fault callback for mmapping a RAM page
|
2005-04-17 06:20:36 +08:00
|
|
|
*/
|
2018-04-25 12:20:29 +08:00
|
|
|
static vm_fault_t snd_pcm_mmap_data_fault(struct vm_fault *vmf)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2017-02-25 06:56:41 +08:00
|
|
|
struct snd_pcm_substream *substream = vmf->vma->vm_private_data;
|
2005-11-17 20:59:38 +08:00
|
|
|
struct snd_pcm_runtime *runtime;
|
2005-04-17 06:20:36 +08:00
|
|
|
unsigned long offset;
|
|
|
|
struct page * page;
|
|
|
|
size_t dma_bytes;
|
|
|
|
|
|
|
|
if (substream == NULL)
|
2007-12-13 23:15:00 +08:00
|
|
|
return VM_FAULT_SIGBUS;
|
2005-04-17 06:20:36 +08:00
|
|
|
runtime = substream->runtime;
|
2007-12-13 23:15:00 +08:00
|
|
|
offset = vmf->pgoff << PAGE_SHIFT;
|
2005-04-17 06:20:36 +08:00
|
|
|
dma_bytes = PAGE_ALIGN(runtime->dma_bytes);
|
|
|
|
if (offset > dma_bytes - PAGE_SIZE)
|
2007-12-13 23:15:00 +08:00
|
|
|
return VM_FAULT_SIGBUS;
|
2009-11-26 19:43:39 +08:00
|
|
|
if (substream->ops->page)
|
2005-04-17 06:20:36 +08:00
|
|
|
page = substream->ops->page(substream, offset);
|
2009-11-26 19:43:39 +08:00
|
|
|
else
|
|
|
|
page = snd_pcm_default_page_ops(substream, offset);
|
|
|
|
if (!page)
|
|
|
|
return VM_FAULT_SIGBUS;
|
2005-10-30 09:16:12 +08:00
|
|
|
get_page(page);
|
2007-12-13 23:15:00 +08:00
|
|
|
vmf->page = page;
|
|
|
|
return 0;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
2009-11-26 19:40:21 +08:00
|
|
|
static const struct vm_operations_struct snd_pcm_vm_ops_data = {
|
|
|
|
.open = snd_pcm_mmap_data_open,
|
|
|
|
.close = snd_pcm_mmap_data_close,
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct vm_operations_struct snd_pcm_vm_ops_data_fault = {
|
2005-04-17 06:20:36 +08:00
|
|
|
.open = snd_pcm_mmap_data_open,
|
|
|
|
.close = snd_pcm_mmap_data_close,
|
2007-12-13 23:15:00 +08:00
|
|
|
.fault = snd_pcm_mmap_data_fault,
|
2005-04-17 06:20:36 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
/*
|
|
|
|
* mmap the DMA buffer on RAM
|
|
|
|
*/
|
2014-10-30 22:02:50 +08:00
|
|
|
|
|
|
|
/**
|
|
|
|
* snd_pcm_lib_default_mmap - Default PCM data mmap function
|
|
|
|
* @substream: PCM substream
|
|
|
|
* @area: VMA
|
|
|
|
*
|
|
|
|
* This is the default mmap handler for PCM data. When mmap pcm_ops is NULL,
|
|
|
|
* this function is invoked implicitly.
|
|
|
|
*/
|
2011-09-28 23:12:59 +08:00
|
|
|
int snd_pcm_lib_default_mmap(struct snd_pcm_substream *substream,
|
|
|
|
struct vm_area_struct *area)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
mm: kill vma flag VM_RESERVED and mm->reserved_vm counter
A long time ago, in v2.4, VM_RESERVED kept swapout process off VMA,
currently it lost original meaning but still has some effects:
| effect | alternative flags
-+------------------------+---------------------------------------------
1| account as reserved_vm | VM_IO
2| skip in core dump | VM_IO, VM_DONTDUMP
3| do not merge or expand | VM_IO, VM_DONTEXPAND, VM_HUGETLB, VM_PFNMAP
4| do not mlock | VM_IO, VM_DONTEXPAND, VM_HUGETLB, VM_PFNMAP
This patch removes reserved_vm counter from mm_struct. Seems like nobody
cares about it, it does not exported into userspace directly, it only
reduces total_vm showed in proc.
Thus VM_RESERVED can be replaced with VM_IO or pair VM_DONTEXPAND | VM_DONTDUMP.
remap_pfn_range() and io_remap_pfn_range() set VM_IO|VM_DONTEXPAND|VM_DONTDUMP.
remap_vmalloc_range() set VM_DONTEXPAND | VM_DONTDUMP.
[akpm@linux-foundation.org: drivers/vfio/pci/vfio_pci.c fixup]
Signed-off-by: Konstantin Khlebnikov <khlebnikov@openvz.org>
Cc: Alexander Viro <viro@zeniv.linux.org.uk>
Cc: Carsten Otte <cotte@de.ibm.com>
Cc: Chris Metcalf <cmetcalf@tilera.com>
Cc: Cyrill Gorcunov <gorcunov@openvz.org>
Cc: Eric Paris <eparis@redhat.com>
Cc: H. Peter Anvin <hpa@zytor.com>
Cc: Hugh Dickins <hughd@google.com>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: James Morris <james.l.morris@oracle.com>
Cc: Jason Baron <jbaron@redhat.com>
Cc: Kentaro Takeda <takedakn@nttdata.co.jp>
Cc: Matt Helsley <matthltc@us.ibm.com>
Cc: Nick Piggin <npiggin@kernel.dk>
Cc: Oleg Nesterov <oleg@redhat.com>
Cc: Peter Zijlstra <a.p.zijlstra@chello.nl>
Cc: Robert Richter <robert.richter@amd.com>
Cc: Suresh Siddha <suresh.b.siddha@intel.com>
Cc: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Cc: Venkatesh Pallipadi <venki@google.com>
Acked-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2012-10-09 07:29:02 +08:00
|
|
|
area->vm_flags |= VM_DONTEXPAND | VM_DONTDUMP;
|
2013-10-24 20:25:32 +08:00
|
|
|
#ifdef CONFIG_GENERIC_ALLOCATOR
|
2013-10-23 11:47:43 +08:00
|
|
|
if (substream->dma_buffer.dev.type == SNDRV_DMA_TYPE_DEV_IRAM) {
|
|
|
|
area->vm_page_prot = pgprot_writecombine(area->vm_page_prot);
|
|
|
|
return remap_pfn_range(area, area->vm_start,
|
|
|
|
substream->dma_buffer.addr >> PAGE_SHIFT,
|
|
|
|
area->vm_end - area->vm_start, area->vm_page_prot);
|
|
|
|
}
|
2013-10-24 20:25:32 +08:00
|
|
|
#endif /* CONFIG_GENERIC_ALLOCATOR */
|
2017-07-10 03:31:02 +08:00
|
|
|
if (IS_ENABLED(CONFIG_HAS_DMA) && !substream->ops->page &&
|
2019-11-09 00:56:26 +08:00
|
|
|
(substream->dma_buffer.dev.type == SNDRV_DMA_TYPE_DEV ||
|
|
|
|
substream->dma_buffer.dev.type == SNDRV_DMA_TYPE_DEV_UC))
|
2009-11-26 19:40:21 +08:00
|
|
|
return dma_mmap_coherent(substream->dma_buffer.dev.dev,
|
|
|
|
area,
|
|
|
|
substream->runtime->dma_area,
|
|
|
|
substream->runtime->dma_addr,
|
2018-03-26 22:10:21 +08:00
|
|
|
substream->runtime->dma_bytes);
|
2009-11-26 19:40:21 +08:00
|
|
|
/* mmap with fault handler */
|
|
|
|
area->vm_ops = &snd_pcm_vm_ops_data_fault;
|
2005-04-17 06:20:36 +08:00
|
|
|
return 0;
|
|
|
|
}
|
2011-09-28 23:12:59 +08:00
|
|
|
EXPORT_SYMBOL_GPL(snd_pcm_lib_default_mmap);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
/*
|
|
|
|
* mmap the DMA buffer on I/O memory area
|
|
|
|
*/
|
|
|
|
#if SNDRV_PCM_INFO_MMAP_IOMEM
|
2014-10-30 22:02:50 +08:00
|
|
|
/**
|
|
|
|
* snd_pcm_lib_mmap_iomem - Default PCM data mmap function for I/O mem
|
|
|
|
* @substream: PCM substream
|
|
|
|
* @area: VMA
|
|
|
|
*
|
|
|
|
* When your hardware uses the iomapped pages as the hardware buffer and
|
|
|
|
* wants to mmap it, pass this function as mmap pcm_ops. Note that this
|
|
|
|
* is supposed to work only on limited architectures.
|
|
|
|
*/
|
2005-11-17 20:59:38 +08:00
|
|
|
int snd_pcm_lib_mmap_iomem(struct snd_pcm_substream *substream,
|
|
|
|
struct vm_area_struct *area)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2018-01-16 21:26:26 +08:00
|
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
area->vm_page_prot = pgprot_noncached(area->vm_page_prot);
|
2013-04-20 01:01:04 +08:00
|
|
|
return vm_iomap_memory(area, runtime->dma_addr, runtime->dma_bytes);
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
2006-04-28 21:13:40 +08:00
|
|
|
EXPORT_SYMBOL(snd_pcm_lib_mmap_iomem);
|
2005-04-17 06:20:36 +08:00
|
|
|
#endif /* SNDRV_PCM_INFO_MMAP */
|
|
|
|
|
|
|
|
/*
|
|
|
|
* mmap DMA buffer
|
|
|
|
*/
|
2005-11-17 20:59:38 +08:00
|
|
|
int snd_pcm_mmap_data(struct snd_pcm_substream *substream, struct file *file,
|
2005-04-17 06:20:36 +08:00
|
|
|
struct vm_area_struct *area)
|
|
|
|
{
|
2005-11-17 20:59:38 +08:00
|
|
|
struct snd_pcm_runtime *runtime;
|
2005-04-17 06:20:36 +08:00
|
|
|
long size;
|
|
|
|
unsigned long offset;
|
|
|
|
size_t dma_bytes;
|
2009-11-26 19:40:21 +08:00
|
|
|
int err;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
|
|
|
if (!(area->vm_flags & (VM_WRITE|VM_READ)))
|
|
|
|
return -EINVAL;
|
|
|
|
} else {
|
|
|
|
if (!(area->vm_flags & VM_READ))
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
runtime = substream->runtime;
|
|
|
|
if (runtime->status->state == SNDRV_PCM_STATE_OPEN)
|
|
|
|
return -EBADFD;
|
|
|
|
if (!(runtime->info & SNDRV_PCM_INFO_MMAP))
|
|
|
|
return -ENXIO;
|
|
|
|
if (runtime->access == SNDRV_PCM_ACCESS_RW_INTERLEAVED ||
|
|
|
|
runtime->access == SNDRV_PCM_ACCESS_RW_NONINTERLEAVED)
|
|
|
|
return -EINVAL;
|
|
|
|
size = area->vm_end - area->vm_start;
|
|
|
|
offset = area->vm_pgoff << PAGE_SHIFT;
|
|
|
|
dma_bytes = PAGE_ALIGN(runtime->dma_bytes);
|
|
|
|
if ((size_t)size > dma_bytes)
|
|
|
|
return -EINVAL;
|
|
|
|
if (offset > dma_bytes - size)
|
|
|
|
return -EINVAL;
|
|
|
|
|
2009-11-26 19:40:21 +08:00
|
|
|
area->vm_ops = &snd_pcm_vm_ops_data;
|
|
|
|
area->vm_private_data = substream;
|
2005-04-17 06:20:36 +08:00
|
|
|
if (substream->ops->mmap)
|
2009-11-26 19:40:21 +08:00
|
|
|
err = substream->ops->mmap(substream, area);
|
2005-04-17 06:20:36 +08:00
|
|
|
else
|
2011-09-28 23:12:59 +08:00
|
|
|
err = snd_pcm_lib_default_mmap(substream, area);
|
2009-11-26 19:40:21 +08:00
|
|
|
if (!err)
|
|
|
|
atomic_inc(&substream->mmap_count);
|
|
|
|
return err;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
2006-04-28 21:13:40 +08:00
|
|
|
EXPORT_SYMBOL(snd_pcm_mmap_data);
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
static int snd_pcm_mmap(struct file *file, struct vm_area_struct *area)
|
|
|
|
{
|
2005-11-17 20:59:38 +08:00
|
|
|
struct snd_pcm_file * pcm_file;
|
|
|
|
struct snd_pcm_substream *substream;
|
2005-04-17 06:20:36 +08:00
|
|
|
unsigned long offset;
|
|
|
|
|
|
|
|
pcm_file = file->private_data;
|
|
|
|
substream = pcm_file->substream;
|
2008-08-08 23:09:09 +08:00
|
|
|
if (PCM_RUNTIME_CHECK(substream))
|
|
|
|
return -ENXIO;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
offset = area->vm_pgoff << PAGE_SHIFT;
|
|
|
|
switch (offset) {
|
ALSA: add new 32-bit layout for snd_pcm_mmap_status/control
The snd_pcm_mmap_status and snd_pcm_mmap_control interfaces are one of the
trickiest areas to get right when moving to 64-bit time_t in user space.
The snd_pcm_mmap_status structure layout is incompatible with user space
that uses a 64-bit time_t, so we need a new layout for it. Since the
SNDRV_PCM_IOCTL_SYNC_PTR ioctl combines it with snd_pcm_mmap_control
into snd_pcm_sync_ptr, we need to change those two as well.
Both structures are also exported via an mmap() operation on certain
architectures, and this suffers from incompatibility between 32-bit
and 64-bit user space. As we have to change both structures anyway,
this is a good opportunity to fix the mmap() problem as well, so let's
standardize on the existing 64-bit layout of the structure where possible.
The downside is that we lose mmap() support for existing 32-bit x86 and
powerpc applications, adding that would introduce very noticeable runtime
overhead and complexity. My assumption here is that not too many people
will miss the removed feature, given that:
- Almost all x86 and powerpc users these days are on 64-bit kernels,
the majority of today's 32-bit users are on architectures that never
supported mmap (ARM, MIPS, ...).
- It never worked in compat mode (it was intentionally disabled there)
- The application already needs to work with a fallback to
SNDRV_PCM_IOCTL_SYNC_PTR, which will keep working with both the old
and new structure layout.
Both the ioctl() and mmap() based interfaces are changed at the same
time, as they are based on the same structures. Unlike other interfaces,
we change the uapi header to export both the traditional structure and
a version that is portable between 32-bit and 64-bit user space code
and that corresponds to the existing 64-bit layout. We further check the
__USE_TIME_BITS64 macro that will be defined by future C library versions
whenever we use the new time_t definition, so any existing user space
source code will not see any changes until it gets rebuilt against a new
C library. However, the new structures are all visible in addition to the
old ones, allowing applications to explicitly request the new structures.
In order to detect the difference between the old snd_pcm_mmap_status and
the new __snd_pcm_mmap_status64 structure from the ioctl command number,
we rely on one quirk in the structure definition: snd_pcm_mmap_status
must be aligned to alignof(time_t), which leads the compiler to insert
four bytes of padding in struct snd_pcm_sync_ptr after 'flags' and a
corresponding change in the size of snd_pcm_sync_ptr itself. On x86-32
(and only there), the compiler doesn't use 64-bit alignment in structure,
so I'm adding an explicit pad in the structure that has no effect on the
existing 64-bit architectures but ensures that the layout matches for x86.
The snd_pcm_uframes_t type compatibility requires another hack: we can't
easily make that 64 bit wide, so I leave the type as 'unsigned long',
but add padding before and after it, to ensure that the data is properly
aligned to the respective 64-bit field in the in-kernel structure.
For the SNDRV_PCM_MMAP_OFFSET_STATUS/CONTROL constants that are used
as the virtual file offset in the mmap() function, we also have to
introduce new constants that depend on hte __USE_TIME_BITS64 macro:
The existing macros are renamed to SNDRV_PCM_MMAP_OFFSET_STATUS_OLD
and SNDRV_PCM_MMAP_OFFSET_CONTROL_OLD, they continue to work fine on
64-bit architectures, but stop working on native 32-bit user space.
The replacement _NEW constants are now used by default for user space
built with __USE_TIME_BITS64, those now work on all new kernels for x86,
ppc and alpha (32 and 64 bit, native and compat). It might be a good idea
for a future alsa-lib to support both the _OLD and _NEW macros and use
the corresponding structures directly. Unmodified alsa-lib source code
will retain the current behavior, so it will no longer be able to use
mmap() for the status/control structures on 32-bit systems, until either
the C library gets updated to 64-bit time_t or alsa-lib gets updated to
support both mmap() layouts.
Co-developed-with: Baolin Wang <baolin.wang@linaro.org>
Signed-off-by: Baolin Wang <baolin.wang@linaro.org>
Signed-off-by: Arnd Bergmann <arnd@arndb.de>
2018-04-24 20:06:15 +08:00
|
|
|
case SNDRV_PCM_MMAP_OFFSET_STATUS_OLD:
|
|
|
|
if (pcm_file->no_compat_mmap || !IS_ENABLED(CONFIG_64BIT))
|
|
|
|
return -ENXIO;
|
2020-07-09 04:32:36 +08:00
|
|
|
fallthrough;
|
ALSA: add new 32-bit layout for snd_pcm_mmap_status/control
The snd_pcm_mmap_status and snd_pcm_mmap_control interfaces are one of the
trickiest areas to get right when moving to 64-bit time_t in user space.
The snd_pcm_mmap_status structure layout is incompatible with user space
that uses a 64-bit time_t, so we need a new layout for it. Since the
SNDRV_PCM_IOCTL_SYNC_PTR ioctl combines it with snd_pcm_mmap_control
into snd_pcm_sync_ptr, we need to change those two as well.
Both structures are also exported via an mmap() operation on certain
architectures, and this suffers from incompatibility between 32-bit
and 64-bit user space. As we have to change both structures anyway,
this is a good opportunity to fix the mmap() problem as well, so let's
standardize on the existing 64-bit layout of the structure where possible.
The downside is that we lose mmap() support for existing 32-bit x86 and
powerpc applications, adding that would introduce very noticeable runtime
overhead and complexity. My assumption here is that not too many people
will miss the removed feature, given that:
- Almost all x86 and powerpc users these days are on 64-bit kernels,
the majority of today's 32-bit users are on architectures that never
supported mmap (ARM, MIPS, ...).
- It never worked in compat mode (it was intentionally disabled there)
- The application already needs to work with a fallback to
SNDRV_PCM_IOCTL_SYNC_PTR, which will keep working with both the old
and new structure layout.
Both the ioctl() and mmap() based interfaces are changed at the same
time, as they are based on the same structures. Unlike other interfaces,
we change the uapi header to export both the traditional structure and
a version that is portable between 32-bit and 64-bit user space code
and that corresponds to the existing 64-bit layout. We further check the
__USE_TIME_BITS64 macro that will be defined by future C library versions
whenever we use the new time_t definition, so any existing user space
source code will not see any changes until it gets rebuilt against a new
C library. However, the new structures are all visible in addition to the
old ones, allowing applications to explicitly request the new structures.
In order to detect the difference between the old snd_pcm_mmap_status and
the new __snd_pcm_mmap_status64 structure from the ioctl command number,
we rely on one quirk in the structure definition: snd_pcm_mmap_status
must be aligned to alignof(time_t), which leads the compiler to insert
four bytes of padding in struct snd_pcm_sync_ptr after 'flags' and a
corresponding change in the size of snd_pcm_sync_ptr itself. On x86-32
(and only there), the compiler doesn't use 64-bit alignment in structure,
so I'm adding an explicit pad in the structure that has no effect on the
existing 64-bit architectures but ensures that the layout matches for x86.
The snd_pcm_uframes_t type compatibility requires another hack: we can't
easily make that 64 bit wide, so I leave the type as 'unsigned long',
but add padding before and after it, to ensure that the data is properly
aligned to the respective 64-bit field in the in-kernel structure.
For the SNDRV_PCM_MMAP_OFFSET_STATUS/CONTROL constants that are used
as the virtual file offset in the mmap() function, we also have to
introduce new constants that depend on hte __USE_TIME_BITS64 macro:
The existing macros are renamed to SNDRV_PCM_MMAP_OFFSET_STATUS_OLD
and SNDRV_PCM_MMAP_OFFSET_CONTROL_OLD, they continue to work fine on
64-bit architectures, but stop working on native 32-bit user space.
The replacement _NEW constants are now used by default for user space
built with __USE_TIME_BITS64, those now work on all new kernels for x86,
ppc and alpha (32 and 64 bit, native and compat). It might be a good idea
for a future alsa-lib to support both the _OLD and _NEW macros and use
the corresponding structures directly. Unmodified alsa-lib source code
will retain the current behavior, so it will no longer be able to use
mmap() for the status/control structures on 32-bit systems, until either
the C library gets updated to 64-bit time_t or alsa-lib gets updated to
support both mmap() layouts.
Co-developed-with: Baolin Wang <baolin.wang@linaro.org>
Signed-off-by: Baolin Wang <baolin.wang@linaro.org>
Signed-off-by: Arnd Bergmann <arnd@arndb.de>
2018-04-24 20:06:15 +08:00
|
|
|
case SNDRV_PCM_MMAP_OFFSET_STATUS_NEW:
|
ALSA: pcm: Add the explicit appl_ptr sync support
Currently x86 platforms use the PCM status/control mmaps for
transferring the PCM status and appl_ptr between kernel and
user-spaces. The mmap is a most efficient way of communication, but
it has a drawback per its nature, namely, it can't notify the change
explicitly to kernel.
The lack of appl_ptr update notification is a problem on a few
existing drivers, but it's mostly a small issue and negligible.
However, a new type of driver that uses DSP for a deep buffer
management requires the exact position of appl_ptr for calculating the
buffer prefetch size, and the asynchronous appl_ptr update between
kernel and user-spaces becomes a significant problem for it.
How can we enforce user-space to report the appl_ptr update? The way
is relatively simple. Just by disabling the PCM control mmap, the
user-space is supposed to fall back to the mode using SYNC_PTR ioctl,
and the kernel gets control over that. This fallback mode is used in
all non-x86 platforms as default, and also in the 32bit compatible
model on all platforms including x86. It's been implemented already
over a decade, so we can say it's fairly safe and stably working.
With the help of the knowledge above, this patch introduces a new PCM
info flag SNDRV_PCM_INFO_SYNC_APPLPTR for achieving the appl_ptr sync
from user-space. When a driver sets this flag at open, the PCM status
/ control mmap is disabled, which effectively switches to SYNC_PTR
mode in user-space side.
In this version, both PCM status and control mmaps are disabled
although only the latter, control mmap, is the target. It's because
the current alsa-lib implementation supposes that both status and
control mmaps are always coupled, thus it handles a fatal error when
only one of them fails.
Of course, the disablement of the status/control mmaps may bring a
slight performance overhead. Thus, as of now, this should be used
only for the dedicated devices that deserves.
Note that the disablement of mmap is a sort of workaround. In the
later patch, we'll introduce the way to identify the protocol version
alsa-lib supports, and keep mmap working while the sync_ptr is
performed together.
Reviewed-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2017-06-20 04:39:18 +08:00
|
|
|
if (!pcm_status_mmap_allowed(pcm_file))
|
2005-04-17 06:20:36 +08:00
|
|
|
return -ENXIO;
|
|
|
|
return snd_pcm_mmap_status(substream, file, area);
|
ALSA: add new 32-bit layout for snd_pcm_mmap_status/control
The snd_pcm_mmap_status and snd_pcm_mmap_control interfaces are one of the
trickiest areas to get right when moving to 64-bit time_t in user space.
The snd_pcm_mmap_status structure layout is incompatible with user space
that uses a 64-bit time_t, so we need a new layout for it. Since the
SNDRV_PCM_IOCTL_SYNC_PTR ioctl combines it with snd_pcm_mmap_control
into snd_pcm_sync_ptr, we need to change those two as well.
Both structures are also exported via an mmap() operation on certain
architectures, and this suffers from incompatibility between 32-bit
and 64-bit user space. As we have to change both structures anyway,
this is a good opportunity to fix the mmap() problem as well, so let's
standardize on the existing 64-bit layout of the structure where possible.
The downside is that we lose mmap() support for existing 32-bit x86 and
powerpc applications, adding that would introduce very noticeable runtime
overhead and complexity. My assumption here is that not too many people
will miss the removed feature, given that:
- Almost all x86 and powerpc users these days are on 64-bit kernels,
the majority of today's 32-bit users are on architectures that never
supported mmap (ARM, MIPS, ...).
- It never worked in compat mode (it was intentionally disabled there)
- The application already needs to work with a fallback to
SNDRV_PCM_IOCTL_SYNC_PTR, which will keep working with both the old
and new structure layout.
Both the ioctl() and mmap() based interfaces are changed at the same
time, as they are based on the same structures. Unlike other interfaces,
we change the uapi header to export both the traditional structure and
a version that is portable between 32-bit and 64-bit user space code
and that corresponds to the existing 64-bit layout. We further check the
__USE_TIME_BITS64 macro that will be defined by future C library versions
whenever we use the new time_t definition, so any existing user space
source code will not see any changes until it gets rebuilt against a new
C library. However, the new structures are all visible in addition to the
old ones, allowing applications to explicitly request the new structures.
In order to detect the difference between the old snd_pcm_mmap_status and
the new __snd_pcm_mmap_status64 structure from the ioctl command number,
we rely on one quirk in the structure definition: snd_pcm_mmap_status
must be aligned to alignof(time_t), which leads the compiler to insert
four bytes of padding in struct snd_pcm_sync_ptr after 'flags' and a
corresponding change in the size of snd_pcm_sync_ptr itself. On x86-32
(and only there), the compiler doesn't use 64-bit alignment in structure,
so I'm adding an explicit pad in the structure that has no effect on the
existing 64-bit architectures but ensures that the layout matches for x86.
The snd_pcm_uframes_t type compatibility requires another hack: we can't
easily make that 64 bit wide, so I leave the type as 'unsigned long',
but add padding before and after it, to ensure that the data is properly
aligned to the respective 64-bit field in the in-kernel structure.
For the SNDRV_PCM_MMAP_OFFSET_STATUS/CONTROL constants that are used
as the virtual file offset in the mmap() function, we also have to
introduce new constants that depend on hte __USE_TIME_BITS64 macro:
The existing macros are renamed to SNDRV_PCM_MMAP_OFFSET_STATUS_OLD
and SNDRV_PCM_MMAP_OFFSET_CONTROL_OLD, they continue to work fine on
64-bit architectures, but stop working on native 32-bit user space.
The replacement _NEW constants are now used by default for user space
built with __USE_TIME_BITS64, those now work on all new kernels for x86,
ppc and alpha (32 and 64 bit, native and compat). It might be a good idea
for a future alsa-lib to support both the _OLD and _NEW macros and use
the corresponding structures directly. Unmodified alsa-lib source code
will retain the current behavior, so it will no longer be able to use
mmap() for the status/control structures on 32-bit systems, until either
the C library gets updated to 64-bit time_t or alsa-lib gets updated to
support both mmap() layouts.
Co-developed-with: Baolin Wang <baolin.wang@linaro.org>
Signed-off-by: Baolin Wang <baolin.wang@linaro.org>
Signed-off-by: Arnd Bergmann <arnd@arndb.de>
2018-04-24 20:06:15 +08:00
|
|
|
case SNDRV_PCM_MMAP_OFFSET_CONTROL_OLD:
|
|
|
|
if (pcm_file->no_compat_mmap || !IS_ENABLED(CONFIG_64BIT))
|
|
|
|
return -ENXIO;
|
2020-07-09 04:32:36 +08:00
|
|
|
fallthrough;
|
ALSA: add new 32-bit layout for snd_pcm_mmap_status/control
The snd_pcm_mmap_status and snd_pcm_mmap_control interfaces are one of the
trickiest areas to get right when moving to 64-bit time_t in user space.
The snd_pcm_mmap_status structure layout is incompatible with user space
that uses a 64-bit time_t, so we need a new layout for it. Since the
SNDRV_PCM_IOCTL_SYNC_PTR ioctl combines it with snd_pcm_mmap_control
into snd_pcm_sync_ptr, we need to change those two as well.
Both structures are also exported via an mmap() operation on certain
architectures, and this suffers from incompatibility between 32-bit
and 64-bit user space. As we have to change both structures anyway,
this is a good opportunity to fix the mmap() problem as well, so let's
standardize on the existing 64-bit layout of the structure where possible.
The downside is that we lose mmap() support for existing 32-bit x86 and
powerpc applications, adding that would introduce very noticeable runtime
overhead and complexity. My assumption here is that not too many people
will miss the removed feature, given that:
- Almost all x86 and powerpc users these days are on 64-bit kernels,
the majority of today's 32-bit users are on architectures that never
supported mmap (ARM, MIPS, ...).
- It never worked in compat mode (it was intentionally disabled there)
- The application already needs to work with a fallback to
SNDRV_PCM_IOCTL_SYNC_PTR, which will keep working with both the old
and new structure layout.
Both the ioctl() and mmap() based interfaces are changed at the same
time, as they are based on the same structures. Unlike other interfaces,
we change the uapi header to export both the traditional structure and
a version that is portable between 32-bit and 64-bit user space code
and that corresponds to the existing 64-bit layout. We further check the
__USE_TIME_BITS64 macro that will be defined by future C library versions
whenever we use the new time_t definition, so any existing user space
source code will not see any changes until it gets rebuilt against a new
C library. However, the new structures are all visible in addition to the
old ones, allowing applications to explicitly request the new structures.
In order to detect the difference between the old snd_pcm_mmap_status and
the new __snd_pcm_mmap_status64 structure from the ioctl command number,
we rely on one quirk in the structure definition: snd_pcm_mmap_status
must be aligned to alignof(time_t), which leads the compiler to insert
four bytes of padding in struct snd_pcm_sync_ptr after 'flags' and a
corresponding change in the size of snd_pcm_sync_ptr itself. On x86-32
(and only there), the compiler doesn't use 64-bit alignment in structure,
so I'm adding an explicit pad in the structure that has no effect on the
existing 64-bit architectures but ensures that the layout matches for x86.
The snd_pcm_uframes_t type compatibility requires another hack: we can't
easily make that 64 bit wide, so I leave the type as 'unsigned long',
but add padding before and after it, to ensure that the data is properly
aligned to the respective 64-bit field in the in-kernel structure.
For the SNDRV_PCM_MMAP_OFFSET_STATUS/CONTROL constants that are used
as the virtual file offset in the mmap() function, we also have to
introduce new constants that depend on hte __USE_TIME_BITS64 macro:
The existing macros are renamed to SNDRV_PCM_MMAP_OFFSET_STATUS_OLD
and SNDRV_PCM_MMAP_OFFSET_CONTROL_OLD, they continue to work fine on
64-bit architectures, but stop working on native 32-bit user space.
The replacement _NEW constants are now used by default for user space
built with __USE_TIME_BITS64, those now work on all new kernels for x86,
ppc and alpha (32 and 64 bit, native and compat). It might be a good idea
for a future alsa-lib to support both the _OLD and _NEW macros and use
the corresponding structures directly. Unmodified alsa-lib source code
will retain the current behavior, so it will no longer be able to use
mmap() for the status/control structures on 32-bit systems, until either
the C library gets updated to 64-bit time_t or alsa-lib gets updated to
support both mmap() layouts.
Co-developed-with: Baolin Wang <baolin.wang@linaro.org>
Signed-off-by: Baolin Wang <baolin.wang@linaro.org>
Signed-off-by: Arnd Bergmann <arnd@arndb.de>
2018-04-24 20:06:15 +08:00
|
|
|
case SNDRV_PCM_MMAP_OFFSET_CONTROL_NEW:
|
2017-06-27 17:54:37 +08:00
|
|
|
if (!pcm_control_mmap_allowed(pcm_file))
|
2005-04-17 06:20:36 +08:00
|
|
|
return -ENXIO;
|
|
|
|
return snd_pcm_mmap_control(substream, file, area);
|
|
|
|
default:
|
|
|
|
return snd_pcm_mmap_data(substream, file, area);
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int snd_pcm_fasync(int fd, struct file * file, int on)
|
|
|
|
{
|
2005-11-17 20:59:38 +08:00
|
|
|
struct snd_pcm_file * pcm_file;
|
|
|
|
struct snd_pcm_substream *substream;
|
|
|
|
struct snd_pcm_runtime *runtime;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
pcm_file = file->private_data;
|
|
|
|
substream = pcm_file->substream;
|
2008-08-08 23:09:09 +08:00
|
|
|
if (PCM_RUNTIME_CHECK(substream))
|
2010-04-08 00:29:46 +08:00
|
|
|
return -ENXIO;
|
2005-04-17 06:20:36 +08:00
|
|
|
runtime = substream->runtime;
|
2010-04-08 00:29:46 +08:00
|
|
|
return fasync_helper(fd, file, on, &runtime->fasync);
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* ioctl32 compat
|
|
|
|
*/
|
|
|
|
#ifdef CONFIG_COMPAT
|
|
|
|
#include "pcm_compat.c"
|
|
|
|
#else
|
|
|
|
#define snd_pcm_ioctl_compat NULL
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/*
|
|
|
|
* To be removed helpers to keep binary compatibility
|
|
|
|
*/
|
|
|
|
|
2005-12-01 17:51:58 +08:00
|
|
|
#ifdef CONFIG_SND_SUPPORT_OLD_API
|
2005-04-17 06:20:36 +08:00
|
|
|
#define __OLD_TO_NEW_MASK(x) ((x&7)|((x&0x07fffff8)<<5))
|
|
|
|
#define __NEW_TO_OLD_MASK(x) ((x&7)|((x&0xffffff00)>>5))
|
|
|
|
|
2005-11-17 20:59:38 +08:00
|
|
|
static void snd_pcm_hw_convert_from_old_params(struct snd_pcm_hw_params *params,
|
|
|
|
struct snd_pcm_hw_params_old *oparams)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
unsigned int i;
|
|
|
|
|
|
|
|
memset(params, 0, sizeof(*params));
|
|
|
|
params->flags = oparams->flags;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(oparams->masks); i++)
|
|
|
|
params->masks[i].bits[0] = oparams->masks[i];
|
|
|
|
memcpy(params->intervals, oparams->intervals, sizeof(oparams->intervals));
|
|
|
|
params->rmask = __OLD_TO_NEW_MASK(oparams->rmask);
|
|
|
|
params->cmask = __OLD_TO_NEW_MASK(oparams->cmask);
|
|
|
|
params->info = oparams->info;
|
|
|
|
params->msbits = oparams->msbits;
|
|
|
|
params->rate_num = oparams->rate_num;
|
|
|
|
params->rate_den = oparams->rate_den;
|
|
|
|
params->fifo_size = oparams->fifo_size;
|
|
|
|
}
|
|
|
|
|
2005-11-17 20:59:38 +08:00
|
|
|
static void snd_pcm_hw_convert_to_old_params(struct snd_pcm_hw_params_old *oparams,
|
|
|
|
struct snd_pcm_hw_params *params)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
unsigned int i;
|
|
|
|
|
|
|
|
memset(oparams, 0, sizeof(*oparams));
|
|
|
|
oparams->flags = params->flags;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(oparams->masks); i++)
|
|
|
|
oparams->masks[i] = params->masks[i].bits[0];
|
|
|
|
memcpy(oparams->intervals, params->intervals, sizeof(oparams->intervals));
|
|
|
|
oparams->rmask = __NEW_TO_OLD_MASK(params->rmask);
|
|
|
|
oparams->cmask = __NEW_TO_OLD_MASK(params->cmask);
|
|
|
|
oparams->info = params->info;
|
|
|
|
oparams->msbits = params->msbits;
|
|
|
|
oparams->rate_num = params->rate_num;
|
|
|
|
oparams->rate_den = params->rate_den;
|
|
|
|
oparams->fifo_size = params->fifo_size;
|
|
|
|
}
|
|
|
|
|
2005-11-17 20:59:38 +08:00
|
|
|
static int snd_pcm_hw_refine_old_user(struct snd_pcm_substream *substream,
|
|
|
|
struct snd_pcm_hw_params_old __user * _oparams)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2005-11-17 20:59:38 +08:00
|
|
|
struct snd_pcm_hw_params *params;
|
|
|
|
struct snd_pcm_hw_params_old *oparams = NULL;
|
2005-04-17 06:20:36 +08:00
|
|
|
int err;
|
|
|
|
|
|
|
|
params = kmalloc(sizeof(*params), GFP_KERNEL);
|
2009-04-10 09:43:08 +08:00
|
|
|
if (!params)
|
|
|
|
return -ENOMEM;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2009-04-10 09:43:08 +08:00
|
|
|
oparams = memdup_user(_oparams, sizeof(*oparams));
|
|
|
|
if (IS_ERR(oparams)) {
|
|
|
|
err = PTR_ERR(oparams);
|
2005-04-17 06:20:36 +08:00
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
snd_pcm_hw_convert_from_old_params(params, oparams);
|
|
|
|
err = snd_pcm_hw_refine(substream, params);
|
2017-06-11 22:56:12 +08:00
|
|
|
if (err < 0)
|
|
|
|
goto out_old;
|
2009-04-10 09:43:08 +08:00
|
|
|
|
2017-06-11 22:56:12 +08:00
|
|
|
err = fixup_unreferenced_params(substream, params);
|
|
|
|
if (err < 0)
|
|
|
|
goto out_old;
|
2009-04-10 09:43:08 +08:00
|
|
|
|
2017-06-11 22:56:12 +08:00
|
|
|
snd_pcm_hw_convert_to_old_params(oparams, params);
|
|
|
|
if (copy_to_user(_oparams, oparams, sizeof(*oparams)))
|
|
|
|
err = -EFAULT;
|
|
|
|
out_old:
|
2009-04-10 09:43:08 +08:00
|
|
|
kfree(oparams);
|
2005-04-17 06:20:36 +08:00
|
|
|
out:
|
|
|
|
kfree(params);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2005-11-17 20:59:38 +08:00
|
|
|
static int snd_pcm_hw_params_old_user(struct snd_pcm_substream *substream,
|
|
|
|
struct snd_pcm_hw_params_old __user * _oparams)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2005-11-17 20:59:38 +08:00
|
|
|
struct snd_pcm_hw_params *params;
|
|
|
|
struct snd_pcm_hw_params_old *oparams = NULL;
|
2005-04-17 06:20:36 +08:00
|
|
|
int err;
|
|
|
|
|
|
|
|
params = kmalloc(sizeof(*params), GFP_KERNEL);
|
2009-04-10 09:43:08 +08:00
|
|
|
if (!params)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
oparams = memdup_user(_oparams, sizeof(*oparams));
|
|
|
|
if (IS_ERR(oparams)) {
|
|
|
|
err = PTR_ERR(oparams);
|
2005-04-17 06:20:36 +08:00
|
|
|
goto out;
|
|
|
|
}
|
2017-06-11 22:56:12 +08:00
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
snd_pcm_hw_convert_from_old_params(params, oparams);
|
|
|
|
err = snd_pcm_hw_params(substream, params);
|
2017-06-11 22:56:12 +08:00
|
|
|
if (err < 0)
|
|
|
|
goto out_old;
|
2009-04-10 09:43:08 +08:00
|
|
|
|
2017-06-11 22:56:12 +08:00
|
|
|
snd_pcm_hw_convert_to_old_params(oparams, params);
|
|
|
|
if (copy_to_user(_oparams, oparams, sizeof(*oparams)))
|
|
|
|
err = -EFAULT;
|
|
|
|
out_old:
|
2009-04-10 09:43:08 +08:00
|
|
|
kfree(oparams);
|
2005-04-17 06:20:36 +08:00
|
|
|
out:
|
|
|
|
kfree(params);
|
|
|
|
return err;
|
|
|
|
}
|
2005-12-01 17:51:58 +08:00
|
|
|
#endif /* CONFIG_SND_SUPPORT_OLD_API */
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2008-09-03 16:54:36 +08:00
|
|
|
#ifndef CONFIG_MMU
|
2010-03-10 01:57:52 +08:00
|
|
|
static unsigned long snd_pcm_get_unmapped_area(struct file *file,
|
|
|
|
unsigned long addr,
|
|
|
|
unsigned long len,
|
|
|
|
unsigned long pgoff,
|
|
|
|
unsigned long flags)
|
|
|
|
{
|
|
|
|
struct snd_pcm_file *pcm_file = file->private_data;
|
|
|
|
struct snd_pcm_substream *substream = pcm_file->substream;
|
|
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
|
|
|
unsigned long offset = pgoff << PAGE_SHIFT;
|
|
|
|
|
|
|
|
switch (offset) {
|
ALSA: add new 32-bit layout for snd_pcm_mmap_status/control
The snd_pcm_mmap_status and snd_pcm_mmap_control interfaces are one of the
trickiest areas to get right when moving to 64-bit time_t in user space.
The snd_pcm_mmap_status structure layout is incompatible with user space
that uses a 64-bit time_t, so we need a new layout for it. Since the
SNDRV_PCM_IOCTL_SYNC_PTR ioctl combines it with snd_pcm_mmap_control
into snd_pcm_sync_ptr, we need to change those two as well.
Both structures are also exported via an mmap() operation on certain
architectures, and this suffers from incompatibility between 32-bit
and 64-bit user space. As we have to change both structures anyway,
this is a good opportunity to fix the mmap() problem as well, so let's
standardize on the existing 64-bit layout of the structure where possible.
The downside is that we lose mmap() support for existing 32-bit x86 and
powerpc applications, adding that would introduce very noticeable runtime
overhead and complexity. My assumption here is that not too many people
will miss the removed feature, given that:
- Almost all x86 and powerpc users these days are on 64-bit kernels,
the majority of today's 32-bit users are on architectures that never
supported mmap (ARM, MIPS, ...).
- It never worked in compat mode (it was intentionally disabled there)
- The application already needs to work with a fallback to
SNDRV_PCM_IOCTL_SYNC_PTR, which will keep working with both the old
and new structure layout.
Both the ioctl() and mmap() based interfaces are changed at the same
time, as they are based on the same structures. Unlike other interfaces,
we change the uapi header to export both the traditional structure and
a version that is portable between 32-bit and 64-bit user space code
and that corresponds to the existing 64-bit layout. We further check the
__USE_TIME_BITS64 macro that will be defined by future C library versions
whenever we use the new time_t definition, so any existing user space
source code will not see any changes until it gets rebuilt against a new
C library. However, the new structures are all visible in addition to the
old ones, allowing applications to explicitly request the new structures.
In order to detect the difference between the old snd_pcm_mmap_status and
the new __snd_pcm_mmap_status64 structure from the ioctl command number,
we rely on one quirk in the structure definition: snd_pcm_mmap_status
must be aligned to alignof(time_t), which leads the compiler to insert
four bytes of padding in struct snd_pcm_sync_ptr after 'flags' and a
corresponding change in the size of snd_pcm_sync_ptr itself. On x86-32
(and only there), the compiler doesn't use 64-bit alignment in structure,
so I'm adding an explicit pad in the structure that has no effect on the
existing 64-bit architectures but ensures that the layout matches for x86.
The snd_pcm_uframes_t type compatibility requires another hack: we can't
easily make that 64 bit wide, so I leave the type as 'unsigned long',
but add padding before and after it, to ensure that the data is properly
aligned to the respective 64-bit field in the in-kernel structure.
For the SNDRV_PCM_MMAP_OFFSET_STATUS/CONTROL constants that are used
as the virtual file offset in the mmap() function, we also have to
introduce new constants that depend on hte __USE_TIME_BITS64 macro:
The existing macros are renamed to SNDRV_PCM_MMAP_OFFSET_STATUS_OLD
and SNDRV_PCM_MMAP_OFFSET_CONTROL_OLD, they continue to work fine on
64-bit architectures, but stop working on native 32-bit user space.
The replacement _NEW constants are now used by default for user space
built with __USE_TIME_BITS64, those now work on all new kernels for x86,
ppc and alpha (32 and 64 bit, native and compat). It might be a good idea
for a future alsa-lib to support both the _OLD and _NEW macros and use
the corresponding structures directly. Unmodified alsa-lib source code
will retain the current behavior, so it will no longer be able to use
mmap() for the status/control structures on 32-bit systems, until either
the C library gets updated to 64-bit time_t or alsa-lib gets updated to
support both mmap() layouts.
Co-developed-with: Baolin Wang <baolin.wang@linaro.org>
Signed-off-by: Baolin Wang <baolin.wang@linaro.org>
Signed-off-by: Arnd Bergmann <arnd@arndb.de>
2018-04-24 20:06:15 +08:00
|
|
|
case SNDRV_PCM_MMAP_OFFSET_STATUS_NEW:
|
2010-03-10 01:57:52 +08:00
|
|
|
return (unsigned long)runtime->status;
|
ALSA: add new 32-bit layout for snd_pcm_mmap_status/control
The snd_pcm_mmap_status and snd_pcm_mmap_control interfaces are one of the
trickiest areas to get right when moving to 64-bit time_t in user space.
The snd_pcm_mmap_status structure layout is incompatible with user space
that uses a 64-bit time_t, so we need a new layout for it. Since the
SNDRV_PCM_IOCTL_SYNC_PTR ioctl combines it with snd_pcm_mmap_control
into snd_pcm_sync_ptr, we need to change those two as well.
Both structures are also exported via an mmap() operation on certain
architectures, and this suffers from incompatibility between 32-bit
and 64-bit user space. As we have to change both structures anyway,
this is a good opportunity to fix the mmap() problem as well, so let's
standardize on the existing 64-bit layout of the structure where possible.
The downside is that we lose mmap() support for existing 32-bit x86 and
powerpc applications, adding that would introduce very noticeable runtime
overhead and complexity. My assumption here is that not too many people
will miss the removed feature, given that:
- Almost all x86 and powerpc users these days are on 64-bit kernels,
the majority of today's 32-bit users are on architectures that never
supported mmap (ARM, MIPS, ...).
- It never worked in compat mode (it was intentionally disabled there)
- The application already needs to work with a fallback to
SNDRV_PCM_IOCTL_SYNC_PTR, which will keep working with both the old
and new structure layout.
Both the ioctl() and mmap() based interfaces are changed at the same
time, as they are based on the same structures. Unlike other interfaces,
we change the uapi header to export both the traditional structure and
a version that is portable between 32-bit and 64-bit user space code
and that corresponds to the existing 64-bit layout. We further check the
__USE_TIME_BITS64 macro that will be defined by future C library versions
whenever we use the new time_t definition, so any existing user space
source code will not see any changes until it gets rebuilt against a new
C library. However, the new structures are all visible in addition to the
old ones, allowing applications to explicitly request the new structures.
In order to detect the difference between the old snd_pcm_mmap_status and
the new __snd_pcm_mmap_status64 structure from the ioctl command number,
we rely on one quirk in the structure definition: snd_pcm_mmap_status
must be aligned to alignof(time_t), which leads the compiler to insert
four bytes of padding in struct snd_pcm_sync_ptr after 'flags' and a
corresponding change in the size of snd_pcm_sync_ptr itself. On x86-32
(and only there), the compiler doesn't use 64-bit alignment in structure,
so I'm adding an explicit pad in the structure that has no effect on the
existing 64-bit architectures but ensures that the layout matches for x86.
The snd_pcm_uframes_t type compatibility requires another hack: we can't
easily make that 64 bit wide, so I leave the type as 'unsigned long',
but add padding before and after it, to ensure that the data is properly
aligned to the respective 64-bit field in the in-kernel structure.
For the SNDRV_PCM_MMAP_OFFSET_STATUS/CONTROL constants that are used
as the virtual file offset in the mmap() function, we also have to
introduce new constants that depend on hte __USE_TIME_BITS64 macro:
The existing macros are renamed to SNDRV_PCM_MMAP_OFFSET_STATUS_OLD
and SNDRV_PCM_MMAP_OFFSET_CONTROL_OLD, they continue to work fine on
64-bit architectures, but stop working on native 32-bit user space.
The replacement _NEW constants are now used by default for user space
built with __USE_TIME_BITS64, those now work on all new kernels for x86,
ppc and alpha (32 and 64 bit, native and compat). It might be a good idea
for a future alsa-lib to support both the _OLD and _NEW macros and use
the corresponding structures directly. Unmodified alsa-lib source code
will retain the current behavior, so it will no longer be able to use
mmap() for the status/control structures on 32-bit systems, until either
the C library gets updated to 64-bit time_t or alsa-lib gets updated to
support both mmap() layouts.
Co-developed-with: Baolin Wang <baolin.wang@linaro.org>
Signed-off-by: Baolin Wang <baolin.wang@linaro.org>
Signed-off-by: Arnd Bergmann <arnd@arndb.de>
2018-04-24 20:06:15 +08:00
|
|
|
case SNDRV_PCM_MMAP_OFFSET_CONTROL_NEW:
|
2010-03-10 01:57:52 +08:00
|
|
|
return (unsigned long)runtime->control;
|
|
|
|
default:
|
|
|
|
return (unsigned long)runtime->dma_area + offset;
|
|
|
|
}
|
2008-09-03 16:54:36 +08:00
|
|
|
}
|
|
|
|
#else
|
2010-03-10 01:57:52 +08:00
|
|
|
# define snd_pcm_get_unmapped_area NULL
|
2008-09-03 16:54:36 +08:00
|
|
|
#endif
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
/*
|
|
|
|
* Register section
|
|
|
|
*/
|
|
|
|
|
2007-02-12 16:55:37 +08:00
|
|
|
const struct file_operations snd_pcm_f_ops[2] = {
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2005-11-20 21:03:48 +08:00
|
|
|
.owner = THIS_MODULE,
|
|
|
|
.write = snd_pcm_write,
|
2015-04-04 12:19:32 +08:00
|
|
|
.write_iter = snd_pcm_writev,
|
2005-11-20 21:06:59 +08:00
|
|
|
.open = snd_pcm_playback_open,
|
2005-11-20 21:03:48 +08:00
|
|
|
.release = snd_pcm_release,
|
2010-04-13 17:49:04 +08:00
|
|
|
.llseek = no_llseek,
|
2018-04-12 00:05:00 +08:00
|
|
|
.poll = snd_pcm_poll,
|
2017-08-30 21:39:32 +08:00
|
|
|
.unlocked_ioctl = snd_pcm_ioctl,
|
2005-11-20 21:03:48 +08:00
|
|
|
.compat_ioctl = snd_pcm_ioctl_compat,
|
|
|
|
.mmap = snd_pcm_mmap,
|
|
|
|
.fasync = snd_pcm_fasync,
|
2010-03-10 01:57:52 +08:00
|
|
|
.get_unmapped_area = snd_pcm_get_unmapped_area,
|
2005-04-17 06:20:36 +08:00
|
|
|
},
|
|
|
|
{
|
2005-11-20 21:03:48 +08:00
|
|
|
.owner = THIS_MODULE,
|
|
|
|
.read = snd_pcm_read,
|
2015-04-04 12:19:32 +08:00
|
|
|
.read_iter = snd_pcm_readv,
|
2005-11-20 21:06:59 +08:00
|
|
|
.open = snd_pcm_capture_open,
|
2005-11-20 21:03:48 +08:00
|
|
|
.release = snd_pcm_release,
|
2010-04-13 17:49:04 +08:00
|
|
|
.llseek = no_llseek,
|
2018-04-12 00:05:00 +08:00
|
|
|
.poll = snd_pcm_poll,
|
2017-08-30 21:39:32 +08:00
|
|
|
.unlocked_ioctl = snd_pcm_ioctl,
|
2005-11-20 21:03:48 +08:00
|
|
|
.compat_ioctl = snd_pcm_ioctl_compat,
|
|
|
|
.mmap = snd_pcm_mmap,
|
|
|
|
.fasync = snd_pcm_fasync,
|
2010-03-10 01:57:52 +08:00
|
|
|
.get_unmapped_area = snd_pcm_get_unmapped_area,
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
};
|