ALSA: provide a simple mechanism to start playback at a given time
Subsystems (2)
RFC 1/1 ALSA: provide a simple mechanism to start playback at a given time
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(+)
include/linux/timekeeping.h +3 -0
@@ -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) {
include/uapi/sound/asound.h +6 -0
@@ -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)/***************************************************************************** * *
kernel/time/timekeeping.c +16 -0
@@ -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
sound/core/pcm_native.c +99 -0
123 lines not on this page · raw message
Signature · 1 line
1.9.3
Hi again Nick!
Quoted 31 lines · Nick Stoughton
Initial implementation / Request For Comment.
Given an absolute time based on a given clock (
CLOCK_MONOTONIC,CLOCK_MONOTONIC_RAW,CLOCK_REALTIMEetc), 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(+)include/linux/timekeeping.h +3
-0@@ -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) {include/uapi/sound/asound.h +0
-0
include/uapi/sound/asound.h struct snd_xfern { · line 469
Quoted 8 lines
include/uapi/sound/asound.h +5
-0@@ -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.
Quoted 147 lines
include/uapi/sound/asound.h +1
-0enum {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)/***************************************************************************** * * kernel/time/timekeeping.c +16
-0@@ -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 sound/core/pcm_native.c +87
-0@@ -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 ; +}
sound/core/pcm_native.c static struct action_ops snd_pcm_action_start = { · line 1115
Quoted 10 lines
sound/core/pcm_native.c +10
-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 ... :)
sound/core/pcm_native.c static int snd_pcm_common_ioctl1(struct file *file, · line 2802
Quoted 13 lines
sound/core/pcm_native.c +2
-0/** * 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 :
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
At Thu, 16 Oct 2014 11:53:07 +0100,
Quoted 38 lines · Tim Cussins
Hi again Nick!
Quoted 34 lines · Nick Stoughton
Initial implementation / Request For Comment.
Given an absolute time based on a given clock (
CLOCK_MONOTONIC,CLOCK_MONOTONIC_RAW,CLOCK_REALTIMEetc), 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(+)include/linux/timekeeping.h +3
-0@@ -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) {include/uapi/sound/asound.h +0
-0@@ -462,6 +462,11 @@ struct snd_xfern {snd_pcm_uframes_t frames; };
Quoted 9 lines
include/uapi/sound/asound.h struct snd_xfern { · line 469
Quoted 5 lines
include/uapi/sound/asound.h +5
-0+struct snd_clock_time { +clockid_t clock; +struct timespec time; +}; +Takashi added
snd_pcm_tstamp_type_tin July - If the finalstart_atpatch includes some "structsnd_clock_time", then it'd probably use Takashi's type in favour of a rawclockid_t.
Yeah, that would make things more consistent. But...
Quoted 2 lines
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...
Quoted 19 lines
Quoted 19 lines
include/uapi/sound/asound.h +1
-0enum {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)/***************************************************************************** * * kernel/time/timekeeping.c +0
-0@@ -789,6 +789,22 @@ error: /* even if we error out, we forwarded the time, so call update */ }EXPORT_SYMBOL (timekeeping_inject_offset);
Quoted 16 lines
Quoted 6 lines
kernel/time/timekeeping.c +6
-0+/** +* 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 ) +{kernel/time/timekeeping.c error: /* even if we error out, we forwarded the time, so call update */ · line 807
Quoted 10 lines
kernel/time/timekeeping.c +10
-0+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.
Quoted 81 lines
Quoted 81 lines
kernel/time/timekeeping.c +0
-0* timekeeping_get_tai_offset - Returns current TAI offset from UTC sound/core/pcm_native.c +58
-0@@ -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 ) {
Quoted 8 lines
sound/core/pcm_native.c static struct action_ops snd_pcm_action_start = { · line 1084
Quoted 8 lines
sound/core/pcm_native.c +8
-0+ 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?
Quoted 35 lines
Quoted 21 lines
sound/core/pcm_native.c +21
-0+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 ; +}sound/core/pcm_native.c static struct action_ops snd_pcm_action_start = { · line 1115
Quoted 10 lines
sound/core/pcm_native.c +10
-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_atcannot be supported, rather than lying to userspace that it will be satisfied :)
Quoted 8 lines
There might be a number of reasons that
snd_pcm_start_atcannot be
satisfied: unsupported clock-type, timespec in the past, incompatible kernel config are just three.While one might argue that
start_atis 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