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
81 bool opened; /* open flag */81 bool opened; /* open flag */
82 bool append; /* append flag (merge more streams) */82 bool append; /* append flag (merge more streams) */
83 bool active_sensing; /* send active sensing when close */83 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 */
84 int use_count; /* use counter (for output) */86 int use_count; /* use counter (for output) */
85 size_t bytes;87 size_t bytes;
86 struct snd_rawmidi *rmidi;88 struct snd_rawmidi *rmidi;
include/uapi/sound/asound.h+28−2
@@ -710,7 +710,7 @@ enum { · 709 unmodified lines
710 * Raw MIDI section - /dev/snd/midi??710 * Raw MIDI section - /dev/snd/midi??
711 */711 */
712 712
713#define SNDRV_RAWMIDI_VERSION SNDRV_PROTOCOL_VERSION(2, 0, 1)713#define SNDRV_RAWMIDI_VERSION SNDRV_PROTOCOL_VERSION(2, 0, 2)
714 714
715enum {715enum {
716 SNDRV_RAWMIDI_STREAM_OUTPUT = 0,716 SNDRV_RAWMIDI_STREAM_OUTPUT = 0,
@@ -736,12 +736,38 @@ struct snd_rawmidi_info { · 19 unmodified lines
736 unsigned char reserved[64]; /* reserved for future use */736 unsigned char reserved[64]; /* reserved for future use */
737};737};
738 738
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
752struct 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
739struct snd_rawmidi_params {764struct snd_rawmidi_params {
740 int stream;765 int stream;
741 size_t buffer_size; /* queue size in bytes */766 size_t buffer_size; /* queue size in bytes */
742 size_t avail_min; /* minimum avail bytes for wakeup */767 size_t avail_min; /* minimum avail bytes for wakeup */
743 unsigned int no_active_sensing: 1; /* do not send active sensing byte in close() */768 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 */
745};771};
746 772
747#ifndef __KERNEL__773#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
680 bool is_input)680 bool is_input)
681{681{
682 char *newbuf, *oldbuf;682 char *newbuf, *oldbuf;
683 unsigned int framing = params->mode & SNDRV_RAWMIDI_MODE_FRAMING_MASK;
683 684
684 if (params->buffer_size < 32 || params->buffer_size > 1024L * 1024L)685 if (params->buffer_size < 32 || params->buffer_size > 1024L * 1024L)
685 return -EINVAL;686 return -EINVAL;
687 if (framing == SNDRV_RAWMIDI_MODE_FRAMING_TSTAMP && (params->buffer_size & 0x1f) != 0)
688 return -EINVAL;
686 if (params->avail_min < 1 || params->avail_min > params->buffer_size)689 if (params->avail_min < 1 || params->avail_min > params->buffer_size)
687 return -EINVAL;690 return -EINVAL;
688 if (params->buffer_size != runtime->buffer_size) {691 if (params->buffer_size != runtime->buffer_size) {
@@ -720,8 +723,24 @@ EXPORT_SYMBOL(snd_rawmidi_output_params); · 31 unmodified lines
720int snd_rawmidi_input_params(struct snd_rawmidi_substream *substream,723int snd_rawmidi_input_params(struct snd_rawmidi_substream *substream,
721 struct snd_rawmidi_params *params)724 struct snd_rawmidi_params *params)
722{725{
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;
723 snd_rawmidi_drain_input(substream);736 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;
725}744}
726EXPORT_SYMBOL(snd_rawmidi_input_params);745EXPORT_SYMBOL(snd_rawmidi_input_params);
727 746
@@ -963,6 +982,62 @@ static int snd_rawmidi_control_ioctl(struct snd_card *card, · 235 unmodified lines
963 return -ENOIOCTLCMD;982 return -ENOIOCTLCMD;
964}983}
965 984
985static 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
1023static 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
966/**1041/**
967 * snd_rawmidi_receive - receive the input data from the device1042 * snd_rawmidi_receive - receive the input data from the device
968 * @substream: the rawmidi substream1043 * @substream: the rawmidi substream
@@ -977,6 +1052,7 @@ int snd_rawmidi_receive(struct snd_rawmidi_substream *substream, · 8 unmodified lines
977 const unsigned char *buffer, int count)1052 const unsigned char *buffer, int count)
978{1053{
979 unsigned long flags;1054 unsigned long flags;
1055 struct timespec64 ts64 = get_framing_tstamp(substream);
980 int result = 0, count1;1056 int result = 0, count1;
981 struct snd_rawmidi_runtime *runtime = substream->runtime;1057 struct snd_rawmidi_runtime *runtime = substream->runtime;
982 1058
@@ -987,8 +1063,11 @@ int snd_rawmidi_receive(struct snd_rawmidi_substream *substream, · 4 unmodified lines
987 "snd_rawmidi_receive: input is not active!!!\n");1063 "snd_rawmidi_receive: input is not active!!!\n");
988 return -EINVAL;1064 return -EINVAL;
989 }1065 }
1066
990 spin_lock_irqsave(&runtime->lock, flags);1067 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 */
992 substream->bytes++;1071 substream->bytes++;
993 if (runtime->avail < runtime->buffer_size) {1072 if (runtime->avail < runtime->buffer_size) {
994 runtime->buffer[runtime->hw_ptr++] = buffer[0];1073 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
1541 struct snd_rawmidi_substream *substream;1620 struct snd_rawmidi_substream *substream;
1542 struct snd_rawmidi_runtime *runtime;1621 struct snd_rawmidi_runtime *runtime;
1543 unsigned long buffer_size, avail, xruns;1622 unsigned long buffer_size, avail, xruns;
1623 unsigned int clock_type;
1624 static const char *clock_names[4] = { "none", "realtime", "monotonic", "monotonic raw" };
1544 1625
1545 rmidi = entry->private_data;1626 rmidi = entry->private_data;
1546 snd_iprintf(buffer, "%s\n\n", rmidi->name);1627 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
1596 " Avail : %lu\n"1677 " Avail : %lu\n"
1597 " Overruns : %lu\n",1678 " Overruns : %lu\n",
1598 buffer_size, avail, xruns);1679 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 }
1599 }1688 }
1600 }1689 }
1601 }1690 }
sound/core/rawmidi_compat.c+3−1
@@ -13,7 +13,8 @@ struct snd_rawmidi_params32 { · 12 unmodified lines
13 u32 buffer_size;13 u32 buffer_size;
14 u32 avail_min;14 u32 avail_min;
15 unsigned int no_active_sensing; /* avoid bit-field */15 unsigned int no_active_sensing; /* avoid bit-field */
16 unsigned char reserved[16];16 unsigned int mode;
17 unsigned char reserved[12];
17} __attribute__((packed));18} __attribute__((packed));
18 19
19static int snd_rawmidi_ioctl_params_compat(struct snd_rawmidi_file *rfile,20static 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
25 if (get_user(params.stream, &src->stream) ||26 if (get_user(params.stream, &src->stream) ||
26 get_user(params.buffer_size, &src->buffer_size) ||27 get_user(params.buffer_size, &src->buffer_size) ||
27 get_user(params.avail_min, &src->avail_min) ||28 get_user(params.avail_min, &src->avail_min) ||
29 get_user(params.mode, &src->mode) ||
28 get_user(val, &src->no_active_sensing))30 get_user(val, &src->no_active_sensing))
29 return -EFAULT;31 return -EFAULT;
30 params.no_active_sensing = val;32 params.no_active_sensing = val;