From 08fdced60ca08e34e316a3ab945636fcdfcbc973 Mon Sep 17 00:00:00 2001 From: David Henningsson Date: Sat, 15 May 2021 09:15:33 +0200 Subject: ALSA: rawmidi: Add framing mode This commit adds a new framing mode that frames all MIDI data into 32-byte frames with a timestamp. The main benefit is that we can get accurate timestamps even if userspace wakeup and processing is not immediate. Testing on a Celeron N3150 with this mode has a max jitter of 2.8 ms, compared to the in-kernel seq implementation which has a max jitter of 5 ms during idle and much worse when running scheduler stress tests in parallel. Signed-off-by: David Henningsson Reviewed-by: Jaroslav Kysela Link: https://lore.kernel.org/r/20210515071533.55332-1-coding@diwic.se Signed-off-by: Takashi Iwai --- sound/core/rawmidi.c | 93 ++++++++++++++++++++++++++++++++++++++++++++- sound/core/rawmidi_compat.c | 4 +- 2 files changed, 94 insertions(+), 3 deletions(-) (limited to 'sound/core') diff --git a/sound/core/rawmidi.c b/sound/core/rawmidi.c index aca00af93afe..4a6534db77d6 100644 --- a/sound/core/rawmidi.c +++ b/sound/core/rawmidi.c @@ -680,9 +680,12 @@ static int resize_runtime_buffer(struct snd_rawmidi_runtime *runtime, bool is_input) { char *newbuf, *oldbuf; + unsigned int framing = params->mode & SNDRV_RAWMIDI_MODE_FRAMING_MASK; if (params->buffer_size < 32 || params->buffer_size > 1024L * 1024L) return -EINVAL; + if (framing == SNDRV_RAWMIDI_MODE_FRAMING_TSTAMP && (params->buffer_size & 0x1f) != 0) + return -EINVAL; if (params->avail_min < 1 || params->avail_min > params->buffer_size) return -EINVAL; if (params->buffer_size != runtime->buffer_size) { @@ -720,8 +723,24 @@ EXPORT_SYMBOL(snd_rawmidi_output_params); int snd_rawmidi_input_params(struct snd_rawmidi_substream *substream, struct snd_rawmidi_params *params) { + unsigned int framing = params->mode & SNDRV_RAWMIDI_MODE_FRAMING_MASK; + unsigned int clock_type = params->mode & SNDRV_RAWMIDI_MODE_CLOCK_MASK; + int err; + + if (framing == SNDRV_RAWMIDI_MODE_FRAMING_NONE && clock_type != SNDRV_RAWMIDI_MODE_CLOCK_NONE) + return -EINVAL; + else if (clock_type > SNDRV_RAWMIDI_MODE_CLOCK_MONOTONIC_RAW) + return -EINVAL; + if (framing > SNDRV_RAWMIDI_MODE_FRAMING_TSTAMP) + return -EINVAL; snd_rawmidi_drain_input(substream); - return resize_runtime_buffer(substream->runtime, params, true); + err = resize_runtime_buffer(substream->runtime, params, true); + if (err < 0) + return err; + + substream->framing = framing; + substream->clock_type = clock_type; + return 0; } EXPORT_SYMBOL(snd_rawmidi_input_params); @@ -963,6 +982,62 @@ static int snd_rawmidi_control_ioctl(struct snd_card *card, return -ENOIOCTLCMD; } +static int receive_with_tstamp_framing(struct snd_rawmidi_substream *substream, + const unsigned char *buffer, int src_count, const struct timespec64 *tstamp) +{ + struct snd_rawmidi_runtime *runtime = substream->runtime; + struct snd_rawmidi_framing_tstamp *dest_ptr; + struct snd_rawmidi_framing_tstamp frame = { .tv_sec = tstamp->tv_sec, .tv_nsec = tstamp->tv_nsec }; + int dest_frames = 0; + int orig_count = src_count; + int frame_size = sizeof(struct snd_rawmidi_framing_tstamp); + + BUILD_BUG_ON(frame_size != 0x20); + if (snd_BUG_ON((runtime->hw_ptr & 0x1f) != 0)) + return -EINVAL; + + while (src_count > 0) { + if ((int)(runtime->buffer_size - runtime->avail) < frame_size) { + runtime->xruns += src_count; + break; + } + if (src_count >= SNDRV_RAWMIDI_FRAMING_DATA_LENGTH) + frame.length = SNDRV_RAWMIDI_FRAMING_DATA_LENGTH; + else { + frame.length = src_count; + memset(frame.data, 0, SNDRV_RAWMIDI_FRAMING_DATA_LENGTH); + } + memcpy(frame.data, buffer, frame.length); + buffer += frame.length; + src_count -= frame.length; + dest_ptr = (struct snd_rawmidi_framing_tstamp *) (runtime->buffer + runtime->hw_ptr); + *dest_ptr = frame; + runtime->avail += frame_size; + runtime->hw_ptr += frame_size; + runtime->hw_ptr %= runtime->buffer_size; + dest_frames++; + } + return orig_count - src_count; +} + +static struct timespec64 get_framing_tstamp(struct snd_rawmidi_substream *substream) +{ + struct timespec64 ts64 = {0, 0}; + + switch (substream->clock_type) { + case SNDRV_RAWMIDI_MODE_CLOCK_MONOTONIC_RAW: + ktime_get_raw_ts64(&ts64); + break; + case SNDRV_RAWMIDI_MODE_CLOCK_MONOTONIC: + ktime_get_ts64(&ts64); + break; + case SNDRV_RAWMIDI_MODE_CLOCK_REALTIME: + ktime_get_real_ts64(&ts64); + break; + } + return ts64; +} + /** * snd_rawmidi_receive - receive the input data from the device * @substream: the rawmidi substream @@ -977,6 +1052,7 @@ int snd_rawmidi_receive(struct snd_rawmidi_substream *substream, const unsigned char *buffer, int count) { unsigned long flags; + struct timespec64 ts64 = get_framing_tstamp(substream); int result = 0, count1; struct snd_rawmidi_runtime *runtime = substream->runtime; @@ -987,8 +1063,11 @@ int snd_rawmidi_receive(struct snd_rawmidi_substream *substream, "snd_rawmidi_receive: input is not active!!!\n"); return -EINVAL; } + spin_lock_irqsave(&runtime->lock, flags); - if (count == 1) { /* special case, faster code */ + if (substream->framing == SNDRV_RAWMIDI_MODE_FRAMING_TSTAMP) { + result = receive_with_tstamp_framing(substream, buffer, count, &ts64); + } else if (count == 1) { /* special case, faster code */ substream->bytes++; if (runtime->avail < runtime->buffer_size) { runtime->buffer[runtime->hw_ptr++] = buffer[0]; @@ -1541,6 +1620,8 @@ static void snd_rawmidi_proc_info_read(struct snd_info_entry *entry, struct snd_rawmidi_substream *substream; struct snd_rawmidi_runtime *runtime; unsigned long buffer_size, avail, xruns; + unsigned int clock_type; + static const char *clock_names[4] = { "none", "realtime", "monotonic", "monotonic raw" }; rmidi = entry->private_data; snd_iprintf(buffer, "%s\n\n", rmidi->name); @@ -1596,6 +1677,14 @@ static void snd_rawmidi_proc_info_read(struct snd_info_entry *entry, " Avail : %lu\n" " Overruns : %lu\n", buffer_size, avail, xruns); + if (substream->framing == SNDRV_RAWMIDI_MODE_FRAMING_TSTAMP) { + clock_type = substream->clock_type >> SNDRV_RAWMIDI_MODE_CLOCK_SHIFT; + if (!snd_BUG_ON(clock_type >= sizeof(clock_names))) + snd_iprintf(buffer, + " Framing : tstamp\n" + " Clock type : %s\n", + clock_names[clock_type]); + } } } } diff --git a/sound/core/rawmidi_compat.c b/sound/core/rawmidi_compat.c index 7397130976d0..68a93443583c 100644 --- a/sound/core/rawmidi_compat.c +++ b/sound/core/rawmidi_compat.c @@ -13,7 +13,8 @@ struct snd_rawmidi_params32 { u32 buffer_size; u32 avail_min; unsigned int no_active_sensing; /* avoid bit-field */ - unsigned char reserved[16]; + unsigned int mode; + unsigned char reserved[12]; } __attribute__((packed)); static int snd_rawmidi_ioctl_params_compat(struct snd_rawmidi_file *rfile, @@ -25,6 +26,7 @@ static int snd_rawmidi_ioctl_params_compat(struct snd_rawmidi_file *rfile, if (get_user(params.stream, &src->stream) || get_user(params.buffer_size, &src->buffer_size) || get_user(params.avail_min, &src->avail_min) || + get_user(params.mode, &src->mode) || get_user(val, &src->no_active_sensing)) return -EFAULT; params.no_active_sensing = val; -- cgit v1.2.3