History

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.

David Henningsson committed 5 years ago

Commit 08fdced1 parent bac59054 files · +124 −5Browse files

Unified · Split

4 files
  1. include/sound/rawmidi.h +2 −0
  2. include/uapi/sound/asound.h +28 −2
  3. sound/core/rawmidi.c +91 −2
  4. sound/core/rawmidi_compat.c +3 −1
include/sound/rawmidi.h+2−0
@@ -81,6 +81,8 @@ struct snd_rawmidi_substream { · 80 unmodified lines
8181 bool opened; /* open flag */
8282 bool append; /* append flag (merge more streams) */
8383 bool active_sensing; /* send active sensing when close */
84+ unsigned int framing; /* whether to frame input data */
85+ unsigned int clock_type; /* clock source to use for input framing */
8486 int use_count; /* use counter (for output) */
8587 size_t bytes;
8688 struct snd_rawmidi *rmidi;
include/uapi/sound/asound.h+28−2
@@ -710,7 +710,7 @@ enum { · 709 unmodified lines
710710 * Raw MIDI section - /dev/snd/midi??
711711 */
712712
713−#define SNDRV_RAWMIDI_VERSION SNDRV_PROTOCOL_VERSION(2, 0, 1)
713+#define SNDRV_RAWMIDI_VERSION SNDRV_PROTOCOL_VERSION(2, 0, 2)
714714
715715 enum {
716716 SNDRV_RAWMIDI_STREAM_OUTPUT = 0,
@@ -736,12 +736,38 @@ struct snd_rawmidi_info { · 19 unmodified lines
736736 unsigned char reserved[64]; /* reserved for future use */
737737 };
738738
739+#define SNDRV_RAWMIDI_MODE_FRAMING_MASK (7<<0)
740+#define SNDRV_RAWMIDI_MODE_FRAMING_SHIFT 0
741+#define SNDRV_RAWMIDI_MODE_FRAMING_NONE (0<<0)
742+#define SNDRV_RAWMIDI_MODE_FRAMING_TSTAMP (1<<0)
743+#define SNDRV_RAWMIDI_MODE_CLOCK_MASK (7<<3)
744+#define SNDRV_RAWMIDI_MODE_CLOCK_SHIFT 3
745+#define SNDRV_RAWMIDI_MODE_CLOCK_NONE (0<<3)
746+#define SNDRV_RAWMIDI_MODE_CLOCK_REALTIME (1<<3)
747+#define SNDRV_RAWMIDI_MODE_CLOCK_MONOTONIC (2<<3)
748+#define SNDRV_RAWMIDI_MODE_CLOCK_MONOTONIC_RAW (3<<3)
749+
750+#define SNDRV_RAWMIDI_FRAMING_DATA_LENGTH 16
751+
752+struct snd_rawmidi_framing_tstamp {
753+ /* For now, frame_type is always 0. Midi 2.0 is expected to add new
754+ * types here. Applications are expected to skip unknown frame types.
755+ */
756+ __u8 frame_type;
757+ __u8 length; /* number of valid bytes in data field */
758+ __u8 reserved[2];
759+ __u32 tv_nsec; /* nanoseconds */
760+ __u64 tv_sec; /* seconds */
761+ __u8 data[SNDRV_RAWMIDI_FRAMING_DATA_LENGTH];
762+} __packed;
763+
739764 struct snd_rawmidi_params {
740765 int stream;
741766 size_t buffer_size; /* queue size in bytes */
742767 size_t avail_min; /* minimum avail bytes for wakeup */
743768 unsigned int no_active_sensing: 1; /* do not send active sensing byte in close() */
744− unsigned char reserved[16]; /* reserved for future use */
769+ unsigned int mode; /* For input data only, frame incoming data */
770+ unsigned char reserved[12]; /* reserved for future use */
745771 };
746772
747773 #ifndef __KERNEL__
sound/core/rawmidi.c+91−2
@@ -680,9 +680,12 @@ static int resize_runtime_buffer(struct snd_rawmidi_runtime *runtime, · 679 unmodified lines
680680 bool is_input)
681681 {
682682 char *newbuf, *oldbuf;
683+ unsigned int framing = params->mode & SNDRV_RAWMIDI_MODE_FRAMING_MASK;
683684
684685 if (params->buffer_size < 32 || params->buffer_size > 1024L * 1024L)
685686 return -EINVAL;
687+ if (framing == SNDRV_RAWMIDI_MODE_FRAMING_TSTAMP && (params->buffer_size & 0x1f) != 0)
688+ return -EINVAL;
686689 if (params->avail_min < 1 || params->avail_min > params->buffer_size)
687690 return -EINVAL;
688691 if (params->buffer_size != runtime->buffer_size) {
@@ -720,8 +723,24 @@ EXPORT_SYMBOL(snd_rawmidi_output_params); · 31 unmodified lines
720723 int snd_rawmidi_input_params(struct snd_rawmidi_substream *substream,
721724 struct snd_rawmidi_params *params)
722725 {
726+ unsigned int framing = params->mode & SNDRV_RAWMIDI_MODE_FRAMING_MASK;
727+ unsigned int clock_type = params->mode & SNDRV_RAWMIDI_MODE_CLOCK_MASK;
728+ int err;
729+
730+ if (framing == SNDRV_RAWMIDI_MODE_FRAMING_NONE && clock_type != SNDRV_RAWMIDI_MODE_CLOCK_NONE)
731+ return -EINVAL;
732+ else if (clock_type > SNDRV_RAWMIDI_MODE_CLOCK_MONOTONIC_RAW)
733+ return -EINVAL;
734+ if (framing > SNDRV_RAWMIDI_MODE_FRAMING_TSTAMP)
735+ return -EINVAL;
723736 snd_rawmidi_drain_input(substream);
724− return resize_runtime_buffer(substream->runtime, params, true);
737+ err = resize_runtime_buffer(substream->runtime, params, true);
738+ if (err < 0)
739+ return err;
740+
741+ substream->framing = framing;
742+ substream->clock_type = clock_type;
743+ return 0;
725744 }
726745 EXPORT_SYMBOL(snd_rawmidi_input_params);
727746
@@ -963,6 +982,62 @@ static int snd_rawmidi_control_ioctl(struct snd_card *card, · 235 unmodified lines
963982 return -ENOIOCTLCMD;
964983 }
965984
985+static int receive_with_tstamp_framing(struct snd_rawmidi_substream *substream,
986+ const unsigned char *buffer, int src_count, const struct timespec64 *tstamp)
987+{
988+ struct snd_rawmidi_runtime *runtime = substream->runtime;
989+ struct snd_rawmidi_framing_tstamp *dest_ptr;
990+ struct snd_rawmidi_framing_tstamp frame = { .tv_sec = tstamp->tv_sec, .tv_nsec = tstamp->tv_nsec };
991+ int dest_frames = 0;
992+ int orig_count = src_count;
993+ int frame_size = sizeof(struct snd_rawmidi_framing_tstamp);
994+
995+ BUILD_BUG_ON(frame_size != 0x20);
996+ if (snd_BUG_ON((runtime->hw_ptr & 0x1f) != 0))
997+ return -EINVAL;
998+
999+ while (src_count > 0) {
1000+ if ((int)(runtime->buffer_size - runtime->avail) < frame_size) {
1001+ runtime->xruns += src_count;
1002+ break;
1003+ }
1004+ if (src_count >= SNDRV_RAWMIDI_FRAMING_DATA_LENGTH)
1005+ frame.length = SNDRV_RAWMIDI_FRAMING_DATA_LENGTH;
1006+ else {
1007+ frame.length = src_count;
1008+ memset(frame.data, 0, SNDRV_RAWMIDI_FRAMING_DATA_LENGTH);
1009+ }
1010+ memcpy(frame.data, buffer, frame.length);
1011+ buffer += frame.length;
1012+ src_count -= frame.length;
1013+ dest_ptr = (struct snd_rawmidi_framing_tstamp *) (runtime->buffer + runtime->hw_ptr);
1014+ *dest_ptr = frame;
1015+ runtime->avail += frame_size;
1016+ runtime->hw_ptr += frame_size;
1017+ runtime->hw_ptr %= runtime->buffer_size;
1018+ dest_frames++;
1019+ }
1020+ return orig_count - src_count;
1021+}
1022+
1023+static struct timespec64 get_framing_tstamp(struct snd_rawmidi_substream *substream)
1024+{
1025+ struct timespec64 ts64 = {0, 0};
1026+
1027+ switch (substream->clock_type) {
1028+ case SNDRV_RAWMIDI_MODE_CLOCK_MONOTONIC_RAW:
1029+ ktime_get_raw_ts64(&ts64);
1030+ break;
1031+ case SNDRV_RAWMIDI_MODE_CLOCK_MONOTONIC:
1032+ ktime_get_ts64(&ts64);
1033+ break;
1034+ case SNDRV_RAWMIDI_MODE_CLOCK_REALTIME:
1035+ ktime_get_real_ts64(&ts64);
1036+ break;
1037+ }
1038+ return ts64;
1039+}
1040+
9661041 /**
9671042 * snd_rawmidi_receive - receive the input data from the device
9681043 * @substream: the rawmidi substream
@@ -977,6 +1052,7 @@ int snd_rawmidi_receive(struct snd_rawmidi_substream *substream, · 8 unmodified lines
9771052 const unsigned char *buffer, int count)
9781053 {
9791054 unsigned long flags;
1055+ struct timespec64 ts64 = get_framing_tstamp(substream);
9801056 int result = 0, count1;
9811057 struct snd_rawmidi_runtime *runtime = substream->runtime;
9821058
@@ -987,8 +1063,11 @@ int snd_rawmidi_receive(struct snd_rawmidi_substream *substream, · 4 unmodified lines
9871063 "snd_rawmidi_receive: input is not active!!!\n");
9881064 return -EINVAL;
9891065 }
1066+
9901067 spin_lock_irqsave(&runtime->lock, flags);
991− if (count == 1) { /* special case, faster code */
1068+ if (substream->framing == SNDRV_RAWMIDI_MODE_FRAMING_TSTAMP) {
1069+ result = receive_with_tstamp_framing(substream, buffer, count, &ts64);
1070+ } else if (count == 1) { /* special case, faster code */
9921071 substream->bytes++;
9931072 if (runtime->avail < runtime->buffer_size) {
9941073 runtime->buffer[runtime->hw_ptr++] = buffer[0];
@@ -1541,6 +1620,8 @@ static void snd_rawmidi_proc_info_read(struct snd_info_entry *entry, · 546 unmodified lines
15411620 struct snd_rawmidi_substream *substream;
15421621 struct snd_rawmidi_runtime *runtime;
15431622 unsigned long buffer_size, avail, xruns;
1623+ unsigned int clock_type;
1624+ static const char *clock_names[4] = { "none", "realtime", "monotonic", "monotonic raw" };
15441625
15451626 rmidi = entry->private_data;
15461627 snd_iprintf(buffer, "%s\n\n", rmidi->name);
@@ -1596,6 +1677,14 @@ static void snd_rawmidi_proc_info_read(struct snd_info_entry *entry, · 49 unmodified lines
15961677 " Avail : %lu\n"
15971678 " Overruns : %lu\n",
15981679 buffer_size, avail, xruns);
1680+ if (substream->framing == SNDRV_RAWMIDI_MODE_FRAMING_TSTAMP) {
1681+ clock_type = substream->clock_type >> SNDRV_RAWMIDI_MODE_CLOCK_SHIFT;
1682+ if (!snd_BUG_ON(clock_type >= sizeof(clock_names)))
1683+ snd_iprintf(buffer,
1684+ " Framing : tstamp\n"
1685+ " Clock type : %s\n",
1686+ clock_names[clock_type]);
1687+ }
15991688 }
16001689 }
16011690 }
sound/core/rawmidi_compat.c+3−1
@@ -13,7 +13,8 @@ struct snd_rawmidi_params32 { · 12 unmodified lines
1313 u32 buffer_size;
1414 u32 avail_min;
1515 unsigned int no_active_sensing; /* avoid bit-field */
16− unsigned char reserved[16];
16+ unsigned int mode;
17+ unsigned char reserved[12];
1718 } __attribute__((packed));
1819
1920 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, · 5 unmodified lines
2526 if (get_user(params.stream, &src->stream) ||
2627 get_user(params.buffer_size, &src->buffer_size) ||
2728 get_user(params.avail_min, &src->avail_min) ||
29+ get_user(params.mode, &src->mode) ||
2830 get_user(val, &src->no_active_sensing))
2931 return -EFAULT;
3032 params.no_active_sensing = val;