mirror of
https://github.com/torvalds/linux.git
synced 2024-12-27 21:33:00 +00:00
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:
parent
bac5905454
commit
08fdced60c
@ -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;
|
||||||
|
@ -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__
|
||||||
|
@ -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]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
Loading…
Reference in New Issue
Block a user