From: Tim Cussins <timcussins@eml.cc>
To: alsa-devel@alsa-project.org
Subject: Re: [RFC] ALSA: provide a simple mechanism to start playback at a given time
Date: Thu, 16 Oct 2014 11:53:07 +0100 [thread overview]
Message-ID: <543FA393.7020605@eml.cc> (raw)
In-Reply-To: <543d33f8.4a66b40a.638d.0583@mx.google.com>
Hi again Nick!
On 14/10/14 09:29, Nick Stoughton wrote:
> 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.
> 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 ... :)
> /**
> * 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
next prev parent reply other threads:[~2014-10-16 10:53 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
2014-10-14 8:29 [RFC] ALSA: provide a simple mechanism to start playback at a given time Nick Stoughton
2014-10-16 10:53 ` Tim Cussins [this message]
2014-10-16 14:34 ` Takashi Iwai
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=543FA393.7020605@eml.cc \
--to=timcussins@eml.cc \
--cc=alsa-devel@alsa-project.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.