[alsa-devel] [PATCH v3 kernel 3/3] snd_pcm_start_at implementation

Tim Cussins timcussins at eml.cc
Fri Feb 6 17:16:28 CET 2015


- posix clocks require hi-res timers. If the kernel is configured without,
  start_at_* will return -ENOSYS.

- If there is a pending startat timer, relevant info is copied to the
  snd_pcm_status structure.

- snd_pcm_start_at_{register,unregister} are provided for the convenience
  of drivers implementing start_at.

- start_at can be called in any state: If the stream isn't in an appropriate
  state when the timer expires, the start_at fails.

- The startat timer is cancelled when the stream is released.

Signed-off-by: Tim Cussins <timcussins at eml.cc>

diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c
index 932234d..af808d9 100644
--- a/sound/core/pcm_native.c
+++ b/sound/core/pcm_native.c
@@ -35,6 +35,9 @@
 #include <sound/pcm_params.h>
 #include <sound/timer.h>
 #include <sound/minors.h>
+#if defined(CONFIG_HIGH_RES_TIMERS)
+#include <linux/hrtimer.h>
+#endif
 
 /*
  *  Compatibility
@@ -709,6 +712,16 @@ int snd_pcm_status(struct snd_pcm_substream *substream,
 	snd_pcm_stream_lock_irq(substream);
 	status->state = runtime->status->state;
 	status->suspended_state = runtime->status->suspended_state;
+
+	if (runtime->startat_timer_running) {
+		status->startat_pending = 1;
+		status->startat_clock_type = runtime->startat_clock_type;
+		status->startat_start_time = runtime->startat_start_time;
+	}
+	else {
+		status->startat_pending = 0;
+	}
+
 	if (status->state == SNDRV_PCM_STATE_OPEN)
 		goto _end;
 	status->trigger_tstamp = runtime->trigger_tstamp;
@@ -1016,6 +1029,251 @@ static struct action_ops snd_pcm_action_start = {
 	.post_action = snd_pcm_post_start
 };
 
+void snd_pcm_start_at_register(struct snd_pcm_substream *substream, int clock_type, const struct timespec *start_time, void *data)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	runtime->startat_timer_running = true;
+	runtime->startat_timer_data = data;
+	runtime->startat_clock_type = clock_type;
+	runtime->startat_start_time = *start_time;
+}
+
+EXPORT_SYMBOL(snd_pcm_start_at_register);
+
+void snd_pcm_start_at_unregister(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	runtime->startat_timer_running = false;
+	runtime->startat_timer_data = NULL;
+}
+
+EXPORT_SYMBOL(snd_pcm_start_at_unregister);
+
+#ifdef CONFIG_HIGH_RES_TIMERS
+/*
+ * hrtimer interface
+ */
+
+struct hrtimer_pcm {
+   struct hrtimer timer;
+   struct snd_pcm_substream *substream;
+};
+
+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;
+
+	snd_pcm_stream_lock(substream);
+
+	snd_pcm_start_at_unregister(substream);
+
+	ret = snd_pcm_do_start(substream, SNDRV_PCM_STATE_RUNNING);
+	if (ret == 0) {
+		snd_pcm_post_start(substream, SNDRV_PCM_STATE_RUNNING);
+	}
+
+	snd_pcm_stream_unlock(substream);
+
+	return HRTIMER_NORESTART;
+}
+#endif
+
+static int snd_pcm_start_at_system(struct snd_pcm_substream *substream, int clock_type, const struct timespec *start_time)
+{
+#ifdef CONFIG_HIGH_RES_TIMERS
+	struct hrtimer_pcm *pcm_timer;
+	struct timespec now;
+	int ret;
+	clockid_t clock;
+
+	switch (clock_type) {
+	case SNDRV_PCM_STARTAT_CLOCK_TYPE_GETTIMEOFDAY:
+		clock = CLOCK_REALTIME;
+		getnstimeofday(&now);
+		break;
+	case SNDRV_PCM_STARTAT_CLOCK_TYPE_MONOTONIC:
+		clock = CLOCK_MONOTONIC;
+		ktime_get_ts(&now);
+		break;
+	default: /* unsupported clocks bounce off */
+		return -ENOSYS;
+	}
+
+	/* Check if start_time is in the past */
+	if (timespec_compare(&now, start_time) >= 0) {
+		return -ETIME;
+	}
+
+	/* 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 */
+	snd_pcm_start_at_register(substream, clock_type, start_time, 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->startat_timer_data;
+	hrtimer_cancel(&pcm_timer->timer); /* Cancel existing timer. (NOP if it's not running) */
+	snd_pcm_start_at_unregister(substream);
+	kfree(pcm_timer);
+	return 0;
+#else
+	return -ENOSYS;
+#endif
+}
+
+static int snd_pcm_start_at_audio(struct snd_pcm_substream *substream, int clock_type, const struct timespec *start_time)
+{
+	if (substream->ops->start_at)
+		return substream->ops->start_at(substream, clock_type, start_time);
+	else
+		return -ENOSYS;
+}
+
+static int snd_pcm_start_at_audio_cancel(struct snd_pcm_substream *substream)
+{
+	if (substream->ops->start_at_abort)
+		return substream->ops->start_at_abort(substream);
+	else
+		return -ENOSYS;
+}
+
+// call this with stream locked
+static int snd_pcm_start_at_cancel(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int ret = 0;
+
+	if (runtime->startat_timer_running) {
+		switch (runtime->startat_clock_type) {
+		case SNDRV_PCM_STARTAT_CLOCK_TYPE_GETTIMEOFDAY:
+		case SNDRV_PCM_STARTAT_CLOCK_TYPE_MONOTONIC:
+			ret = snd_pcm_start_at_system_cancel(substream);
+			break;
+		case SNDRV_PCM_STARTAT_CLOCK_TYPE_LINK:
+			ret = snd_pcm_start_at_audio_cancel(substream);
+			break;
+		default:
+			ret = -ENOSYS;
+		}
+	}
+	return ret;
+}
+
+static int snd_pcm_start_at_gettime(struct snd_pcm_substream *substream, int clock_type, struct timespec *current_time)
+{
+	int ret = 0;
+
+	switch (clock_type)
+	{
+	case SNDRV_PCM_STARTAT_CLOCK_TYPE_GETTIMEOFDAY:
+		getnstimeofday(current_time);
+		break;
+	case SNDRV_PCM_STARTAT_CLOCK_TYPE_MONOTONIC:
+		ktime_get_ts(current_time);
+		break;
+	case SNDRV_PCM_STARTAT_CLOCK_TYPE_LINK:
+		if (substream->ops->start_at_gettime)
+			ret = substream->ops->start_at_gettime(substream, clock_type, current_time);
+		else
+			ret = -ENOSYS;
+		break;
+	default:
+		ret = -EINVAL;
+	}
+	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;
+
+	snd_pcm_stream_lock(substream);
+
+	switch (start_at.op)
+	{
+	case SNDRV_PCM_STARTAT_OP_SET:
+		if (!timespec_valid(&start_at.args.set.start_time)) {
+			ret = -EINVAL;
+			break;
+		}
+
+		/*  If not a playback substream, give up */
+		if (substream->stream != SNDRV_PCM_STREAM_PLAYBACK) {
+			ret = -EINVAL;
+			break;
+		}
+
+		switch (start_at.args.set.clock_type)
+		{
+		case SNDRV_PCM_STARTAT_CLOCK_TYPE_GETTIMEOFDAY:
+		case SNDRV_PCM_STARTAT_CLOCK_TYPE_MONOTONIC:
+			ret = snd_pcm_start_at_system(substream,
+				start_at.args.set.clock_type, &start_at.args.set.start_time);
+			break;
+		case SNDRV_PCM_STARTAT_CLOCK_TYPE_LINK:
+			ret = snd_pcm_start_at_audio(substream,
+				start_at.args.set.clock_type, &start_at.args.set.start_time);
+			break;
+		default:
+			ret = -EINVAL;
+		}
+		break;
+	case SNDRV_PCM_STARTAT_OP_CANCEL:
+		ret = snd_pcm_start_at_cancel(substream);
+		break;
+	case SNDRV_PCM_STARTAT_OP_STATUS:
+		ret = snd_pcm_start_at_gettime(substream, start_at.args.status.clock_type, &start_at.args.status.current_time);
+		if (ret == 0)
+			ret = copy_to_user(_start_at, &start_at, sizeof(start_at));
+		break;
+	default:
+		ret = -EINVAL;
+	}
+
+	snd_pcm_stream_unlock(substream);
+	return ret;
+}
+
 /**
  * snd_pcm_start - start all linked streams
  * @substream: the PCM substream instance
@@ -2184,6 +2442,8 @@ void snd_pcm_release_substream(struct snd_pcm_substream *substream)
 	if (substream->ref_count > 0)
 		return;
 
+	snd_pcm_start_at_cancel(substream);
+
 	snd_pcm_drop(substream);
 	if (substream->hw_opened) {
 		if (substream->ops->hw_free != NULL)
@@ -2729,6 +2989,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:
-- 
1.9.1



More information about the Alsa-devel mailing list