Commit 08fdced6 authored by David Henningsson's avatar David Henningsson Committed by Takashi Iwai
Browse files

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: default avatarDavid Henningsson <coding@diwic.se>
Reviewed-by: default avatarJaroslav Kysela <perex@perex.cz>
Link: https://lore.kernel.org/r/20210515071533.55332-1-coding@diwic.se


Signed-off-by: default avatarTakashi Iwai <tiwai@suse.de>
parent bac59054
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -81,6 +81,8 @@ struct snd_rawmidi_substream {
	bool opened;			/* open flag */
	bool append;			/* append flag (merge more streams) */
	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) */
	size_t bytes;
	struct snd_rawmidi *rmidi;
+28 −2
Original line number Diff line number Diff line
@@ -710,7 +710,7 @@ enum {
 *  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 {
	SNDRV_RAWMIDI_STREAM_OUTPUT = 0,
@@ -736,12 +736,38 @@ struct snd_rawmidi_info {
	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 {
	int stream;
	size_t buffer_size;		/* queue size in bytes */
	size_t avail_min;		/* minimum avail bytes for wakeup */
	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__
+91 −2
Original line number Diff line number Diff line
@@ -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]);
				}
			}
		}
	}
+3 −1
Original line number Diff line number Diff line
@@ -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;