Discussion:
[RFC] ALSA: provide a simple mechanism to start playback at a given time
Nick Stoughton
2014-10-14 08:29:31 UTC
Permalink
Initial implementation / Request For Comment.

Given an absolute time based on a given clock (CLOCK_MONOTONIC,
CLOCK_MONOTONIC_RAW, CLOCK_REALTIME etc), setup a high resolution timer
to cause playback to be triggered at that time.
---
include/linux/timekeeping.h | 3 ++
include/uapi/sound/asound.h | 6 +++
kernel/time/timekeeping.c | 16 ++++++++
sound/core/pcm_native.c | 99 +++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 124 insertions(+)

diff --git a/include/linux/timekeeping.h b/include/linux/timekeeping.h
index 1caa6b0..74eb6b2 100644
--- a/include/linux/timekeeping.h
+++ b/include/linux/timekeeping.h
@@ -32,6 +32,9 @@ extern void ktime_get_ts64(struct timespec64 *ts);
extern int __getnstimeofday64(struct timespec64 *tv);
extern void getnstimeofday64(struct timespec64 *tv);

+/* Get the offset between monotonic and monotonic raw clocks */
+extern ktime_t ktime_get_raw_offset(void);
+
#if BITS_PER_LONG == 64
static inline int __getnstimeofday(struct timespec *ts)
{
diff --git a/include/uapi/sound/asound.h b/include/uapi/sound/asound.h
index 6ee5867..c7aa88c 100644
--- a/include/uapi/sound/asound.h
+++ b/include/uapi/sound/asound.h
@@ -462,6 +462,11 @@ struct snd_xfern {
snd_pcm_uframes_t frames;
};

+struct snd_clock_time {
+ clockid_t clock;
+ struct timespec time;
+};
+
enum {
SNDRV_PCM_TSTAMP_TYPE_GETTIMEOFDAY = 0, /* gettimeofday equivalent */
SNDRV_PCM_TSTAMP_TYPE_MONOTONIC, /* posix_clock_monotonic equivalent */
@@ -547,6 +552,7 @@ enum {
#define SNDRV_PCM_IOCTL_READN_FRAMES _IOR('A', 0x53, struct snd_xfern)
#define SNDRV_PCM_IOCTL_LINK _IOW('A', 0x60, int)
#define SNDRV_PCM_IOCTL_UNLINK _IO('A', 0x61)
+#define SNDRV_PCM_IOCTL_START_AT _IOR('A', 0x62, struct snd_clock_time)

/*****************************************************************************
* *
diff --git a/kernel/time/timekeeping.c b/kernel/time/timekeeping.c
index ec1791f..3d5d0bc 100644
--- a/kernel/time/timekeeping.c
+++ b/kernel/time/timekeeping.c
@@ -789,6 +789,22 @@ error: /* even if we error out, we forwarded the time, so call update */
}
EXPORT_SYMBOL(timekeeping_inject_offset);

+/**
+ * ktime_get_raw_offset - get the offset in ktime format between
+ * the monotonic_raw clock and the monotonic clock
+ */
+ktime_t ktime_get_raw_offset(void)
+{
+ struct timespec rawtime = {0,0};
+ struct timespec now = {0,0};
+ struct timespec delta;
+
+ ktime_get_ts(&now);
+ getrawmonotonic(&rawtime);
+ delta = timespec_sub(now, rawtime);
+ return timespec_to_ktime(delta);
+}
+EXPORT_SYMBOL(ktime_get_raw_offset);

/**
* timekeeping_get_tai_offset - Returns current TAI offset from UTC
diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c
index 85fe1a2..1121127 100644
--- a/sound/core/pcm_native.c
+++ b/sound/core/pcm_native.c
@@ -27,6 +27,7 @@
#include <linux/pm_qos.h>
#include <linux/aio.h>
#include <linux/dma-mapping.h>
+#include <linux/time.h>
#include <sound/core.h>
#include <sound/control.h>
#include <sound/info.h>
@@ -38,6 +39,9 @@
#if defined(CONFIG_MIPS) && defined(CONFIG_DMA_NONCOHERENT)
#include <dma-coherence.h>
#endif
+#if defined(CONFIG_HIGH_RES_TIMERS)
+#include <linux/hrtimer.h>
+#endif

/*
* Compatibility
@@ -1016,6 +1020,99 @@ 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;
+
+ if (substream->runtime) {
+ ret = snd_pcm_do_start(substream, SNDRV_PCM_STATE_RUNNING);
+ if (ret == 0) {
+ snd_pcm_post_start(substream, SNDRV_PCM_STATE_RUNNING);
+ }
+ }
+ kfree(pcm_timer);
+
+ return HRTIMER_NORESTART;
+}
+
+int snd_pcm_start_at(struct snd_pcm_substream *substream,
+ struct snd_clock_time __user *_start_time)
+{
+ struct hrtimer_pcm *pcm_timer;
+ struct snd_clock_time start_time;
+ int clock;
+ ktime_t start_kt;
+ ktime_t raw_offset;
+ int ret;
+
+ if (copy_from_user(&start_time, _start_time, sizeof(start_time)))
+ return -EFAULT;
+
+ if (!timespec_valid(&start_time.time))
+ return -EINVAL;
+
+ start_kt = timespec_to_ktime(start_time.time);
+ clock = start_time.clock;
+ if (start_time.clock == CLOCK_MONOTONIC_RAW) {
+ raw_offset = ktime_get_raw_offset();
+
+ /* convert to clock monotonic */
+ start_kt = ktime_add(raw_offset, start_kt);
+ clock = CLOCK_MONOTONIC;
+ }
+
+ /* if not playback substream or less than 100 ms give up */
+ if (substream->stream != SNDRV_PCM_STREAM_PLAYBACK ||
+ (ktime_compare(start_kt, ktime_add_ns(ktime_get(), 100000000)) <= 0)) {
+ return snd_pcm_action_lock_irq(&snd_pcm_action_start,
+ substream, SNDRV_PCM_STATE_RUNNING);
+ }
+
+ ret = snd_pcm_pre_start(substream, SNDRV_PCM_STATE_PREPARED);
+ if (ret != 0) {
+ return ret;
+ }
+
+ pcm_timer = kmalloc(sizeof(*pcm_timer), GFP_KERNEL);
+ hrtimer_init(&pcm_timer->timer, clock, HRTIMER_MODE_ABS);
+
+ pcm_timer->timer.function = snd_pcm_do_start_time;
+ pcm_timer->substream = substream;
+
+ hrtimer_start(&pcm_timer->timer, start_kt, HRTIMER_MODE_ABS);
+
+ return 0;
+}
+#else
+/* without high res time, interface is identical to snd_pcm_start() */
+int snd_pcm_start_at(struct snd_pcm_substream *substream,
+ struct snd_clock_time __user *_start_time)
+{
+ return snd_pcm_action_lock_irq(&snd_pcm_action_start,
+ substream, SNDRV_PCM_STATE_RUNNING);
+}
+#endif
+
/**
* snd_pcm_start - start all linked streams
* @substream: the PCM substream instance
@@ -2698,6 +2795,8 @@ static int snd_pcm_common_ioctl1(struct file *file,
return snd_pcm_reset(substream);
case SNDRV_PCM_IOCTL_START:
return snd_pcm_action_lock_irq(&snd_pcm_action_start, substream, SNDRV_PCM_STATE_RUNNING);
+ case SNDRV_PCM_IOCTL_START_AT:
+ return snd_pcm_start_at(substream, arg);
case SNDRV_PCM_IOCTL_LINK:
return snd_pcm_link(substream, (int)(unsigned long) arg);
case SNDRV_PCM_IOCTL_UNLINK:
--
1.9.3
Tim Cussins
2014-10-16 10:53:07 UTC
Permalink
Hi again Nick!
Post by Nick Stoughton
Initial implementation / Request For Comment.
Given an absolute time based on a given clock (CLOCK_MONOTONIC,
CLOCK_MONOTONIC_RAW, CLOCK_REALTIME etc), setup a high resolution timer
to cause playback to be triggered at that time.
---
include/linux/timekeeping.h | 3 ++
include/uapi/sound/asound.h | 6 +++
kernel/time/timekeeping.c | 16 ++++++++
sound/core/pcm_native.c | 99 +++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 124 insertions(+)
diff --git a/include/linux/timekeeping.h b/include/linux/timekeeping.h
index 1caa6b0..74eb6b2 100644
--- a/include/linux/timekeeping.h
+++ b/include/linux/timekeeping.h
@@ -32,6 +32,9 @@ extern void ktime_get_ts64(struct timespec64 *ts);
extern int __getnstimeofday64(struct timespec64 *tv);
extern void getnstimeofday64(struct timespec64 *tv);
+/* Get the offset between monotonic and monotonic raw clocks */
+extern ktime_t ktime_get_raw_offset(void);
+
#if BITS_PER_LONG == 64
static inline int __getnstimeofday(struct timespec *ts)
{
diff --git a/include/uapi/sound/asound.h b/include/uapi/sound/asound.h
index 6ee5867..c7aa88c 100644
--- a/include/uapi/sound/asound.h
+++ b/include/uapi/sound/asound.h
@@ -462,6 +462,11 @@ struct snd_xfern {
snd_pcm_uframes_t frames;
};
+struct snd_clock_time {
+ clockid_t clock;
+ struct timespec time;
+};
+
Takashi added snd_pcm_tstamp_type_t in July - If the final start_at
patch includes some "struct snd_clock_time", then it'd probably use
Takashi's type in favour of a raw clockid_t.

I like the idea of a single struct that includes the context for the
timespec btw. Cool.
Post by Nick Stoughton
enum {
SNDRV_PCM_TSTAMP_TYPE_GETTIMEOFDAY = 0, /* gettimeofday equivalent */
SNDRV_PCM_TSTAMP_TYPE_MONOTONIC, /* posix_clock_monotonic equivalent */
@@ -547,6 +552,7 @@ enum {
#define SNDRV_PCM_IOCTL_READN_FRAMES _IOR('A', 0x53, struct snd_xfern)
#define SNDRV_PCM_IOCTL_LINK _IOW('A', 0x60, int)
#define SNDRV_PCM_IOCTL_UNLINK _IO('A', 0x61)
+#define SNDRV_PCM_IOCTL_START_AT _IOR('A', 0x62, struct snd_clock_time)
/*****************************************************************************
* *
diff --git a/kernel/time/timekeeping.c b/kernel/time/timekeeping.c
index ec1791f..3d5d0bc 100644
--- a/kernel/time/timekeeping.c
+++ b/kernel/time/timekeeping.c
@@ -789,6 +789,22 @@ error: /* even if we error out, we forwarded the time, so call update */
}
EXPORT_SYMBOL(timekeeping_inject_offset);
+/**
+ * ktime_get_raw_offset - get the offset in ktime format between
+ * the monotonic_raw clock and the monotonic clock
+ */
+ktime_t ktime_get_raw_offset(void)
+{
+ struct timespec rawtime = {0,0};
+ struct timespec now = {0,0};
+ struct timespec delta;
+
+ ktime_get_ts(&now);
+ getrawmonotonic(&rawtime);
+ delta = timespec_sub(now, rawtime);
+ return timespec_to_ktime(delta);
+}
+EXPORT_SYMBOL(ktime_get_raw_offset);
/**
* timekeeping_get_tai_offset - Returns current TAI offset from UTC
diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c
index 85fe1a2..1121127 100644
--- a/sound/core/pcm_native.c
+++ b/sound/core/pcm_native.c
@@ -27,6 +27,7 @@
#include <linux/pm_qos.h>
#include <linux/aio.h>
#include <linux/dma-mapping.h>
+#include <linux/time.h>
#include <sound/core.h>
#include <sound/control.h>
#include <sound/info.h>
@@ -38,6 +39,9 @@
#if defined(CONFIG_MIPS) && defined(CONFIG_DMA_NONCOHERENT)
#include <dma-coherence.h>
#endif
+#if defined(CONFIG_HIGH_RES_TIMERS)
+#include <linux/hrtimer.h>
+#endif
/*
* Compatibility
@@ -1016,6 +1020,99 @@ 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;
+
+ if (substream->runtime) {
+ ret = snd_pcm_do_start(substream, SNDRV_PCM_STATE_RUNNING);
+ if (ret == 0) {
+ snd_pcm_post_start(substream, SNDRV_PCM_STATE_RUNNING);
+ }
+ }
+ kfree(pcm_timer);
+
+ return HRTIMER_NORESTART;
+}
+
+int snd_pcm_start_at(struct snd_pcm_substream *substream,
+ struct snd_clock_time __user *_start_time)
+{
+ struct hrtimer_pcm *pcm_timer;
+ struct snd_clock_time start_time;
+ int clock;
+ ktime_t start_kt;
+ ktime_t raw_offset;
+ int ret;
+
+ if (copy_from_user(&start_time, _start_time, sizeof(start_time)))
+ return -EFAULT;
+
+ if (!timespec_valid(&start_time.time))
+ return -EINVAL;
+
+ start_kt = timespec_to_ktime(start_time.time);
+ clock = start_time.clock;
+ if (start_time.clock == CLOCK_MONOTONIC_RAW) {
+ raw_offset = ktime_get_raw_offset();
+
+ /* convert to clock monotonic */
+ start_kt = ktime_add(raw_offset, start_kt);
+ clock = CLOCK_MONOTONIC;
+ }
+
+ /* if not playback substream or less than 100 ms give up */
+ if (substream->stream != SNDRV_PCM_STREAM_PLAYBACK ||
+ (ktime_compare(start_kt, ktime_add_ns(ktime_get(), 100000000)) <= 0)) {
+ return snd_pcm_action_lock_irq(&snd_pcm_action_start,
+ substream, SNDRV_PCM_STATE_RUNNING);
+ }
+
+ ret = snd_pcm_pre_start(substream, SNDRV_PCM_STATE_PREPARED);
+ if (ret != 0) {
+ return ret;
+ }
+
+ pcm_timer = kmalloc(sizeof(*pcm_timer), GFP_KERNEL);
+ hrtimer_init(&pcm_timer->timer, clock, HRTIMER_MODE_ABS);
+
+ pcm_timer->timer.function = snd_pcm_do_start_time;
+ pcm_timer->substream = substream;
+
+ hrtimer_start(&pcm_timer->timer, start_kt, HRTIMER_MODE_ABS);
+
+ return 0;
+}
+#else
+/* without high res time, interface is identical to snd_pcm_start() */
+int snd_pcm_start_at(struct snd_pcm_substream *substream,
+ struct snd_clock_time __user *_start_time)
+{
+ return snd_pcm_action_lock_irq(&snd_pcm_action_start,
+ substream, SNDRV_PCM_STATE_RUNNING);
+}
+#endif
+
I'd prefer to return an error code when snd_pcm_start_at cannot be
supported, rather than lying to userspace that it will be satisfied :)

