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 <coding@diwic.se>
Reviewed-by: Jaroslav Kysela <perex@perex.cz>
Link: https://lore.kernel.org/r/20210515071533.55332-1-coding@diwic.se
Signed-off-by: Takashi Iwai <tiwai@suse.de>
This commit is contained in:
David Henningsson 2021-05-15 09:15:33 +02:00 committed by Takashi Iwai
parent bac5905454
commit 08fdced60c
4 changed files with 124 additions and 5 deletions

View File

@ -81,6 +81,8 @@ struct snd_rawmidi_substream {
bool opened; /* open flag */ bool opened; /* open flag */
bool append; /* append flag (merge more streams) */ bool append; /* append flag (merge more streams) */
bool active_sensing; /* send active sensing when close */ bool active_sensing; /* send active sensing when close */
unsigned int framing; /* whether to frame input data */
unsigned int clock_type; /* clock source to use for input framing */
int use_count; /* use counter (for output) */ int use_count; /* use counter (for output) */
size_t bytes; size_t bytes;
struct snd_rawmidi *rmidi; struct snd_rawmidi *rmidi;

View File

@ -710,7 +710,7 @@ enum {
* Raw MIDI section - /dev/snd/midi?? * Raw MIDI section - /dev/snd/midi??
*/ */
#define SNDRV_RAWMIDI_VERSION SNDRV_PROTOCOL_VERSION(2, 0, 1) #define SNDRV_RAWMIDI_VERSION SNDRV_PROTOCOL_VERSION(2, 0, 2)
enum { enum {
SNDRV_RAWMIDI_STREAM_OUTPUT = 0, SNDRV_RAWMIDI_STREAM_OUTPUT = 0,
@ -736,12 +736,38 @@ struct snd_rawmidi_info {
unsigned char reserved[64]; /* reserved for future use */ unsigned char reserved[64]; /* reserved for future use */
}; };
#define SNDRV_RAWMIDI_MODE_FRAMING_MASK (7<<0)
#define SNDRV_RAWMIDI_MODE_FRAMING_SHIFT 0
#define SNDRV_RAWMIDI_MODE_FRAMING_NONE (0<<0)
#define SNDRV_RAWMIDI_MODE_FRAMING_TSTAMP (1<<0)
#define SNDRV_RAWMIDI_MODE_CLOCK_MASK (7<<3)
#define SNDRV_RAWMIDI_MODE_CLOCK_SHIFT 3
#define SNDRV_RAWMIDI_MODE_CLOCK_NONE (0<<3)
#define SNDRV_RAWMIDI_MODE_CLOCK_REALTIME (1<<3)
#define SNDRV_RAWMIDI_MODE_CLOCK_MONOTONIC (2<<3)
#define SNDRV_RAWMIDI_MODE_CLOCK_MONOTONIC_RAW (3<<3)
#define SNDRV_RAWMIDI_FRAMING_DATA_LENGTH 16
struct snd_rawmidi_framing_tstamp {
/* For now, frame_type is always 0. Midi 2.0 is expected to add new
* types here. Applications are expected to skip unknown frame types.
*/
__u8 frame_type;
__u8 length; /* number of valid bytes in data field */
__u8 reserved[2];
__u32 tv_nsec; /* nanoseconds */
__u64 tv_sec; /* seconds */
__u8 data[SNDRV_RAWMIDI_FRAMING_DATA_LENGTH];
} __packed;
struct snd_rawmidi_params { struct snd_rawmidi_params {
int stream; int stream;
size_t buffer_size; /* queue size in bytes */ size_t buffer_size; /* queue size in bytes */
size_t avail_min; /* minimum avail bytes for wakeup */ size_t avail_min; /* minimum avail bytes for wakeup */
unsigned int no_active_sensing: 1; /* do not send active sensing byte in close() */ unsigned int no_active_sensing: 1; /* do not send active sensing byte in close() */
unsigned char reserved[16]; /* reserved for future use */ unsigned int mode; /* For input data only, frame incoming data */
unsigned char reserved[12]; /* reserved for future use */
}; };
#ifndef __KERNEL__ #ifndef __KERNEL__

View File

@ -680,9 +680,12 @@ static int resize_runtime_buffer(struct snd_rawmidi_runtime *runtime,
bool is_input) bool is_input)
{ {
char *newbuf, *oldbuf; char *newbuf, *oldbuf;
unsigned int framing = params->mode & SNDRV_RAWMIDI_MODE_FRAMING_MASK;
if (params->buffer_size < 32 || params->buffer_size > 1024L * 1024L) if (params->buffer_size < 32 || params->buffer_size > 1024L * 1024L)
return -EINVAL; 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) if (params->avail_min < 1 || params->avail_min > params->buffer_size)
return -EINVAL; return -EINVAL;
if (params->buffer_size != runtime->buffer_size) { 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, int snd_rawmidi_input_params(struct snd_rawmidi_substream *substream,
struct snd_rawmidi_params *params) 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); 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); EXPORT_SYMBOL(snd_rawmidi_input_params);
@ -963,6 +982,62 @@ static int snd_rawmidi_control_ioctl(struct snd_card *card,
return -ENOIOCTLCMD; 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 * snd_rawmidi_receive - receive the input data from the device
* @substream: the rawmidi substream * @substream: the rawmidi substream
@ -977,6 +1052,7 @@ int snd_rawmidi_receive(struct snd_rawmidi_substream *substream,
const unsigned char *buffer, int count) const unsigned char *buffer, int count)
{ {
unsigned long flags; unsigned long flags;
struct timespec64 ts64 = get_framing_tstamp(substream);
int result = 0, count1; int result = 0, count1;
struct snd_rawmidi_runtime *runtime = substream->runtime; 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"); "snd_rawmidi_receive: input is not active!!!\n");
return -EINVAL; return -EINVAL;
} }
spin_lock_irqsave(&runtime->lock, flags); 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++; substream->bytes++;
if (runtime->avail < runtime->buffer_size) { if (runtime->avail < runtime->buffer_size) {
runtime->buffer[runtime->hw_ptr++] = buffer[0]; 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_substream *substream;
struct snd_rawmidi_runtime *runtime; struct snd_rawmidi_runtime *runtime;
unsigned long buffer_size, avail, xruns; 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; rmidi = entry->private_data;
snd_iprintf(buffer, "%s\n\n", rmidi->name); 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" " Avail : %lu\n"
" Overruns : %lu\n", " Overruns : %lu\n",
buffer_size, avail, xruns); 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]);
}
} }
} }
} }

View File

@ -13,7 +13,8 @@ struct snd_rawmidi_params32 {
u32 buffer_size; u32 buffer_size;
u32 avail_min; u32 avail_min;
unsigned int no_active_sensing; /* avoid bit-field */ unsigned int no_active_sensing; /* avoid bit-field */
unsigned char reserved[16]; unsigned int mode;
unsigned char reserved[12];
} __attribute__((packed)); } __attribute__((packed));
static int snd_rawmidi_ioctl_params_compat(struct snd_rawmidi_file *rfile, 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) || if (get_user(params.stream, &src->stream) ||
get_user(params.buffer_size, &src->buffer_size) || get_user(params.buffer_size, &src->buffer_size) ||
get_user(params.avail_min, &src->avail_min) || get_user(params.avail_min, &src->avail_min) ||
get_user(params.mode, &src->mode) ||
get_user(val, &src->no_active_sensing)) get_user(val, &src->no_active_sensing))
return -EFAULT; return -EFAULT;
params.no_active_sensing = val; params.no_active_sensing = val;