Skip to content

Commit

Permalink
sound: rawmidi: Add framing mode
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
diwic authored and intel-lab-lkp committed Apr 18, 2021
1 parent d86f43b commit 6b82e8d
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 4 deletions.
2 changes: 2 additions & 0 deletions include/sound/rawmidi.h
Expand Up @@ -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 */
u8 framing; /* whether to frame input data */
clockid_t clock_type; /* clock source to use for input framing */
int use_count; /* use counter (for output) */
size_t bytes;
struct snd_rawmidi *rmidi;
Expand Down
26 changes: 24 additions & 2 deletions include/uapi/sound/asound.h
Expand Up @@ -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,
Expand All @@ -736,12 +736,34 @@ struct snd_rawmidi_info {
unsigned char reserved[64]; /* reserved for future use */
};

enum {
SNDRV_RAWMIDI_FRAMING_NONE = 0,
SNDRV_RAWMIDI_FRAMING_TSTAMP,
SNDRV_RAWMIDI_FRAMING_LAST = SNDRV_RAWMIDI_FRAMING_TSTAMP,
};

#define SND_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[SND_RAWMIDI_FRAMING_DATA_LENGTH];
};

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 char framing; /* For input data only, frame incoming data */
unsigned char clock_type; /* Type of clock to use for framing, same as clockid_t */
unsigned char reserved[14]; /* reserved for future use */
};

#ifndef __KERNEL__
Expand Down
73 changes: 72 additions & 1 deletion sound/core/rawmidi.c
Expand Up @@ -683,6 +683,8 @@ static int resize_runtime_buffer(struct snd_rawmidi_runtime *runtime,

if (params->buffer_size < 32 || params->buffer_size > 1024L * 1024L)
return -EINVAL;
if (params->framing == SNDRV_RAWMIDI_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) {
Expand Down Expand Up @@ -720,7 +722,16 @@ EXPORT_SYMBOL(snd_rawmidi_output_params);
int snd_rawmidi_input_params(struct snd_rawmidi_substream *substream,
struct snd_rawmidi_params *params)
{
if (params->framing) {
if (params->framing > SNDRV_RAWMIDI_FRAMING_LAST)
return -EINVAL;
/* framing requires a valid clock type */
if (params->clock_type != CLOCK_MONOTONIC_RAW && params->clock_type != CLOCK_MONOTONIC)
return -EINVAL;
}
snd_rawmidi_drain_input(substream);
substream->framing = params->framing;
substream->clock_type = params->clock_type;
return resize_runtime_buffer(substream->runtime, params, true);
}
EXPORT_SYMBOL(snd_rawmidi_input_params);
Expand Down Expand Up @@ -963,6 +974,56 @@ 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 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 >= SND_RAWMIDI_FRAMING_DATA_LENGTH)
frame.length = SND_RAWMIDI_FRAMING_DATA_LENGTH;
else {
frame.length = src_count;
memset(frame.data, 0, SND_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 dest_frames * frame_size;
}

struct timespec64 get_framing_tstamp(struct snd_rawmidi_substream *substream)
{
struct timespec64 ts64 = {0, 0};

if (substream->framing != SNDRV_RAWMIDI_FRAMING_TSTAMP)
return ts64;
if (substream->clock_type == CLOCK_MONOTONIC_RAW)
ktime_get_raw_ts64(&ts64);
else
ktime_get_ts64(&ts64);
return ts64;
}

/**
* snd_rawmidi_receive - receive the input data from the device
* @substream: the rawmidi substream
Expand All @@ -977,6 +1038,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;

Expand All @@ -987,8 +1049,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_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];
Expand Down Expand Up @@ -1596,6 +1661,12 @@ 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_FRAMING_TSTAMP) {
snd_iprintf(buffer,
" Framing : tstamp\n"
" Clock type : %s\n",
substream->clock_type == CLOCK_MONOTONIC_RAW ? "monotonic raw" : "monotonic");
}
}
}
}
Expand Down
6 changes: 5 additions & 1 deletion sound/core/rawmidi_compat.c
Expand Up @@ -13,7 +13,9 @@ struct snd_rawmidi_params32 {
u32 buffer_size;
u32 avail_min;
unsigned int no_active_sensing; /* avoid bit-field */
unsigned char reserved[16];
unsigned char framing;
unsigned char clock_type;
unsigned char reserved[14];
} __attribute__((packed));

static int snd_rawmidi_ioctl_params_compat(struct snd_rawmidi_file *rfile,
Expand All @@ -25,6 +27,8 @@ 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.framing, &src->framing) ||
get_user(params.clock_type, &src->clock_type) ||
get_user(val, &src->no_active_sensing))
return -EFAULT;
params.no_active_sensing = val;
Expand Down

0 comments on commit 6b82e8d

Please sign in to comment.