There might be a number of reasons that snd_pcm_start_at cannot be
satisfied: unsupported clock-type, timespec in the past, incompatible
kernel config are just three.

While one might argue that start_at is a "best effort" mechanism, on
principle the function should make every effort to return an error when
it would be meaningful (read: handleable) by userspace. Not sure how you
feel about that ... :)
Post by Nick Stoughton
/**
* snd_pcm_start - start all linked streams
@@ -2698,6 +2795,8 @@ static int snd_pcm_common_ioctl1(struct file *file,
return snd_pcm_reset(substream);
return snd_pcm_action_lock_irq(&snd_pcm_action_start, substream, SNDRV_PCM_STATE_RUNNING);
+ return snd_pcm_start_at(substream, arg);
return snd_pcm_link(substream, (int)(unsigned long) arg);
I'm really not qualified to pass comment on the kernel half of start_at:
I'll have to some back to that, or defer to others...

Cheers,
Tim
Takashi Iwai
2014-10-16 14:34:49 UTC
Permalink
At Thu, 16 Oct 2014 11:53:07 +0100,
Post by Tim Cussins
Hi again Nick!
Post by Nick Stoughton
Initial implementation / Request For Comment.
Given an absolute time based on a given clock (CLOCK_MONOTONIC,
CLOCK_MONOTONIC_RAW, CLOCK_REALTIME etc), setup a high resolution timer
to cause playback to be triggered at that time.
---
include/linux/timekeeping.h | 3 ++
include/uapi/sound/asound.h | 6 +++
kernel/time/timekeeping.c | 16 ++++++++
sound/core/pcm_native.c | 99 +++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 124 insertions(+)
diff --git a/include/linux/timekeeping.h b/include/linux/timekeeping.h
index 1caa6b0..74eb6b2 100644
--- a/include/linux/timekeeping.h
+++ b/include/linux/timekeeping.h
@@ -32,6 +32,9 @@ extern void ktime_get_ts64(struct timespec64 *ts);
extern int __getnstimeofday64(struct timespec64 *tv);
extern void getnstimeofday64(struct timespec64 *tv);
+/* Get the offset between monotonic and monotonic raw clocks */
+extern ktime_t ktime_get_raw_offset(void);
+
#if BITS_PER_LONG == 64
static inline int __getnstimeofday(struct timespec *ts)
{
diff --git a/include/uapi/sound/asound.h b/include/uapi/sound/asound.h
index 6ee5867..c7aa88c 100644
--- a/include/uapi/sound/asound.h
+++ b/include/uapi/sound/asound.h
@@ -462,6 +462,11 @@ struct snd_xfern {
snd_pcm_uframes_t frames;
};
+struct snd_clock_time {
+ clockid_t clock;
+ struct timespec time;
+};
+
Takashi added snd_pcm_tstamp_type_t in July - If the final start_at
patch includes some "struct snd_clock_time", then it'd probably use
Takashi's type in favour of a raw clockid_t.
Yeah, that would make things more consistent. But...
Post by Tim Cussins
I like the idea of a single struct that includes the context for the
timespec btw. Cool.
... thinking of this again, can we drop specifying the type here but
assumes to be same as the pre-given timestamp type? Then we'd just
need to pass timespec to ioctl.

And, if we introduce a new PTP or DEVICE_SPECIFIC timestamp type as
we've discussed, these need to provide the wakeup / callback at the
programmed time. In the latter case, the device driver should provide
it (hmm, so the start_at() would end up calling the callback?) This
needs to be sorted out a bit better...
Post by Tim Cussins
Post by Nick Stoughton
enum {
SNDRV_PCM_TSTAMP_TYPE_GETTIMEOFDAY = 0, /* gettimeofday equivalent */
SNDRV_PCM_TSTAMP_TYPE_MONOTONIC, /* posix_clock_monotonic equivalent */
@@ -547,6 +552,7 @@ enum {
#define SNDRV_PCM_IOCTL_READN_FRAMES _IOR('A', 0x53, struct snd_xfern)
#define SNDRV_PCM_IOCTL_LINK _IOW('A', 0x60, int)
#define SNDRV_PCM_IOCTL_UNLINK _IO('A', 0x61)
+#define SNDRV_PCM_IOCTL_START_AT _IOR('A', 0x62, struct snd_clock_time)
/*****************************************************************************
* *
diff --git a/kernel/time/timekeeping.c b/kernel/time/timekeeping.c
index ec1791f..3d5d0bc 100644
--- a/kernel/time/timekeeping.c
+++ b/kernel/time/timekeeping.c
@@ -789,6 +789,22 @@ error: /* even if we error out, we forwarded the time, so call update */
}
EXPORT_SYMBOL(timekeeping_inject_offset);
+/**
+ * ktime_get_raw_offset - get the offset in ktime format between
+ * the monotonic_raw clock and the monotonic clock
+ */
+ktime_t ktime_get_raw_offset(void)
+{
+ struct timespec rawtime = {0,0};
+ struct timespec now = {0,0};
+ struct timespec delta;
+
+ ktime_get_ts(&now);
+ getrawmonotonic(&rawtime);
+ delta = timespec_sub(now, rawtime);
+ return timespec_to_ktime(delta);
+}
+EXPORT_SYMBOL(ktime_get_raw_offset);
This part needs a review and comment from Thomas and others.
We can have this code snippet even in the PCM code without disturbing
the core side.
Post by Tim Cussins
Post by Nick Stoughton
* timekeeping_get_tai_offset - Returns current TAI offset from UTC
diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c
index 85fe1a2..1121127 100644
--- a/sound/core/pcm_native.c
+++ b/sound/core/pcm_native.c
@@ -27,6 +27,7 @@
#include <linux/pm_qos.h>
#include <linux/aio.h>
#include <linux/dma-mapping.h>
+#include <linux/time.h>
#include <sound/core.h>
#include <sound/control.h>
#include <sound/info.h>
@@ -38,6 +39,9 @@
#if defined(CONFIG_MIPS) && defined(CONFIG_DMA_NONCOHERENT)
#include <dma-coherence.h>
#endif
+#if defined(CONFIG_HIGH_RES_TIMERS)
+#include <linux/hrtimer.h>
+#endif
/*
* Compatibility
@@ -1016,6 +1020,99 @@ 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;
+
+ if (substream->runtime) {
+ ret = snd_pcm_do_start(substream, SNDRV_PCM_STATE_RUNNING);
+ if (ret == 0) {
+ snd_pcm_post_start(substream, SNDRV_PCM_STATE_RUNNING);
+ }
+ }
+ kfree(pcm_timer);
+
+ return HRTIMER_NORESTART;
+}
+
+int snd_pcm_start_at(struct snd_pcm_substream *substream,
+ struct snd_clock_time __user *_start_time)
+{
+ struct hrtimer_pcm *pcm_timer;
+ struct snd_clock_time start_time;
+ int clock;
+ ktime_t start_kt;
+ ktime_t raw_offset;
+ int ret;
+
+ if (copy_from_user(&start_time, _start_time, sizeof(start_time)))
+ return -EFAULT;
+
+ if (!timespec_valid(&start_time.time))
+ return -EINVAL;
+
+ start_kt = timespec_to_ktime(start_time.time);
+ clock = start_time.clock;
+ if (start_time.clock == CLOCK_MONOTONIC_RAW) {
+ raw_offset = ktime_get_raw_offset();
+
+ /* convert to clock monotonic */
+ start_kt = ktime_add(raw_offset, start_kt);
+ clock = CLOCK_MONOTONIC;
+ }
+
+ /* if not playback substream or less than 100 ms give up */
Is there any technical reason to give up for over longer schedule?
Post by Tim Cussins
Post by Nick Stoughton
+ if (substream->stream != SNDRV_PCM_STREAM_PLAYBACK ||
+ (ktime_compare(start_kt, ktime_add_ns(ktime_get(), 100000000)) <= 0)) {
+ return snd_pcm_action_lock_irq(&snd_pcm_action_start,
+ substream, SNDRV_PCM_STATE_RUNNING);
+ }
+
+ ret = snd_pcm_pre_start(substream, SNDRV_PCM_STATE_PREPARED);
+ if (ret != 0) {
+ return ret;
+ }
+
+ pcm_timer = kmalloc(sizeof(*pcm_timer), GFP_KERNEL);
+ hrtimer_init(&pcm_timer->timer, clock, HRTIMER_MODE_ABS);
+
+ pcm_timer->timer.function = snd_pcm_do_start_time;
+ pcm_timer->substream = substream;
+
+ hrtimer_start(&pcm_timer->timer, start_kt, HRTIMER_MODE_ABS);
+
+ return 0;
+}
+#else
+/* without high res time, interface is identical to snd_pcm_start() */
+int snd_pcm_start_at(struct snd_pcm_substream *substream,
+ struct snd_clock_time __user *_start_time)
+{
+ return snd_pcm_action_lock_irq(&snd_pcm_action_start,
+ substream, SNDRV_PCM_STATE_RUNNING);
+}
+#endif
+
I'd prefer to return an error code when snd_pcm_start_at cannot be
supported, rather than lying to userspace that it will be satisfied :)
There might be a number of reasons that snd_pcm_start_at cannot be
satisfied: unsupported clock-type, timespec in the past, incompatible
kernel config are just three.
While one might argue that start_at is a "best effort" mechanism, on
principle the function should make every effort to return an error when
it would be meaningful (read: handleable) by userspace. Not sure how you
feel about that ... :)
Yeah, I second that, too. (BTW, the function should be static.)


