[alsa-devel] [PATCH v2 3/3] snd_pcm_start_at: Implement snd_pcm_start_at for system and audio tstamp types.
Tim Cussins
timcussins at eml.cc
Wed Dec 17 18:29:25 CET 2014
System tstamp types rely on high-res timers.
Audio tstamp types delegate to the pcm driver.
Current start_at timer is cancelled on attempt to change stream state.
Signed-off-by: Tim Cussins <timcussins at eml.cc>
diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c
index 095d957..0ca2818 100644
--- a/sound/core/pcm_native.c
+++ b/sound/core/pcm_native.c
@@ -35,6 +35,9 @@
#include <sound/timer.h>
#include <sound/minors.h>
#include <asm/io.h>
+#if defined(CONFIG_HIGH_RES_TIMERS)
+#include <linux/hrtimer.h>
+#endif
/*
* Compatibility
@@ -67,6 +70,8 @@ static int snd_pcm_hw_params_old_user(struct snd_pcm_substream *substream,
#endif
static int snd_pcm_open(struct file *file, struct snd_pcm *pcm, int stream);
+static int snd_pcm_start_at_cancel(struct snd_pcm_substream *substream);
+
/*
*
*/
@@ -834,6 +839,11 @@ static int snd_pcm_action_group(struct action_ops *ops,
struct snd_pcm_substream *s1;
int res = 0, depth = 1;
+ /* Any attempt to change state cancels a pending start_at timer */
+ res = snd_pcm_start_at_cancel(substream);
+ if (res < 0)
+ return res;
+
snd_pcm_group_for_each_entry(s, substream) {
if (do_lock && s != substream) {
if (s->pcm->nonatomic)
@@ -889,6 +899,11 @@ static int snd_pcm_action_single(struct action_ops *ops,
{
int res;
+ /* Any attempt to change state cancels a pending start_at timer */
+ res = snd_pcm_start_at_cancel(substream);
+ if (res < 0)
+ return res;
+
res = ops->pre_action(substream, state);
if (res < 0)
return res;
@@ -1015,6 +1030,191 @@ static struct action_ops snd_pcm_action_start = {
.post_action = snd_pcm_post_start
};
+
+#ifdef CONFIG_HIGH_RES_TIMERS
+/*
+ * hrtimer interface
+ */
+
+struct hrtimer_pcm {
+ struct hrtimer timer;
+ struct snd_pcm_substream *substream;
+};
+
+/*
+ * called from a hard irq context - no need for locks.
+ * only problem is that the caller might have gone away and closed the substream
+ * before the timer expires.
+ */
+enum hrtimer_restart snd_pcm_do_start_time(struct hrtimer *timer)
+{
+ struct hrtimer_pcm *pcm_timer;
+ struct snd_pcm_substream *substream;
+ int ret;
+
+ pcm_timer = container_of(timer, struct hrtimer_pcm, timer);
+ substream = pcm_timer->substream;
+
+ ret = snd_pcm_do_start(substream, SNDRV_PCM_STATE_RUNNING);
+ if (ret == 0) {
+ snd_pcm_post_start(substream, SNDRV_PCM_STATE_RUNNING);
+ }
+ return HRTIMER_NORESTART;
+}
+#endif
+
+static int snd_pcm_start_at_system(struct snd_pcm_substream *substream, int system_tstamp_type, const struct timespec *start_time)
+{
+#ifdef CONFIG_HIGH_RES_TIMERS
+ struct hrtimer_pcm *pcm_timer;
+ struct timespec now;
+ int ret;
+ clockid_t clock;
+
+ /* Get time now and check if start_time is in the past */
+ snd_pcm_gettime(substream->runtime, &now);
+ if (timespec_compare(&now, start_time) >= 0) {
+ return -ETIME;
+ }
+
+ switch (system_tstamp_type) {
+ case SNDRV_PCM_TSTAMP_TYPE_GETTIMEOFDAY:
+ clock = CLOCK_REALTIME;
+ break;
+ case SNDRV_PCM_TSTAMP_TYPE_MONOTONIC:
+ clock = CLOCK_MONOTONIC;
+ break;
+ case SNDRV_PCM_TSTAMP_TYPE_MONOTONIC_RAW:
+ default: /* unsupported clocks bounce off */
+ return -ENOSYS;
+ }
+
+ /* Allocate a hrtimer to handle the start_at */
+ pcm_timer = kmalloc(sizeof(*pcm_timer), GFP_KERNEL);
+ if (!pcm_timer)
+ return -ENOMEM;
+
+ hrtimer_init(&pcm_timer->timer, clock, HRTIMER_MODE_ABS);
+
+ /* Setup timer */
+ pcm_timer->timer.function = snd_pcm_do_start_time;
+ pcm_timer->substream = substream;
+
+ /* Store timer in runtime start_at info */
+ substream->runtime->start_at_timer_data = pcm_timer;
+
+ /* Pre start */
+ ret = snd_pcm_pre_start(substream, SNDRV_PCM_STATE_PREPARED);
+ if (ret < 0)
+ goto error;
+
+ ret = hrtimer_start(&pcm_timer->timer, timespec_to_ktime(*start_time), HRTIMER_MODE_ABS);
+ if (ret < 0 )
+ goto error;
+
+ return 0;
+error:
+ kfree(pcm_timer);
+ return ret;
+#else
+ return -ENOSYS;
+#endif
+}
+
+static int snd_pcm_start_at_system_cancel(struct snd_pcm_substream *substream)
+{
+#ifdef CONFIG_HIGH_RES_TIMERS
+ struct hrtimer_pcm *pcm_timer = substream->runtime->start_at_timer_data;
+ hrtimer_cancel(&pcm_timer->timer); /* Cancel existing timer. (NOP if it's not running) */
+ kfree(pcm_timer);
+ return 0;
+#else
+ return -ENOSYS;
+#endif
+}
+
+static int snd_pcm_start_at_audio(struct snd_pcm_substream *substream, int audio_tstamp_type, const struct timespec *start_time)
+{
+ if (substream->ops->start_at)
+ return substream->ops->start_at(substream, audio_tstamp_type, start_time);
+ else
+ return -ENOSYS;
+}
+
+static int snd_pcm_start_at_audio_cancel(struct snd_pcm_substream *substream)
+{
+ if (substream->ops->start_at_cancel)
+ return substream->ops->start_at_cancel(substream);
+ else
+ return -ENOSYS;
+}
+
+/* snd_pcm_start_at_cancel() allows state-transition code to conveniently cancel the pending timer */
+static int snd_pcm_start_at_cancel(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ int ret = 0;
+
+ if (runtime->start_at_timer_running) {
+ switch (runtime->start_at_timer_tstamp_class) {
+ case SNDRV_PCM_TSTAMP_CLASS_SYSTEM:
+ ret = snd_pcm_start_at_system_cancel(substream);
+ break;
+ case SNDRV_PCM_TSTAMP_CLASS_AUDIO:
+ ret = snd_pcm_start_at_audio_cancel(substream);
+ break;
+ default:
+ ret = -ENOSYS;
+ }
+ }
+
+ if (ret == 0) {
+ runtime->start_at_timer_running = 0;
+ }
+
+ return ret;
+}
+
+int snd_pcm_start_at(struct snd_pcm_substream *substream,
+ struct snd_start_at __user *_start_at)
+{
+ struct snd_start_at start_at;
+ int ret;
+
+ if (copy_from_user(&start_at, _start_at, sizeof(start_at)))
+ return -EFAULT;
+
+ if (!timespec_valid(&start_at.start_time))
+ return -EINVAL;
+
+ /* If not a playback substream, give up */
+ if (substream->stream != SNDRV_PCM_STREAM_PLAYBACK)
+ return -EINVAL;
+
+ /* Cancel any existing timer */
+ ret = snd_pcm_start_at_cancel(substream);
+ if (ret < 0)
+ return ret;
+
+ switch (start_at.tstamp_class) {
+ case SNDRV_PCM_TSTAMP_CLASS_SYSTEM:
+ ret = snd_pcm_start_at_system(substream, start_at.tstamp_type, &start_at.start_time);
+ break;
+ case SNDRV_PCM_TSTAMP_CLASS_AUDIO:
+ ret = snd_pcm_start_at_audio(substream, start_at.tstamp_type, &start_at.start_time);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (ret == 0) {
+ substream->runtime->start_at_timer_tstamp_class = start_at.tstamp_class;
+ substream->runtime->start_at_timer_running = 1;
+ }
+
+ return ret;
+}
+
/**
* snd_pcm_start - start all linked streams
* @substream: the PCM substream instance
@@ -2721,6 +2921,8 @@ static int snd_pcm_common_ioctl1(struct file *file,
return snd_pcm_action_lock_irq(&snd_pcm_action_start, substream, SNDRV_PCM_STATE_RUNNING);
case SNDRV_PCM_IOCTL_LINK:
return snd_pcm_link(substream, (int)(unsigned long) arg);
+ case SNDRV_PCM_IOCTL_START_AT:
+ return snd_pcm_start_at(substream, arg);
case SNDRV_PCM_IOCTL_UNLINK:
return snd_pcm_unlink(substream);
case SNDRV_PCM_IOCTL_RESUME:
--
2.1.0
More information about the Alsa-devel
mailing list