[alsa-devel] [PATCH v2 1/3] snd_pcm_start_at: Add enum for timestamp class, and helper struct for start_at ioctl.
As per Pierre's suggestion, timestamp types are of two types: System, and Audio.
Signed-off-by: Tim Cussins timcussins@eml.cc
diff --git a/include/uapi/sound/asound.h b/include/uapi/sound/asound.h index 1f23cd6..bd1e9be 100644 --- a/include/uapi/sound/asound.h +++ b/include/uapi/sound/asound.h @@ -466,12 +466,30 @@ struct snd_xfern { };
enum { + SNDRV_PCM_TSTAMP_CLASS_SYSTEM = 0, + SNDRV_PCM_TSTAMP_CLASS_AUDIO, + SNDRV_PCM_TSTAMP_CLASS_LAST = SNDRV_PCM_TSTAMP_CLASS_AUDIO, +}; + +enum { SNDRV_PCM_TSTAMP_TYPE_GETTIMEOFDAY = 0, /* gettimeofday equivalent */ SNDRV_PCM_TSTAMP_TYPE_MONOTONIC, /* posix_clock_monotonic equivalent */ SNDRV_PCM_TSTAMP_TYPE_MONOTONIC_RAW, /* monotonic_raw (no NTP) */ SNDRV_PCM_TSTAMP_TYPE_LAST = SNDRV_PCM_TSTAMP_TYPE_MONOTONIC_RAW, };
+enum { + SNDRV_PCM_AUDIO_TSTAMP_TYPE_DEFAULT = 0, + SNDRV_PCM_AUDIO_TSTAMP_TYPE_LINK, + SNDRV_PCM_AUDIO_TSTAMP_TYPE_LAST = SNDRV_PCM_AUDIO_TSTAMP_TYPE_LINK, +}; + +struct snd_start_at { + int tstamp_class; + int tstamp_type; + struct timespec start_time; +}; + /* channel positions */ enum { SNDRV_CHMAP_UNKNOWN = 0, @@ -550,6 +568,8 @@ 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_start_at) +
/***************************************************************************** * *
Provide members for starting and cancelling the timer. Add state to snd_pcm_runtime that tracks current timer: This allows us to cancel on state-change, or subsequent start_at call.
Signed-off-by: Tim Cussins timcussins@eml.cc
diff --git a/include/sound/pcm.h b/include/sound/pcm.h index 1e7f74a..e7d5ded 100644 --- a/include/sound/pcm.h +++ b/include/sound/pcm.h @@ -73,6 +73,8 @@ struct snd_pcm_ops { snd_pcm_uframes_t (*pointer)(struct snd_pcm_substream *substream); int (*wall_clock)(struct snd_pcm_substream *substream, struct timespec *audio_ts); + int (*start_at)(struct snd_pcm_substream *substream, int audio_tstamp_type, const struct timespec *ts); + int (*start_at_cancel)(struct snd_pcm_substream *substream); int (*copy)(struct snd_pcm_substream *substream, int channel, snd_pcm_uframes_t pos, void __user *buf, snd_pcm_uframes_t count); @@ -364,6 +366,10 @@ struct snd_pcm_runtime { #ifdef CONFIG_SND_PCM_XRUN_DEBUG struct snd_pcm_hwptr_log *hwptr_log; #endif + + int start_at_timer_running; /* 0 if no timer is running, 1 if running */ + int start_at_timer_tstamp_class; /* tstamp class of current timer */ + void* start_at_timer_data; /* data associated with current timer */ };
struct snd_pcm_group { /* keep linked substreams */
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@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:
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.
http://git.alsa-project.org/?p=alsa-lib.git;a=blob_plain;f=test/timer.c;hb=H...
Are there any difference between sound card's timer with high res timer/wall clock time stamp ?
It seem those sound cards timer have resolution and max tick
https://git.kernel.org/cgit/linux/kernel/git/tiwai/sound.git/commit/sound/pc...
snd_timer_status_get_resolution
The clock tick counters are 32 bits and wrap around
This may mean that snd_pcm_start_at can only start within certian time interval
On 17/12/14 23:37, Raymond Yau wrote:
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.
http://git.alsa-project.org/?p=alsa-lib.git;a=blob_plain;f=test/timer.c;hb=H...
Are there any difference between sound card's timer with high res timer/wall clock time stamp ?
It seem those sound cards timer have resolution and max tick
https://git.kernel.org/cgit/linux/kernel/git/tiwai/sound.git/commit/sound/pc...
snd_timer_status_get_resolution
The clock tick counters are 32 bits and wrap around
This may mean that snd_pcm_start_at can only start within certian time interval
[and this time I'll post to the list :)]
I had presumed it would be pretty simple to use the timer-wrap interrupt to maintain a monotonic sense of time. Hardware that can't support this might not be able to support start_at.
T
participants (2)
-
Raymond Yau
-
Tim Cussins