thanks,

Takashi
Nick Stoughton
2014-10-14 08:29:31 UTC
Permalink
Given an absolute time based on the clock used by the substream
setup a high resolution timer to cause playback to be triggered
at that time.

Signed-off-by: Nick Stoughton <***@aether.com>
---
include/uapi/sound/asound.h | 6 ++
sound/core/pcm_native.c | 142 ++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 148 insertions(+)

diff --git a/include/uapi/sound/asound.h b/include/uapi/sound/asound.h
index 6ee5867..92d5856 100644
--- a/include/uapi/sound/asound.h
+++ b/include/uapi/sound/asound.h
@@ -462,6 +462,11 @@ struct snd_xfern {
snd_pcm_uframes_t frames;
};

+struct snd_clock_time {
+ clockid_t clock;
+ struct timespec time;
+};
+
enum {
SNDRV_PCM_TSTAMP_TYPE_GETTIMEOFDAY = 0, /* gettimeofday equivalent */
SNDRV_PCM_TSTAMP_TYPE_MONOTONIC, /* posix_clock_monotonic equivalent */
@@ -547,6 +552,7 @@ enum {
#define SNDRV_PCM_IOCTL_READN_FRAMES _IOR('A', 0x53, struct snd_xfern)
#define SNDRV_PCM_IOCTL_LINK _IOW('A', 0x60, int)
#define SNDRV_PCM_IOCTL_UNLINK _IO('A', 0x61)
+#define SNDRV_PCM_IOCTL_START_AT _IOW('A', 0x62, struct snd_clock_time)

/*****************************************************************************
* *
diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c
index 85fe1a2..f16ac3c 100644
--- a/sound/core/pcm_native.c
+++ b/sound/core/pcm_native.c
@@ -27,6 +27,7 @@
#include <linux/pm_qos.h>
#include <linux/aio.h>
#include <linux/dma-mapping.h>
+#include <linux/time.h>
#include <sound/core.h>
#include <sound/control.h>
#include <sound/info.h>
@@ -38,6 +39,9 @@
#if defined(CONFIG_MIPS) && defined(CONFIG_DMA_NONCOHERENT)
#include <dma-coherence.h>
#endif
+#if defined(CONFIG_HIGH_RES_TIMERS)
+#include <linux/hrtimer.h>
+#endif

/*
* Compatibility
@@ -1016,6 +1020,142 @@ static struct action_ops snd_pcm_action_start = {
.post_action = snd_pcm_post_start
};

+#ifdef CONFIG_HIGH_RES_TIMERS
+/**
+ * ktime_get_raw_offset - get the offset in ktime format between
+ * the monotonic_raw clock and the monotonic clock
+ */
+static ktime_t ktime_get_raw_offset(void)
+{
+ struct timespec rawtime = {0,0};
+ struct timespec now = {0,0};
+ struct timespec delta;
+
+ ktime_get_ts(&now);
+ getrawmonotonic(&rawtime);
+ delta = timespec_sub(now, rawtime);
+ return timespec_to_ktime(delta);
+}
+
+/*
+ * hrtimer interface
+ */
+
+struct hrtimer_pcm {
+ struct hrtimer timer;
+ struct snd_pcm_substream *substream;
+};
+
+/*
+ * called from a hard irq context - no need for locks.
+ */
+enum hrtimer_restart snd_pcm_do_start_time(struct hrtimer *timer)
+{
+ struct hrtimer_pcm *pcm_timer;
+ struct snd_pcm_substream *substream;
+ struct snd_pcm_runtime *runtime;
+ int ret;
+
+ pcm_timer = container_of(timer, struct hrtimer_pcm, timer);
+ substream = pcm_timer->substream;
+ runtime = substream->runtime;
+
+ /* Check that the stream is still open and in the right state. */
+ if (runtime && runtime->status->state == SNDRV_PCM_STATE_PREPARED) {
+
+ ret = snd_pcm_do_start(substream, SNDRV_PCM_STATE_RUNNING);
+ if (ret == 0) {
+ snd_pcm_post_start(substream, SNDRV_PCM_STATE_RUNNING);
+ }
+ }
+ kfree(pcm_timer);
+
+ return HRTIMER_NORESTART;
+}
+
+int snd_pcm_start_at(struct snd_pcm_substream *substream,
+ struct timespec __user *_start_time)
+{
+ struct hrtimer_pcm *pcm_timer;
+ struct timespec start_time;
+ int clock;
+ ktime_t start_kt;
+ ktime_t raw_offset;
+ int ret;
+
+ if (copy_from_user(&start_time, _start_time, sizeof(start_time)))
+ return -EFAULT;
+
+ if (!timespec_valid(&start_time))
+ return -EINVAL;
+
+ start_kt = timespec_to_ktime(start_time);
+ switch (substream->runtime->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:
+ clock = CLOCK_MONOTONIC_RAW;
+ break;
+ default:
+ return -EINVAL;
+ }
+ switch(clock) {
+ case CLOCK_MONOTONIC_RAW:
+ /*
+ * convert to clock monotonic:
+ * the raw clock and the monotonic clock run at different
+ * rates. Currently there is no timer based on the
+ * monotonic_raw clock but for our purposes, we assume that
+ * the divergence between the raw and the monotonic clock
+ * over the life of the timer will be minimal.
+ * Typically the difference is less than 20PPM.
+ * TODO: implement high res timer based on CLOCK_MONOTONIC_RAW
+ */
+ raw_offset = ktime_get_raw_offset();
+ start_kt = ktime_add(raw_offset, start_kt);
+ clock = CLOCK_MONOTONIC;
+ /* and fall through ... */
+ case CLOCK_MONOTONIC:
+ if (ktime_compare(start_kt, ktime_get()) <= 0)
+ return -ETIME;
+ break;
+ case CLOCK_REALTIME:
+ if (ktime_compare(start_kt,
+ ktime_get_with_offset(TK_OFFS_REAL)) <= 0)
+ return -EINVAL;
+ }
+
+ /* Fail if not playback substream */
+ if (substream->stream != SNDRV_PCM_STREAM_PLAYBACK)
+ return -EBADFD;
+
+ ret = snd_pcm_pre_start(substream, SNDRV_PCM_STATE_PREPARED);
+ if (ret != 0)
+ return ret;
+
+ pcm_timer = kmalloc(sizeof(*pcm_timer), GFP_KERNEL);
+ hrtimer_init(&pcm_timer->timer, clock, HRTIMER_MODE_ABS);
+
+ pcm_timer->timer.function = snd_pcm_do_start_time;
+ pcm_timer->substream = substream;
+
+ hrtimer_start(&pcm_timer->timer, start_kt, HRTIMER_MODE_ABS);
+
+ return 0;
+}
+#else
+/* without high res time, interface is identical to snd_pcm_start() */
+int snd_pcm_start_at(struct snd_pcm_substream *substream,
+ struct snd_clock_time __user *_start_time)
+{
+ return -ENOSYS;
+}
+#endif
+
/**
* snd_pcm_start - start all linked streams
* @substream: the PCM substream instance
@@ -2698,6 +2838,8 @@ static int snd_pcm_common_ioctl1(struct file *file,
return snd_pcm_reset(substream);
case SNDRV_PCM_IOCTL_START:
return snd_pcm_action_lock_irq(&snd_pcm_action_start, substream, SNDRV_PCM_STATE_RUNNING);
+ case SNDRV_PCM_IOCTL_START_AT:
+ return snd_pcm_start_at(substream, arg);
case SNDRV_PCM_IOCTL_LINK:
return snd_pcm_link(substream, (int)(unsigned long) arg);
case SNDRV_PCM_IOCTL_UNLINK:
--
1.9.3
Loading...