Dne 10. 04. 21 v 14:02 David Henningsson napsal(a): > 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@xxxxxxxx> > --- > include/sound/rawmidi.h | 2 ++ > include/uapi/sound/asound.h | 26 ++++++++++++++-- > sound/core/rawmidi.c | 60 +++++++++++++++++++++++++++++++++++-- > 3 files changed, 84 insertions(+), 4 deletions(-) > > diff --git a/include/sound/rawmidi.h b/include/sound/rawmidi.h > index 334842daa904..b0057a193c31 100644 > --- a/include/sound/rawmidi.h > +++ b/include/sound/rawmidi.h > @@ -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; > diff --git a/include/uapi/sound/asound.h b/include/uapi/sound/asound.h > index 535a7229e1d9..af8e60740218 100644 > --- a/include/uapi/sound/asound.h > +++ b/include/uapi/sound/asound.h > @@ -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,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. > + */ > + unsigned char frame_type; > + unsigned char length; /* number of valid bytes in data field */ > + unsigned char reserved[2]; > + unsigned int tv_nsec; /* nanoseconds */ > + unsigned long long tv_sec; /* seconds */ > + unsigned char 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 */ We can move this flag to above 32-bit word (no_active_sensing). I'm not sure, if we need 8 bits for this. It's first change after 20 years. Another flag may obsolete this one. > + 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__ > diff --git a/sound/core/rawmidi.c b/sound/core/rawmidi.c > index aca00af93afe..d4b6b9b5c0e4 100644 > --- a/sound/core/rawmidi.c > +++ b/sound/core/rawmidi.c > @@ -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) I would use '(a & b) != 0' here. It's more readable. > + return -EINVAL; > if (params->avail_min < 1 || params->avail_min > params->buffer_size) > return -EINVAL; > if (params->buffer_size != runtime->buffer_size) { > @@ -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; The CLOCK_REALTIME may be supported, too. For example, the input subsystem supports those three timestamps and we support this in the PCM interface, too. > + } > 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); > @@ -963,6 +974,42 @@ 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, 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); > + > + if (snd_BUG_ON(runtime->hw_ptr & 0x1f || runtime->buffer_size & 0x1f || frame_size != 0x20)) > + return -EINVAL; > + while (src_count > 0) { > + if ((int)(runtime->buffer_size - runtime->avail) < frame_size) { > + runtime->xruns += src_count; > + return dest_frames * frame_size; > + } > + 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); We know the length here, so we can skip the zeroing the copied bytes with memcpy(). Jaroslav -- Jaroslav Kysela <perex@xxxxxxxx> Linux Sound Maintainer; ALSA Project; Red Hat, Inc.