summaryrefslogtreecommitdiff
path: root/sound/core
diff options
context:
space:
mode:
authorTakashi Iwai <tiwai@suse.de>2023-05-23 14:14:41 +0200
committerTakashi Iwai <tiwai@suse.de>2023-05-23 14:14:47 +0200
commit03a58514d494fe50c6a6cb56604bb7bd4f46e676 (patch)
tree898613d68d3a8fcc703fe770f2025548d58c97a9 /sound/core
parent7195fb46dafb8750ed4055804cb131f376eb854e (diff)
parent6b39e30dce18114e3fc27074cee9a2b91a3639d1 (diff)
Merge branch 'topic/midi20' into for-next
This is a (largish) patch set for adding the support of MIDI 2.0 functionality, mainly targeted for USB devices. MIDI 2.0 is a complete overhaul of the 40-years old MIDI 1.0. Unlike MIDI 1.0 byte stream, MIDI 2.0 uses packets in 32bit words for Universal MIDI Packet (UMP) protocol. It supports both MIDI 1.0 commands for compatibility and the extended MIDI 2.0 commands for higher resolutions and more functions. For supporting the UMP, the patch set extends the existing ALSA rawmidi and sequencer interfaces, and adds the USB MIDI 2.0 support to the standard USB-audio driver. The rawmidi for UMP has a different device name (/dev/snd/umpC*D*) and it reads/writes UMP packet data in 32bit CPU-native endianness. For the old MIDI 1.0 applications, the legacy rawmidi interface is provided, too. As default, USB-audio driver will take the alternate setting for MIDI 2.0 interface, and the compatibility with MIDI 1.0 is provided via the rawmidi common layer. However, user may let the driver falling back to the old MIDI 1.0 interface by a module option, too. A UMP-capable rawmidi device can create the corresponding ALSA sequencer client(s) to support the UMP Endpoint and UMP Group connections. As a nature of ALSA sequencer, arbitrary connections between clients/ports are allowed, and the ALSA sequencer core performs the automatic conversions for the connections between a new UMP sequencer client and a legacy MIDI 1.0 sequencer client. It allows the existing application to use MIDI 2.0 devices without changes. The MIDI-CI, which is another major extension in MIDI 2.0, isn't covered by this patch set. It would be implemented rather in user-space. Roughly speaking, the first half of this patch set is for extending the rawmidi and USB-audio, and the second half is for extending the ALSA sequencer interface. The patch set is based on 6.4-rc2 kernel, but all patches can be cleanly applicable on 6.2 and 6.3 kernels, too (while 6.1 and older kernels would need minor adjustment for uapi header changes). The updates for alsa-lib and alsa-utils will follow shortly later. The author thanks members of MIDI Association OS/API Working Group, especially Andrew Mee, for great helps for the initial design and debugging / testing the drivers. Link: https://lore.kernel.org/r/20230523075358.9672-1-tiwai@suse.de Signed-off-by: Takashi Iwai <tiwai@suse.de>
Diffstat (limited to 'sound/core')
-rw-r--r--sound/core/Kconfig13
-rw-r--r--sound/core/Makefile3
-rw-r--r--sound/core/rawmidi.c248
-rw-r--r--sound/core/rawmidi_compat.c4
-rw-r--r--sound/core/seq/Kconfig14
-rw-r--r--sound/core/seq/Makefile3
-rw-r--r--sound/core/seq/seq_clientmgr.c557
-rw-r--r--sound/core/seq/seq_clientmgr.h27
-rw-r--r--sound/core/seq/seq_compat.c3
-rw-r--r--sound/core/seq/seq_dummy.c9
-rw-r--r--sound/core/seq/seq_memory.c98
-rw-r--r--sound/core/seq/seq_memory.h19
-rw-r--r--sound/core/seq/seq_midi.c12
-rw-r--r--sound/core/seq/seq_ports.c47
-rw-r--r--sound/core/seq/seq_ports.h23
-rw-r--r--sound/core/seq/seq_ump_client.c464
-rw-r--r--sound/core/seq/seq_ump_convert.c1203
-rw-r--r--sound/core/seq/seq_ump_convert.h22
-rw-r--r--sound/core/seq/seq_virmidi.c1
-rw-r--r--sound/core/ump.c668
-rw-r--r--sound/core/ump_convert.c520
-rw-r--r--sound/core/ump_convert.h43
22 files changed, 3712 insertions, 289 deletions
diff --git a/sound/core/Kconfig b/sound/core/Kconfig
index 12990d9a4dff..e41818e59a15 100644
--- a/sound/core/Kconfig
+++ b/sound/core/Kconfig
@@ -26,6 +26,19 @@ config SND_RAWMIDI
tristate
select SND_SEQ_DEVICE if SND_SEQUENCER != n
+config SND_UMP
+ tristate
+ select SND_RAWMIDI
+
+config SND_UMP_LEGACY_RAWMIDI
+ bool "Legacy raw MIDI support for UMP streams"
+ depends on SND_UMP
+ help
+ This option enables the legacy raw MIDI support for UMP streams.
+ When this option is set, an additional rawmidi device for the
+ legacy MIDI 1.0 byte streams is created for each UMP Endpoint.
+ The device contains 16 substreams corresponding to UMP groups.
+
config SND_COMPRESS_OFFLOAD
tristate
diff --git a/sound/core/Makefile b/sound/core/Makefile
index 2762f03d9b7b..a6b444ee2832 100644
--- a/sound/core/Makefile
+++ b/sound/core/Makefile
@@ -28,6 +28,8 @@ snd-pcm-dmaengine-objs := pcm_dmaengine.o
snd-ctl-led-objs := control_led.o
snd-rawmidi-objs := rawmidi.o
+snd-ump-objs := ump.o
+snd-ump-$(CONFIG_SND_UMP_LEGACY_RAWMIDI) += ump_convert.o
snd-timer-objs := timer.o
snd-hrtimer-objs := hrtimer.o
snd-rtctimer-objs := rtctimer.o
@@ -45,6 +47,7 @@ obj-$(CONFIG_SND_PCM) += snd-pcm.o
obj-$(CONFIG_SND_DMAENGINE_PCM) += snd-pcm-dmaengine.o
obj-$(CONFIG_SND_SEQ_DEVICE) += snd-seq-device.o
obj-$(CONFIG_SND_RAWMIDI) += snd-rawmidi.o
+obj-$(CONFIG_SND_UMP) += snd-ump.o
obj-$(CONFIG_SND_OSSEMUL) += oss/
obj-$(CONFIG_SND_SEQUENCER) += seq/
diff --git a/sound/core/rawmidi.c b/sound/core/rawmidi.c
index 7147fda66d93..2d3cec908154 100644
--- a/sound/core/rawmidi.c
+++ b/sound/core/rawmidi.c
@@ -21,6 +21,7 @@
#include <sound/control.h>
#include <sound/minors.h>
#include <sound/initval.h>
+#include <sound/ump.h>
MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
MODULE_DESCRIPTION("Midlevel RawMidi code for ALSA.");
@@ -35,7 +36,6 @@ module_param_array(amidi_map, int, NULL, 0444);
MODULE_PARM_DESC(amidi_map, "Raw MIDI device number assigned to 2nd OSS device.");
#endif /* CONFIG_SND_OSSEMUL */
-static int snd_rawmidi_free(struct snd_rawmidi *rmidi);
static int snd_rawmidi_dev_free(struct snd_device *device);
static int snd_rawmidi_dev_register(struct snd_device *device);
static int snd_rawmidi_dev_disconnect(struct snd_device *device);
@@ -73,6 +73,9 @@ struct snd_rawmidi_status64 {
#define SNDRV_RAWMIDI_IOCTL_STATUS64 _IOWR('W', 0x20, struct snd_rawmidi_status64)
+#define rawmidi_is_ump(rmidi) \
+ (IS_ENABLED(CONFIG_SND_UMP) && ((rmidi)->info_flags & SNDRV_RAWMIDI_INFO_UMP))
+
static struct snd_rawmidi *snd_rawmidi_search(struct snd_card *card, int device)
{
struct snd_rawmidi *rawmidi;
@@ -181,9 +184,23 @@ static int snd_rawmidi_runtime_create(struct snd_rawmidi_substream *substream)
}
runtime->appl_ptr = runtime->hw_ptr = 0;
substream->runtime = runtime;
+ if (rawmidi_is_ump(substream->rmidi))
+ runtime->align = 3;
return 0;
}
+/* get the current alignment (either 0 or 3) */
+static inline int get_align(struct snd_rawmidi_runtime *runtime)
+{
+ if (IS_ENABLED(CONFIG_SND_UMP))
+ return runtime->align;
+ else
+ return 0;
+}
+
+/* get the trimmed size with the current alignment */
+#define get_aligned_size(runtime, size) ((size) & ~get_align(runtime))
+
static int snd_rawmidi_runtime_free(struct snd_rawmidi_substream *substream)
{
struct snd_rawmidi_runtime *runtime = substream->runtime;
@@ -406,24 +423,15 @@ static int rawmidi_open_priv(struct snd_rawmidi *rmidi, int subdevice, int mode,
}
/* called from sound/core/seq/seq_midi.c */
-int snd_rawmidi_kernel_open(struct snd_card *card, int device, int subdevice,
+int snd_rawmidi_kernel_open(struct snd_rawmidi *rmidi, int subdevice,
int mode, struct snd_rawmidi_file *rfile)
{
- struct snd_rawmidi *rmidi;
- int err = 0;
+ int err;
if (snd_BUG_ON(!rfile))
return -EINVAL;
-
- mutex_lock(&register_mutex);
- rmidi = snd_rawmidi_search(card, device);
- if (!rmidi)
- err = -ENODEV;
- else if (!try_module_get(rmidi->card->module))
- err = -ENXIO;
- mutex_unlock(&register_mutex);
- if (err < 0)
- return err;
+ if (!try_module_get(rmidi->card->module))
+ return -ENXIO;
mutex_lock(&rmidi->open_mutex);
err = rawmidi_open_priv(rmidi, subdevice, mode, rfile);
@@ -730,6 +738,8 @@ static int resize_runtime_buffer(struct snd_rawmidi_substream *substream,
return -EINVAL;
if (params->avail_min < 1 || params->avail_min > params->buffer_size)
return -EINVAL;
+ if (params->buffer_size & get_align(runtime))
+ return -EINVAL;
if (params->buffer_size != runtime->buffer_size) {
newbuf = kvzalloc(params->buffer_size, GFP_KERNEL);
if (!newbuf)
@@ -902,6 +912,7 @@ static int snd_rawmidi_ioctl_status64(struct snd_rawmidi_file *rfile,
static long snd_rawmidi_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
struct snd_rawmidi_file *rfile;
+ struct snd_rawmidi *rmidi;
void __user *argp = (void __user *)arg;
rfile = file->private_data;
@@ -993,12 +1004,67 @@ static long snd_rawmidi_ioctl(struct file *file, unsigned int cmd, unsigned long
}
}
default:
- rmidi_dbg(rfile->rmidi,
- "rawmidi: unknown command = 0x%x\n", cmd);
+ rmidi = rfile->rmidi;
+ if (rmidi->ops && rmidi->ops->ioctl)
+ return rmidi->ops->ioctl(rmidi, cmd, argp);
+ rmidi_dbg(rmidi, "rawmidi: unknown command = 0x%x\n", cmd);
}
return -ENOTTY;
}
+/* ioctl to find the next device; either legacy or UMP depending on @find_ump */
+static int snd_rawmidi_next_device(struct snd_card *card, int __user *argp,
+ bool find_ump)
+
+{
+ struct snd_rawmidi *rmidi;
+ int device;
+ bool is_ump;
+
+ if (get_user(device, argp))
+ return -EFAULT;
+ if (device >= SNDRV_RAWMIDI_DEVICES) /* next device is -1 */
+ device = SNDRV_RAWMIDI_DEVICES - 1;
+ mutex_lock(&register_mutex);
+ device = device < 0 ? 0 : device + 1;
+ for (; device < SNDRV_RAWMIDI_DEVICES; device++) {
+ rmidi = snd_rawmidi_search(card, device);
+ if (!rmidi)
+ continue;
+ is_ump = rawmidi_is_ump(rmidi);
+ if (find_ump == is_ump)
+ break;
+ }
+ if (device == SNDRV_RAWMIDI_DEVICES)
+ device = -1;
+ mutex_unlock(&register_mutex);
+ if (put_user(device, argp))
+ return -EFAULT;
+ return 0;
+}
+
+#if IS_ENABLED(CONFIG_SND_UMP)
+/* inquiry of UMP endpoint and block info via control API */
+static int snd_rawmidi_call_ump_ioctl(struct snd_card *card, int cmd,
+ void __user *argp)
+{
+ struct snd_ump_endpoint_info __user *info = argp;
+ struct snd_rawmidi *rmidi;
+ int device, ret;
+
+ if (get_user(device, &info->device))
+ return -EFAULT;
+ mutex_lock(&register_mutex);
+ rmidi = snd_rawmidi_search(card, device);
+ if (rmidi && rmidi->ops && rmidi->ops->ioctl)
+ ret = rmidi->ops->ioctl(rmidi, cmd, argp);
+ else
+ ret = -ENXIO;
+ mutex_unlock(&register_mutex);
+ return ret;
+}
+#endif
+
static int snd_rawmidi_control_ioctl(struct snd_card *card,
struct snd_ctl_file *control,
unsigned int cmd,
@@ -1008,27 +1074,15 @@ static int snd_rawmidi_control_ioctl(struct snd_card *card,
switch (cmd) {
case SNDRV_CTL_IOCTL_RAWMIDI_NEXT_DEVICE:
- {
- int device;
-
- if (get_user(device, (int __user *)argp))
- return -EFAULT;
- if (device >= SNDRV_RAWMIDI_DEVICES) /* next device is -1 */
- device = SNDRV_RAWMIDI_DEVICES - 1;
- mutex_lock(&register_mutex);
- device = device < 0 ? 0 : device + 1;
- while (device < SNDRV_RAWMIDI_DEVICES) {
- if (snd_rawmidi_search(card, device))
- break;
- device++;
- }
- if (device == SNDRV_RAWMIDI_DEVICES)
- device = -1;
- mutex_unlock(&register_mutex);
- if (put_user(device, (int __user *)argp))
- return -EFAULT;
- return 0;
- }
+ return snd_rawmidi_next_device(card, argp, false);
+#if IS_ENABLED(CONFIG_SND_UMP)
+ case SNDRV_CTL_IOCTL_UMP_NEXT_DEVICE:
+ return snd_rawmidi_next_device(card, argp, true);
+ case SNDRV_CTL_IOCTL_UMP_ENDPOINT_INFO:
+ return snd_rawmidi_call_ump_ioctl(card, SNDRV_UMP_IOCTL_ENDPOINT_INFO, argp);
+ case SNDRV_CTL_IOCTL_UMP_BLOCK_INFO:
+ return snd_rawmidi_call_ump_ioctl(card, SNDRV_UMP_IOCTL_BLOCK_INFO, argp);
+#endif
case SNDRV_CTL_IOCTL_RAWMIDI_PREFER_SUBDEVICE:
{
int val;
@@ -1052,12 +1106,13 @@ static int receive_with_tstamp_framing(struct snd_rawmidi_substream *substream,
struct snd_rawmidi_framing_tstamp frame = { .tv_sec = tstamp->tv_sec, .tv_nsec = tstamp->tv_nsec };
int orig_count = src_count;
int frame_size = sizeof(struct snd_rawmidi_framing_tstamp);
+ int align = get_align(runtime);
BUILD_BUG_ON(frame_size != 0x20);
if (snd_BUG_ON((runtime->hw_ptr & 0x1f) != 0))
return -EINVAL;
- while (src_count > 0) {
+ while (src_count > align) {
if ((int)(runtime->buffer_size - runtime->avail) < frame_size) {
runtime->xruns += src_count;
break;
@@ -1065,7 +1120,9 @@ static int receive_with_tstamp_framing(struct snd_rawmidi_substream *substream,
if (src_count >= SNDRV_RAWMIDI_FRAMING_DATA_LENGTH)
frame.length = SNDRV_RAWMIDI_FRAMING_DATA_LENGTH;
else {
- frame.length = src_count;
+ frame.length = get_aligned_size(runtime, src_count);
+ if (!frame.length)
+ break;
memset(frame.data, 0, SNDRV_RAWMIDI_FRAMING_DATA_LENGTH);
}
memcpy(frame.data, buffer, frame.length);
@@ -1129,6 +1186,10 @@ int snd_rawmidi_receive(struct snd_rawmidi_substream *substream,
goto unlock;
}
+ count = get_aligned_size(runtime, count);
+ if (!count)
+ goto unlock;
+
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 */
@@ -1148,6 +1209,9 @@ int snd_rawmidi_receive(struct snd_rawmidi_substream *substream,
count1 = count;
if (count1 > (int)(runtime->buffer_size - runtime->avail))
count1 = runtime->buffer_size - runtime->avail;
+ count1 = get_aligned_size(runtime, count1);
+ if (!count1)
+ goto unlock;
memcpy(runtime->buffer + runtime->hw_ptr, buffer, count1);
runtime->hw_ptr += count1;
runtime->hw_ptr %= runtime->buffer_size;
@@ -1348,12 +1412,18 @@ static int __snd_rawmidi_transmit_peek(struct snd_rawmidi_substream *substream,
count1 = count;
if (count1 > (int)(runtime->buffer_size - runtime->avail))
count1 = runtime->buffer_size - runtime->avail;
+ count1 = get_aligned_size(runtime, count1);
+ if (!count1)
+ goto __skip;
memcpy(buffer, runtime->buffer + runtime->hw_ptr, count1);
count -= count1;
result += count1;
if (count > 0) {
if (count > (int)(runtime->buffer_size - runtime->avail - count1))
count = runtime->buffer_size - runtime->avail - count1;
+ count = get_aligned_size(runtime, count);
+ if (!count)
+ goto __skip;
memcpy(buffer + count1, runtime->buffer, count);
result += count;
}
@@ -1410,6 +1480,7 @@ static int __snd_rawmidi_transmit_ack(struct snd_rawmidi_substream *substream,
return -EINVAL;
}
snd_BUG_ON(runtime->avail + count > runtime->buffer_size);
+ count = get_aligned_size(runtime, count);
runtime->hw_ptr += count;
runtime->hw_ptr %= runtime->buffer_size;
runtime->avail += count;
@@ -1696,6 +1767,11 @@ static void snd_rawmidi_proc_info_read(struct snd_info_entry *entry,
rmidi = entry->private_data;
snd_iprintf(buffer, "%s\n\n", rmidi->name);
+ if (IS_ENABLED(CONFIG_SND_UMP))
+ snd_iprintf(buffer, "Type: %s\n",
+ rawmidi_is_ump(rmidi) ? "UMP" : "Legacy");
+ if (rmidi->ops->proc_read)
+ rmidi->ops->proc_read(entry, buffer);
mutex_lock(&rmidi->open_mutex);
if (rmidi->info_flags & SNDRV_RAWMIDI_INFO_OUTPUT) {
list_for_each_entry(substream,
@@ -1806,25 +1882,12 @@ static void release_rawmidi_device(struct device *dev)
kfree(container_of(dev, struct snd_rawmidi, dev));
}
-/**
- * snd_rawmidi_new - create a rawmidi instance
- * @card: the card instance
- * @id: the id string
- * @device: the device index
- * @output_count: the number of output streams
- * @input_count: the number of input streams
- * @rrawmidi: the pointer to store the new rawmidi instance
- *
- * Creates a new rawmidi instance.
- * Use snd_rawmidi_set_ops() to set the operators to the new instance.
- *
- * Return: Zero if successful, or a negative error code on failure.
- */
-int snd_rawmidi_new(struct snd_card *card, char *id, int device,
- int output_count, int input_count,
- struct snd_rawmidi **rrawmidi)
+/* used for both rawmidi and ump */
+int snd_rawmidi_init(struct snd_rawmidi *rmidi,
+ struct snd_card *card, char *id, int device,
+ int output_count, int input_count,
+ unsigned int info_flags)
{
- struct snd_rawmidi *rmidi;
int err;
static const struct snd_device_ops ops = {
.dev_free = snd_rawmidi_dev_free,
@@ -1832,50 +1895,78 @@ int snd_rawmidi_new(struct snd_card *card, char *id, int device,
.dev_disconnect = snd_rawmidi_dev_disconnect,
};
- if (snd_BUG_ON(!card))
- return -ENXIO;
- if (rrawmidi)
- *rrawmidi = NULL;
- rmidi = kzalloc(sizeof(*rmidi), GFP_KERNEL);
- if (!rmidi)
- return -ENOMEM;
rmidi->card = card;
rmidi->device = device;
mutex_init(&rmidi->open_mutex);
init_waitqueue_head(&rmidi->open_wait);
INIT_LIST_HEAD(&rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT].substreams);
INIT_LIST_HEAD(&rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT].substreams);
+ rmidi->info_flags = info_flags;
if (id != NULL)
strscpy(rmidi->id, id, sizeof(rmidi->id));
snd_device_initialize(&rmidi->dev, card);
rmidi->dev.release = release_rawmidi_device;
- dev_set_name(&rmidi->dev, "midiC%iD%i", card->number, device);
+ if (rawmidi_is_ump(rmidi))
+ dev_set_name(&rmidi->dev, "umpC%iD%i", card->number, device);
+ else
+ dev_set_name(&rmidi->dev, "midiC%iD%i", card->number, device);
err = snd_rawmidi_alloc_substreams(rmidi,
&rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT],
SNDRV_RAWMIDI_STREAM_INPUT,
input_count);
if (err < 0)
- goto error;
+ return err;
err = snd_rawmidi_alloc_substreams(rmidi,
&rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT],
SNDRV_RAWMIDI_STREAM_OUTPUT,
output_count);
if (err < 0)
- goto error;
+ return err;
err = snd_device_new(card, SNDRV_DEV_RAWMIDI, rmidi, &ops);
if (err < 0)
- goto error;
+ return err;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_rawmidi_init);
+
+/**
+ * snd_rawmidi_new - create a rawmidi instance
+ * @card: the card instance
+ * @id: the id string
+ * @device: the device index
+ * @output_count: the number of output streams
+ * @input_count: the number of input streams
+ * @rrawmidi: the pointer to store the new rawmidi instance
+ *
+ * Creates a new rawmidi instance.
+ * Use snd_rawmidi_set_ops() to set the operators to the new instance.
+ *
+ * Return: Zero if successful, or a negative error code on failure.
+ */
+int snd_rawmidi_new(struct snd_card *card, char *id, int device,
+ int output_count, int input_count,
+ struct snd_rawmidi **rrawmidi)
+{
+ struct snd_rawmidi *rmidi;
+ int err;
if (rrawmidi)
+ *rrawmidi = NULL;
+ rmidi = kzalloc(sizeof(*rmidi), GFP_KERNEL);
+ if (!rmidi)
+ return -ENOMEM;
+ err = snd_rawmidi_init(rmidi, card, id, device,
+ output_count, input_count, 0);
+ if (err < 0) {
+ snd_rawmidi_free(rmidi);
+ return err;
+ }
+ if (rrawmidi)
*rrawmidi = rmidi;
return 0;
-
- error:
- snd_rawmidi_free(rmidi);
- return err;
}
EXPORT_SYMBOL(snd_rawmidi_new);
@@ -1890,7 +1981,8 @@ static void snd_rawmidi_free_substreams(struct snd_rawmidi_str *stream)
}
}
-static int snd_rawmidi_free(struct snd_rawmidi *rmidi)
+/* called from ump.c, too */
+int snd_rawmidi_free(struct snd_rawmidi *rmidi)
{
if (!rmidi)
return 0;
@@ -1907,6 +1999,7 @@ static int snd_rawmidi_free(struct snd_rawmidi *rmidi)
put_device(&rmidi->dev);
return 0;
}
+EXPORT_SYMBOL_GPL(snd_rawmidi_free);
static int snd_rawmidi_dev_free(struct snd_device *device)
{
@@ -1957,7 +2050,8 @@ static int snd_rawmidi_dev_register(struct snd_device *device)
}
#ifdef CONFIG_SND_OSSEMUL
rmidi->ossreg = 0;
- if ((int)rmidi->device == midi_map[rmidi->card->number]) {
+ if (!rawmidi_is_ump(rmidi) &&
+ (int)rmidi->device == midi_map[rmidi->card->number]) {
if (snd_register_oss_device(SNDRV_OSS_DEVICE_TYPE_MIDI,
rmidi->card, 0, &snd_rawmidi_f_ops,
rmidi) < 0) {
@@ -1971,7 +2065,8 @@ static int snd_rawmidi_dev_register(struct snd_device *device)
#endif
}
}
- if ((int)rmidi->device == amidi_map[rmidi->card->number]) {
+ if (!rawmidi_is_ump(rmidi) &&
+ (int)rmidi->device == amidi_map[rmidi->card->number]) {
if (snd_register_oss_device(SNDRV_OSS_DEVICE_TYPE_MIDI,
rmidi->card, 1, &snd_rawmidi_f_ops,
rmidi) < 0) {
@@ -1995,7 +2090,8 @@ static int snd_rawmidi_dev_register(struct snd_device *device)
}
rmidi->proc_entry = entry;
#if IS_ENABLED(CONFIG_SND_SEQUENCER)
- if (!rmidi->ops || !rmidi->ops->dev_register) { /* own registration mechanism */
+ /* no own registration mechanism? */
+ if (!rmidi->ops || !rmidi->ops->dev_register) {
if (snd_seq_device_new(rmidi->card, rmidi->device, SNDRV_SEQ_DEV_ID_MIDISYNTH, 0, &rmidi->seq_dev) >= 0) {
rmidi->seq_dev->private_data = rmidi;
rmidi->seq_dev->private_free = snd_rawmidi_dev_seq_free;
diff --git a/sound/core/rawmidi_compat.c b/sound/core/rawmidi_compat.c
index 68a93443583c..b81b30d82f88 100644
--- a/sound/core/rawmidi_compat.c
+++ b/sound/core/rawmidi_compat.c
@@ -111,6 +111,10 @@ static long snd_rawmidi_ioctl_compat(struct file *file, unsigned int cmd, unsign
case SNDRV_RAWMIDI_IOCTL_INFO:
case SNDRV_RAWMIDI_IOCTL_DROP:
case SNDRV_RAWMIDI_IOCTL_DRAIN:
+#if IS_ENABLED(CONFIG_SND_UMP)
+ case SNDRV_UMP_IOCTL_ENDPOINT_INFO:
+ case SNDRV_UMP_IOCTL_BLOCK_INFO:
+#endif
return snd_rawmidi_ioctl(file, cmd, (unsigned long)argp);
case SNDRV_RAWMIDI_IOCTL_PARAMS32:
return snd_rawmidi_ioctl_params_compat(rfile, argp);
diff --git a/sound/core/seq/Kconfig b/sound/core/seq/Kconfig
index f84718a44980..c14981daf943 100644
--- a/sound/core/seq/Kconfig
+++ b/sound/core/seq/Kconfig
@@ -60,4 +60,18 @@ config SND_SEQ_MIDI_EMUL
config SND_SEQ_VIRMIDI
tristate
+config SND_SEQ_UMP
+ bool "Support for UMP events"
+ default y if SND_SEQ_UMP_CLIENT
+ help
+ Say Y here to enable the support for handling UMP (Universal MIDI
+ Packet) events via ALSA sequencer infrastructure, which is an
+ essential feature for enabling MIDI 2.0 support.
+ It includes the automatic conversion of ALSA sequencer events
+ among legacy and UMP clients.
+
+config SND_SEQ_UMP_CLIENT
+ tristate
+ def_tristate SND_UMP
+
endif # SND_SEQUENCER
diff --git a/sound/core/seq/Makefile b/sound/core/seq/Makefile
index 3a2177a7e50c..990eec7c83ad 100644
--- a/sound/core/seq/Makefile
+++ b/sound/core/seq/Makefile
@@ -8,17 +8,20 @@ snd-seq-objs := seq.o seq_lock.o seq_clientmgr.o seq_memory.o seq_queue.o \
seq_fifo.o seq_prioq.o seq_timer.o \
seq_system.o seq_ports.o
snd-seq-$(CONFIG_SND_PROC_FS) += seq_info.o
+snd-seq-$(CONFIG_SND_SEQ_UMP) += seq_ump_convert.o
snd-seq-midi-objs := seq_midi.o
snd-seq-midi-emul-objs := seq_midi_emul.o
snd-seq-midi-event-objs := seq_midi_event.o
snd-seq-dummy-objs := seq_dummy.o
snd-seq-virmidi-objs := seq_virmidi.o
+snd-seq-ump-client-objs := seq_ump_client.o
obj-$(CONFIG_SND_SEQUENCER) += snd-seq.o
obj-$(CONFIG_SND_SEQUENCER_OSS) += oss/
obj-$(CONFIG_SND_SEQ_DUMMY) += snd-seq-dummy.o
obj-$(CONFIG_SND_SEQ_MIDI) += snd-seq-midi.o
+obj-$(CONFIG_SND_SEQ_UMP_CLIENT) += snd-seq-ump-client.o
obj-$(CONFIG_SND_SEQ_MIDI_EMUL) += snd-seq-midi-emul.o
obj-$(CONFIG_SND_SEQ_MIDI_EVENT) += snd-seq-midi-event.o
obj-$(CONFIG_SND_SEQ_VIRMIDI) += snd-seq-virmidi.o
diff --git a/sound/core/seq/seq_clientmgr.c b/sound/core/seq/seq_clientmgr.c
index 2d707afa1ef1..948ae45e0cc3 100644
--- a/sound/core/seq/seq_clientmgr.c
+++ b/sound/core/seq/seq_clientmgr.c
@@ -14,12 +14,14 @@
#include <linux/kmod.h>
#include <sound/seq_kernel.h>
+#include <sound/ump.h>
#include "seq_clientmgr.h"
#include "seq_memory.h"
#include "seq_queue.h"
#include "seq_timer.h"
#include "seq_info.h"
#include "seq_system.h"
+#include "seq_ump_convert.h"
#include <sound/seq_device.h>
#ifdef CONFIG_COMPAT
#include <linux/compat.h>
@@ -70,6 +72,10 @@ static int snd_seq_deliver_single_event(struct snd_seq_client *client,
struct snd_seq_event *event,
int filter, int atomic, int hop);
+#if IS_ENABLED(CONFIG_SND_SEQ_UMP)
+static void free_ump_info(struct snd_seq_client *client);
+#endif
+
/*
*/
static inline unsigned short snd_seq_file_flags(struct file *file)
@@ -239,6 +245,7 @@ static struct snd_seq_client *seq_create_client1(int client_index, int poolsize)
mutex_init(&client->ports_mutex);
INIT_LIST_HEAD(&client->ports_list_head);
mutex_init(&client->ioctl_mutex);
+ client->ump_endpoint_port = -1;
/* find free slot in the client table */
spin_lock_irq(&clients_lock);
@@ -380,6 +387,9 @@ static int snd_seq_release(struct inode *inode, struct file *file)
seq_free_client(client);
if (client->data.user.fifo)
snd_seq_fifo_delete(&client->data.user.fifo);
+#if IS_ENABLED(CONFIG_SND_SEQ_UMP)
+ free_ump_info(client);
+#endif
put_pid(client->data.user.owner);
kfree(client);
}
@@ -387,6 +397,15 @@ static int snd_seq_release(struct inode *inode, struct file *file)
return 0;
}
+static bool event_is_compatible(const struct snd_seq_client *client,
+ const struct snd_seq_event *ev)
+{
+ if (snd_seq_ev_is_ump(ev) && !client->midi_version)
+ return false;
+ if (snd_seq_ev_is_ump(ev) && snd_seq_ev_is_variable(ev))
+ return false;
+ return true;
+}
/* handle client read() */
/* possible error values:
@@ -400,6 +419,7 @@ static ssize_t snd_seq_read(struct file *file, char __user *buf, size_t count,
{
struct snd_seq_client *client = file->private_data;
struct snd_seq_fifo *fifo;
+ size_t aligned_size;
int err;
long result = 0;
struct snd_seq_event_cell *cell;
@@ -431,43 +451,54 @@ static ssize_t snd_seq_read(struct file *file, char __user *buf, size_t count,
err = 0;
snd_seq_fifo_lock(fifo);
+ if (client->midi_version > 0)
+ aligned_size = sizeof(struct snd_seq_ump_event);
+ else
+ aligned_size = sizeof(struct snd_seq_event);
+
/* while data available in queue */
- while (count >= sizeof(struct snd_seq_event)) {
+ while (count >= aligned_size) {
int nonblock;
nonblock = (file->f_flags & O_NONBLOCK) || result > 0;
err = snd_seq_fifo_cell_out(fifo, &cell, nonblock);
if (err < 0)
break;
+ if (!event_is_compatible(client, &cell->event)) {
+ snd_seq_cell_free(cell);
+ cell = NULL;
+ continue;
+ }
if (snd_seq_ev_is_variable(&cell->event)) {
- struct snd_seq_event tmpev;
- tmpev = cell->event;
+ struct snd_seq_ump_event tmpev;
+
+ memcpy(&tmpev, &cell->event, aligned_size);
tmpev.data.ext.len &= ~SNDRV_SEQ_EXT_MASK;
- if (copy_to_user(buf, &tmpev, sizeof(struct snd_seq_event))) {
+ if (copy_to_user(buf, &tmpev, aligned_size)) {
err = -EFAULT;
break;
}
- count -= sizeof(struct snd_seq_event);
- buf += sizeof(struct snd_seq_event);
+ count -= aligned_size;
+ buf += aligned_size;
err = snd_seq_expand_var_event(&cell->event, count,
(char __force *)buf, 0,
- sizeof(struct snd_seq_event));
+ aligned_size);
if (err < 0)
break;
result += err;
count -= err;
buf += err;
} else {
- if (copy_to_user(buf, &cell->event, sizeof(struct snd_seq_event))) {
+ if (copy_to_user(buf, &cell->event, aligned_size)) {
err = -EFAULT;
break;
}
- count -= sizeof(struct snd_seq_event);
- buf += sizeof(struct snd_seq_event);
+ count -= aligned_size;
+ buf += aligned_size;
}
snd_seq_cell_free(cell);
cell = NULL; /* to be sure */
- result += sizeof(struct snd_seq_event);
+ result += aligned_size;
}
if (err < 0) {
@@ -590,6 +621,27 @@ static int update_timestamp_of_queue(struct snd_seq_event *event,
return 1;
}
+/* deliver a single event; called from below and UMP converter */
+int __snd_seq_deliver_single_event(struct snd_seq_client *dest,
+ struct snd_seq_client_port *dest_port,
+ struct snd_seq_event *event,
+ int atomic, int hop)
+{
+ switch (dest->type) {
+ case USER_CLIENT:
+ if (!dest->data.user.fifo)
+ return 0;
+ return snd_seq_fifo_event_in(dest->data.user.fifo, event);
+ case KERNEL_CLIENT:
+ if (!dest_port->event_input)
+ return 0;
+ return dest_port->event_input(event,
+ snd_seq_ev_is_direct(event),
+ dest_port->private_data,
+ atomic, hop);
+ }
+ return 0;
+}
/*
* deliver an event to the specified destination.
@@ -626,22 +678,22 @@ static int snd_seq_deliver_single_event(struct snd_seq_client *client,
update_timestamp_of_queue(event, dest_port->time_queue,
dest_port->time_real);
- switch (dest->type) {
- case USER_CLIENT:
- if (dest->data.user.fifo)
- result = snd_seq_fifo_event_in(dest->data.user.fifo, event);
- break;
+#if IS_ENABLED(CONFIG_SND_SEQ_UMP)
+ if (!(dest->filter & SNDRV_SEQ_FILTER_NO_CONVERT)) {
+ if (snd_seq_ev_is_ump(event)) {
+ result = snd_seq_deliver_from_ump(client, dest, dest_port,
+ event, atomic, hop);
+ goto __skip;
+ } else if (snd_seq_client_is_ump(dest)) {
+ result = snd_seq_deliver_to_ump(client, dest, dest_port,
+ event, atomic, hop);
+ goto __skip;
+ }
+ }
+#endif /* CONFIG_SND_SEQ_UMP */
- case KERNEL_CLIENT:
- if (dest_port->event_input == NULL)
- break;
- result = dest_port->event_input(event, direct,
- dest_port->private_data,
+ result = __snd_seq_deliver_single_event(dest, dest_port, event,
atomic, hop);
- break;
- default:
- break;
- }
__skip:
if (dest_port)
@@ -659,21 +711,20 @@ static int snd_seq_deliver_single_event(struct snd_seq_client *client,
/*
* send the event to all subscribers:
*/
-static int deliver_to_subscribers(struct snd_seq_client *client,
- struct snd_seq_event *event,
- int atomic, int hop)
+static int __deliver_to_subscribers(struct snd_seq_client *client,
+ struct snd_seq_event *event,
+ struct snd_seq_client_port *src_port,
+ int atomic, int hop)
{
struct snd_seq_subscribers *subs;
int err, result = 0, num_ev = 0;
- struct snd_seq_event event_saved;
- struct snd_seq_client_port *src_port;
+ union __snd_seq_event event_saved;
+ size_t saved_size;
struct snd_seq_port_subs_info *grp;
- src_port = snd_seq_port_use_ptr(client, event->source.port);
- if (src_port == NULL)
- return -EINVAL; /* invalid source port */
/* save original event record */
- event_saved = *event;
+ saved_size = snd_seq_event_packet_size(event);
+ memcpy(&event_saved, event, saved_size);
grp = &src_port->c_src;
/* lock list */
@@ -700,103 +751,40 @@ static int deliver_to_subscribers(struct snd_seq_client *client,
}
num_ev++;
/* restore original event record */
- *event = event_saved;
+ memcpy(event, &event_saved, saved_size);
}
if (atomic)
read_unlock(&grp->list_lock);
else
up_read(&grp->list_mutex);
- *event = event_saved; /* restore */
- snd_seq_port_unlock(src_port);
- return (result < 0) ? result : num_ev;
-}
-
-
-#ifdef SUPPORT_BROADCAST
-/*
- * broadcast to all ports:
- */
-static int port_broadcast_event(struct snd_seq_client *client,
- struct snd_seq_event *event,
- int atomic, int hop)
-{
- int num_ev = 0, err, result = 0;
- struct snd_seq_client *dest_client;
- struct snd_seq_client_port *port;
-
- dest_client = get_event_dest_client(event, SNDRV_SEQ_FILTER_BROADCAST);
- if (dest_client == NULL)
- return 0; /* no matching destination */
-
- read_lock(&dest_client->ports_lock);
- list_for_each_entry(port, &dest_client->ports_list_head, list) {
- event->dest.port = port->addr.port;
- /* pass NULL as source client to avoid error bounce */
- err = snd_seq_deliver_single_event(NULL, event,
- SNDRV_SEQ_FILTER_BROADCAST,
- atomic, hop);
- if (err < 0) {
- /* save first error that occurs and continue */
- if (!result)
- result = err;
- continue;
- }
- num_ev++;
- }
- read_unlock(&dest_client->ports_lock);
- snd_seq_client_unlock(dest_client);
- event->dest.port = SNDRV_SEQ_ADDRESS_BROADCAST; /* restore */
+ memcpy(event, &event_saved, saved_size);
return (result < 0) ? result : num_ev;
}
-/*
- * send the event to all clients:
- * if destination port is also ADDRESS_BROADCAST, deliver to all ports.
- */
-static int broadcast_event(struct snd_seq_client *client,
- struct snd_seq_event *event, int atomic, int hop)
+static int deliver_to_subscribers(struct snd_seq_client *client,
+ struct snd_seq_event *event,
+ int atomic, int hop)
{
- int err, result = 0, num_ev = 0;
- int dest;
- struct snd_seq_addr addr;
-
- addr = event->dest; /* save */
+ struct snd_seq_client_port *src_port;
+ int ret = 0, ret2;
- for (dest = 0; dest < SNDRV_SEQ_MAX_CLIENTS; dest++) {
- /* don't send to itself */
- if (dest == client->number)
- continue;
- event->dest.client = dest;
- event->dest.port = addr.port;
- if (addr.port == SNDRV_SEQ_ADDRESS_BROADCAST)
- err = port_broadcast_event(client, event, atomic, hop);
- else
- /* pass NULL as source client to avoid error bounce */
- err = snd_seq_deliver_single_event(NULL, event,
- SNDRV_SEQ_FILTER_BROADCAST,
- atomic, hop);
- if (err < 0) {
- /* save first error that occurs and continue */
- if (!result)
- result = err;
- continue;
- }
- num_ev += err;
+ src_port = snd_seq_port_use_ptr(client, event->source.port);
+ if (src_port) {
+ ret = __deliver_to_subscribers(client, event, src_port, atomic, hop);
+ snd_seq_port_unlock(src_port);
}
- event->dest = addr; /* restore */
- return (result < 0) ? result : num_ev;
-}
+ if (client->ump_endpoint_port < 0 ||
+ event->source.port == client->ump_endpoint_port)
+ return ret;
-/* multicast - not supported yet */
-static int multicast_event(struct snd_seq_client *client, struct snd_seq_event *event,
- int atomic, int hop)
-{
- pr_debug("ALSA: seq: multicast not supported yet.\n");
- return 0; /* ignored */
+ src_port = snd_seq_port_use_ptr(client, client->ump_endpoint_port);
+ if (!src_port)
+ return ret;
+ ret2 = __deliver_to_subscribers(client, event, src_port, atomic, hop);
+ snd_seq_port_unlock(src_port);
+ return ret2 < 0 ? ret2 : ret;
}
-#endif /* SUPPORT_BROADCAST */
-
/* deliver an event to the destination port(s).
* if the event is to subscribers or broadcast, the event is dispatched
@@ -826,15 +814,6 @@ static int snd_seq_deliver_event(struct snd_seq_client *client, struct snd_seq_e
if (event->queue == SNDRV_SEQ_ADDRESS_SUBSCRIBERS ||
event->dest.client == SNDRV_SEQ_ADDRESS_SUBSCRIBERS)
result = deliver_to_subscribers(client, event, atomic, hop);
-#ifdef SUPPORT_BROADCAST
- else if (event->queue == SNDRV_SEQ_ADDRESS_BROADCAST ||
- event->dest.client == SNDRV_SEQ_ADDRESS_BROADCAST)
- result = broadcast_event(client, event, atomic, hop);
- else if (event->dest.client >= SNDRV_SEQ_MAX_CLIENTS)
- result = multicast_event(client, event, atomic, hop);
- else if (event->dest.port == SNDRV_SEQ_ADDRESS_BROADCAST)
- result = port_broadcast_event(client, event, atomic, hop);
-#endif
else
result = snd_seq_deliver_single_event(client, event, 0, atomic, hop);
@@ -865,7 +844,8 @@ int snd_seq_dispatch_event(struct snd_seq_event_cell *cell, int atomic, int hop)
return -EINVAL;
}
- if (cell->event.type == SNDRV_SEQ_EVENT_NOTE) {
+ if (!snd_seq_ev_is_ump(&cell->event) &&
+ cell->event.type == SNDRV_SEQ_EVENT_NOTE) {
/* NOTE event:
* the event cell is re-used as a NOTE-OFF event and
* enqueued again.
@@ -889,7 +869,7 @@ int snd_seq_dispatch_event(struct snd_seq_event_cell *cell, int atomic, int hop)
/* add the duration time */
switch (ev->flags & SNDRV_SEQ_TIME_STAMP_MASK) {
case SNDRV_SEQ_TIME_STAMP_TICK:
- ev->time.tick += ev->data.note.duration;
+ cell->event.time.tick += ev->data.note.duration;
break;
case SNDRV_SEQ_TIME_STAMP_REAL:
/* unit for duration is ms */
@@ -936,14 +916,7 @@ static int snd_seq_client_enqueue_event(struct snd_seq_client *client,
if (event->queue == SNDRV_SEQ_ADDRESS_SUBSCRIBERS) {
event->dest.client = SNDRV_SEQ_ADDRESS_SUBSCRIBERS;
event->queue = SNDRV_SEQ_QUEUE_DIRECT;
- } else
-#ifdef SUPPORT_BROADCAST
- if (event->queue == SNDRV_SEQ_ADDRESS_BROADCAST) {
- event->dest.client = SNDRV_SEQ_ADDRESS_BROADCAST;
- event->queue = SNDRV_SEQ_QUEUE_DIRECT;
- }
-#endif
- if (event->dest.client == SNDRV_SEQ_ADDRESS_SUBSCRIBERS) {
+ } else if (event->dest.client == SNDRV_SEQ_ADDRESS_SUBSCRIBERS) {
/* check presence of source port */
struct snd_seq_client_port *src_port = snd_seq_port_use_ptr(client, event->source.port);
if (src_port == NULL)
@@ -953,7 +926,8 @@ static int snd_seq_client_enqueue_event(struct snd_seq_client *client,
/* direct event processing without enqueued */
if (snd_seq_ev_is_direct(event)) {
- if (event->type == SNDRV_SEQ_EVENT_NOTE)
+ if (!snd_seq_ev_is_ump(event) &&
+ event->type == SNDRV_SEQ_EVENT_NOTE)
return -EINVAL; /* this event must be enqueued! */
return snd_seq_deliver_event(client, event, atomic, hop);
}
@@ -1023,7 +997,8 @@ static ssize_t snd_seq_write(struct file *file, const char __user *buf,
struct snd_seq_client *client = file->private_data;
int written = 0, len;
int err, handled;
- struct snd_seq_event event;
+ union __snd_seq_event __event;
+ struct snd_seq_event *ev = &__event.legacy;
if (!(snd_seq_file_flags(file) & SNDRV_SEQ_LFLG_OUTPUT))
return -ENXIO;
@@ -1049,49 +1024,66 @@ static ssize_t snd_seq_write(struct file *file, const char __user *buf,
err = -EINVAL;
while (count >= sizeof(struct snd_seq_event)) {
/* Read in the event header from the user */
- len = sizeof(event);
- if (copy_from_user(&event, buf, len)) {
+ len = sizeof(struct snd_seq_event);
+ if (copy_from_user(ev, buf, len)) {
err = -EFAULT;
break;
}
- event.source.client = client->number; /* fill in client number */
+ /* read in the rest bytes for UMP events */
+ if (snd_seq_ev_is_ump(ev)) {
+ if (count < sizeof(struct snd_seq_ump_event))
+ break;
+ if (copy_from_user((char *)ev + len, buf + len,
+ sizeof(struct snd_seq_ump_event) - len)) {
+ err = -EFAULT;
+ break;
+ }
+ len = sizeof(struct snd_seq_ump_event);
+ }
+
+ ev->source.client = client->number; /* fill in client number */
/* Check for extension data length */
- if (check_event_type_and_length(&event)) {
+ if (check_event_type_and_length(ev)) {
err = -EINVAL;
break;
}
- /* check for special events */
- if (event.type == SNDRV_SEQ_EVENT_NONE)
- goto __skip_event;
- else if (snd_seq_ev_is_reserved(&event)) {
+ if (!event_is_compatible(client, ev)) {
err = -EINVAL;
break;
}
- if (snd_seq_ev_is_variable(&event)) {
- int extlen = event.data.ext.len & ~SNDRV_SEQ_EXT_MASK;
+ /* check for special events */
+ if (!snd_seq_ev_is_ump(ev)) {
+ if (ev->type == SNDRV_SEQ_EVENT_NONE)
+ goto __skip_event;
+ else if (snd_seq_ev_is_reserved(ev)) {
+ err = -EINVAL;
+ break;
+ }
+ }
+
+ if (snd_seq_ev_is_variable(ev)) {
+ int extlen = ev->data.ext.len & ~SNDRV_SEQ_EXT_MASK;
if ((size_t)(extlen + len) > count) {
/* back out, will get an error this time or next */
err = -EINVAL;
break;
}
/* set user space pointer */
- event.data.ext.len = extlen | SNDRV_SEQ_EXT_USRPTR;
- event.data.ext.ptr = (char __force *)buf
- + sizeof(struct snd_seq_event);
+ ev->data.ext.len = extlen | SNDRV_SEQ_EXT_USRPTR;
+ ev->data.ext.ptr = (char __force *)buf + len;
len += extlen; /* increment data length */
} else {
#ifdef CONFIG_COMPAT
- if (client->convert32 && snd_seq_ev_is_varusr(&event)) {
- void *ptr = (void __force *)compat_ptr(event.data.raw32.d[1]);
- event.data.ext.ptr = ptr;
- }
+ if (client->convert32 && snd_seq_ev_is_varusr(ev))
+ ev->data.ext.ptr =
+ (void __force *)compat_ptr(ev->data.raw32.d[1]);
#endif
}
/* ok, enqueue it */
- err = snd_seq_client_enqueue_event(client, &event, file,
+ err = snd_seq_client_enqueue_event(client, ev, file,
!(file->f_flags & O_NONBLOCK),
0, 0, &client->ioctl_mutex);
if (err < 0)
@@ -1159,6 +1151,12 @@ static int snd_seq_ioctl_pversion(struct snd_seq_client *client, void *arg)
return 0;
}
+static int snd_seq_ioctl_user_pversion(struct snd_seq_client *client, void *arg)
+{
+ client->user_pversion = *(unsigned int *)arg;
+ return 0;
+}
+
static int snd_seq_ioctl_client_id(struct snd_seq_client *client, void *arg)
{
int *client_id = arg;
@@ -1231,6 +1229,7 @@ static void get_client_info(struct snd_seq_client *cptr,
info->filter = cptr->filter;
info->event_lost = cptr->event_lost;
memcpy(info->event_filter, cptr->event_filter, 32);
+ info->group_filter = cptr->group_filter;
info->num_ports = cptr->num_ports;
if (cptr->type == USER_CLIENT)
@@ -1243,6 +1242,7 @@ static void get_client_info(struct snd_seq_client *cptr,
else
info->card = -1;
+ info->midi_version = cptr->midi_version;
memset(info->reserved, 0, sizeof(info->reserved));
}
@@ -1277,14 +1277,21 @@ static int snd_seq_ioctl_set_client_info(struct snd_seq_client *client,
if (client->type != client_info->type)
return -EINVAL;
+ /* check validity of midi_version field */
+ if (client->user_pversion >= SNDRV_PROTOCOL_VERSION(1, 0, 3) &&
+ client_info->midi_version > SNDRV_SEQ_CLIENT_UMP_MIDI_2_0)
+ return -EINVAL;
+
/* fill the info fields */
if (client_info->name[0])
strscpy(client->name, client_info->name, sizeof(client->name));
client->filter = client_info->filter;
client->event_lost = client_info->event_lost;
+ if (client->user_pversion >= SNDRV_PROTOCOL_VERSION(1, 0, 3))
+ client->midi_version = client_info->midi_version;
memcpy(client->event_filter, client_info->event_filter, 32);
-
+ client->group_filter = client_info->group_filter;
return 0;
}
@@ -1297,22 +1304,27 @@ static int snd_seq_ioctl_create_port(struct snd_seq_client *client, void *arg)
struct snd_seq_port_info *info = arg;
struct snd_seq_client_port *port;
struct snd_seq_port_callback *callback;
- int port_idx;
+ int port_idx, err;
/* it is not allowed to create the port for an another client */
if (info->addr.client != client->number)
return -EPERM;
+ if (client->type == USER_CLIENT && info->kernel)
+ return -EINVAL;
+ if ((info->capability & SNDRV_SEQ_PORT_CAP_UMP_ENDPOINT) &&
+ client->ump_endpoint_port >= 0)
+ return -EBUSY;
- port = snd_seq_create_port(client, (info->flags & SNDRV_SEQ_PORT_FLG_GIVEN_PORT) ? info->addr.port : -1);
- if (port == NULL)
- return -ENOMEM;
-
- if (client->type == USER_CLIENT && info->kernel) {
- port_idx = port->addr.port;
- snd_seq_port_unlock(port);
- snd_seq_delete_port(client, port_idx);
+ if (info->flags & SNDRV_SEQ_PORT_FLG_GIVEN_PORT)
+ port_idx = info->addr.port;
+ else
+ port_idx = -1;
+ if (port_idx >= SNDRV_SEQ_ADDRESS_UNKNOWN)
return -EINVAL;
- }
+ err = snd_seq_create_port(client, port_idx, &port);
+ if (err < 0)
+ return err;
+
if (client->type == KERNEL_CLIENT) {
callback = info->kernel;
if (callback) {
@@ -1331,6 +1343,8 @@ static int snd_seq_ioctl_create_port(struct snd_seq_client *client, void *arg)
info->addr = port->addr;
snd_seq_set_port_info(port, info);
+ if (info->capability & SNDRV_SEQ_PORT_CAP_UMP_ENDPOINT)
+ client->ump_endpoint_port = port->addr.port;
snd_seq_system_client_ev_port_start(port->addr.client, port->addr.port);
snd_seq_port_unlock(port);
@@ -1350,8 +1364,11 @@ static int snd_seq_ioctl_delete_port(struct snd_seq_client *client, void *arg)
return -EPERM;
err = snd_seq_delete_port(client, info->addr.port);
- if (err >= 0)
+ if (err >= 0) {
+ if (client->ump_endpoint_port == info->addr.port)
+ client->ump_endpoint_port = -1;
snd_seq_system_client_ev_port_exit(client->number, info->addr.port);
+ }
return err;
}
@@ -2079,6 +2096,135 @@ static int snd_seq_ioctl_query_next_port(struct snd_seq_client *client,
return 0;
}
+#if IS_ENABLED(CONFIG_SND_SEQ_UMP)
+#define NUM_UMP_INFOS (SNDRV_UMP_MAX_BLOCKS + 1)
+
+static void free_ump_info(struct snd_seq_client *client)
+{
+ int i;
+
+ if (!client->ump_info)
+ return;
+ for (i = 0; i < NUM_UMP_INFOS; i++)
+ kfree(client->ump_info[i]);
+ kfree(client->ump_info);
+ client->ump_info = NULL;
+}
+
+static void terminate_ump_info_strings(void *p, int type)
+{
+ if (type == SNDRV_SEQ_CLIENT_UMP_INFO_ENDPOINT) {
+ struct snd_ump_endpoint_info *ep = p;
+ ep->name[sizeof(ep->name) - 1] = 0;
+ } else {
+ struct snd_ump_block_info *bp = p;
+ bp->name[sizeof(bp->name) - 1] = 0;
+ }
+}
+
+#ifdef CONFIG_SND_PROC_FS
+static void dump_ump_info(struct snd_info_buffer *buffer,
+ struct snd_seq_client *client)
+{
+ struct snd_ump_endpoint_info *ep;
+ struct snd_ump_block_info *bp;
+ int i;
+
+ if (!client->ump_info)
+ return;
+ ep = client->ump_info[SNDRV_SEQ_CLIENT_UMP_INFO_ENDPOINT];
+ if (ep && *ep->name)
+ snd_iprintf(buffer, " UMP Endpoint: \"%s\"\n", ep->name);
+ for (i = 0; i < SNDRV_UMP_MAX_BLOCKS; i++) {
+ bp = client->ump_info[i + 1];
+ if (bp && *bp->name) {
+ snd_iprintf(buffer, " UMP Block %d: \"%s\" [%s]\n",
+ i, bp->name,
+ bp->active ? "Active" : "Inactive");
+ snd_iprintf(buffer, " Groups: %d-%d\n",
+ bp->first_group + 1,
+ bp->first_group + bp->num_groups);
+ }
+ }
+}
+#endif
+
+/* UMP-specific ioctls -- called directly without data copy */
+static int snd_seq_ioctl_client_ump_info(struct snd_seq_client *caller,
+ unsigned int cmd,
+ unsigned long arg)
+{
+ struct snd_seq_client_ump_info __user *argp =
+ (struct snd_seq_client_ump_info __user *)arg;
+ struct snd_seq_client *cptr;
+ int client, type, err = 0;
+ size_t size;
+ void *p;
+
+ if (get_user(client, &argp->client) || get_user(type, &argp->type))
+ return -EFAULT;
+ if (cmd == SNDRV_SEQ_IOCTL_SET_CLIENT_UMP_INFO &&
+ caller->number != client)
+ return -EPERM;
+ if (type < 0 || type >= NUM_UMP_INFOS)
+ return -EINVAL;
+ if (type == SNDRV_SEQ_CLIENT_UMP_INFO_ENDPOINT)
+ size = sizeof(struct snd_ump_endpoint_info);
+ else
+ size = sizeof(struct snd_ump_block_info);
+ cptr = snd_seq_client_use_ptr(client);
+ if (!cptr)
+ return -ENOENT;
+
+ mutex_lock(&cptr->ioctl_mutex);
+ if (!cptr->midi_version) {
+ err = -EBADFD;
+ goto error;
+ }
+
+ if (cmd == SNDRV_SEQ_IOCTL_GET_CLIENT_UMP_INFO) {
+ if (!cptr->ump_info)
+ p = NULL;
+ else
+ p = cptr->ump_info[type];
+ if (!p) {
+ err = -ENODEV;
+ goto error;
+ }
+ if (copy_to_user(argp->info, p, size)) {
+ err = -EFAULT;
+ goto error;
+ }
+ } else {
+ if (cptr->type != USER_CLIENT) {
+ err = -EBADFD;
+ goto error;
+ }
+ if (!cptr->ump_info) {
+ cptr->ump_info = kcalloc(NUM_UMP_INFOS,
+ sizeof(void *), GFP_KERNEL);
+ if (!cptr->ump_info) {
+ err = -ENOMEM;
+ goto error;
+ }
+ }
+ p = memdup_user(argp->info, size);
+ if (IS_ERR(p)) {
+ err = PTR_ERR(p);
+ goto error;
+ }
+ kfree(cptr->ump_info[type]);
+ terminate_ump_info_strings(p, type);
+ cptr->ump_info[type] = p;
+ }
+
+ error:
+ mutex_unlock(&cptr->ioctl_mutex);
+ snd_seq_client_unlock(cptr);
+ return err;
+}
+#endif
+
/* -------------------------------------------------------- */
static const struct ioctl_handler {
@@ -2086,6 +2232,7 @@ static const struct ioctl_handler {
int (*func)(struct snd_seq_client *client, void *arg);
} ioctl_handlers[] = {
{ SNDRV_SEQ_IOCTL_PVERSION, snd_seq_ioctl_pversion },
+ { SNDRV_SEQ_IOCTL_USER_PVERSION, snd_seq_ioctl_user_pversion },
{ SNDRV_SEQ_IOCTL_CLIENT_ID, snd_seq_ioctl_client_id },
{ SNDRV_SEQ_IOCTL_SYSTEM_INFO, snd_seq_ioctl_system_info },
{ SNDRV_SEQ_IOCTL_RUNNING_MODE, snd_seq_ioctl_running_mode },
@@ -2148,6 +2295,15 @@ static long snd_seq_ioctl(struct file *file, unsigned int cmd,
if (snd_BUG_ON(!client))
return -ENXIO;
+#if IS_ENABLED(CONFIG_SND_SEQ_UMP)
+ /* exception - handling large data */
+ switch (cmd) {
+ case SNDRV_SEQ_IOCTL_GET_CLIENT_UMP_INFO:
+ case SNDRV_SEQ_IOCTL_SET_CLIENT_UMP_INFO:
+ return snd_seq_ioctl_client_ump_info(client, cmd, arg);
+ }
+#endif
+
for (handler = ioctl_handlers; handler->cmd > 0; ++handler) {
if (handler->cmd == cmd)
break;
@@ -2226,6 +2382,7 @@ int snd_seq_create_kernel_client(struct snd_card *card, int client_index,
client->accept_input = 1;
client->accept_output = 1;
client->data.kernel.card = card;
+ client->user_pversion = SNDRV_SEQ_VERSION;
va_start(args, name_fmt);
vsnprintf(client->name, sizeof(client->name), name_fmt, args);
@@ -2274,10 +2431,12 @@ int snd_seq_kernel_client_enqueue(int client, struct snd_seq_event *ev,
if (snd_BUG_ON(!ev))
return -EINVAL;
- if (ev->type == SNDRV_SEQ_EVENT_NONE)
- return 0; /* ignore this */
- if (ev->type == SNDRV_SEQ_EVENT_KERNEL_ERROR)
- return -EINVAL; /* quoted events can't be enqueued */
+ if (!snd_seq_ev_is_ump(ev)) {
+ if (ev->type == SNDRV_SEQ_EVENT_NONE)
+ return 0; /* ignore this */
+ if (ev->type == SNDRV_SEQ_EVENT_KERNEL_ERROR)
+ return -EINVAL; /* quoted events can't be enqueued */
+ }
/* fill in client number */
ev->source.client = client;
@@ -2390,6 +2549,21 @@ int snd_seq_kernel_client_write_poll(int clientid, struct file *file, poll_table
}
EXPORT_SYMBOL(snd_seq_kernel_client_write_poll);
+/* get a sequencer client object; for internal use from a kernel client */
+struct snd_seq_client *snd_seq_kernel_client_get(int id)
+{
+ return snd_seq_client_use_ptr(id);
+}
+EXPORT_SYMBOL_GPL(snd_seq_kernel_client_get);
+
+/* put a sequencer client object; for internal use from a kernel client */
+void snd_seq_kernel_client_put(struct snd_seq_client *cptr)
+{
+ if (cptr)
+ snd_seq_client_unlock(cptr);
+}
+EXPORT_SYMBOL_GPL(snd_seq_kernel_client_put);
+
/*---------------------------------------------------------------------------*/
#ifdef CONFIG_SND_PROC_FS
@@ -2435,6 +2609,17 @@ static void snd_seq_info_dump_subscribers(struct snd_info_buffer *buffer,
#define FLAG_PERM_DUPLEX(perm) ((perm) & SNDRV_SEQ_PORT_CAP_DUPLEX ? 'X' : '-')
+static const char *port_direction_name(unsigned char dir)
+{
+ static const char *names[4] = {
+ "-", "In", "Out", "In/Out"
+ };
+
+ if (dir > SNDRV_SEQ_PORT_DIR_BIDIRECTION)
+ return "Invalid";
+ return names[dir];
+}
+
static void snd_seq_info_dump_ports(struct snd_info_buffer *buffer,
struct snd_seq_client *client)
{
@@ -2442,18 +2627,34 @@ static void snd_seq_info_dump_ports(struct snd_info_buffer *buffer,
mutex_lock(&client->ports_mutex);
list_for_each_entry(p, &client->ports_list_head, list) {
- snd_iprintf(buffer, " Port %3d : \"%s\" (%c%c%c%c)\n",
+ if (p->capability & SNDRV_SEQ_PORT_CAP_INACTIVE)
+ continue;
+ snd_iprintf(buffer, " Port %3d : \"%s\" (%c%c%c%c) [%s]\n",
p->addr.port, p->name,
FLAG_PERM_RD(p->capability),
FLAG_PERM_WR(p->capability),
FLAG_PERM_EX(p->capability),
- FLAG_PERM_DUPLEX(p->capability));
+ FLAG_PERM_DUPLEX(p->capability),
+ port_direction_name(p->direction));
snd_seq_info_dump_subscribers(buffer, &p->c_src, 1, " Connecting To: ");
snd_seq_info_dump_subscribers(buffer, &p->c_dest, 0, " Connected From: ");
}
mutex_unlock(&client->ports_mutex);
}
+static const char *midi_version_string(unsigned int version)
+{
+ switch (version) {
+ case SNDRV_SEQ_CLIENT_LEGACY_MIDI:
+ return "Legacy";
+ case SNDRV_SEQ_CLIENT_UMP_MIDI_1_0:
+ return "UMP MIDI1";
+ case SNDRV_SEQ_CLIENT_UMP_MIDI_2_0:
+ return "UMP MIDI2";
+ default:
+ return "Unknown";
+ }
+}
/* exported to seq_info.c */
void snd_seq_info_clients_read(struct snd_info_entry *entry,
@@ -2478,9 +2679,13 @@ void snd_seq_info_clients_read(struct snd_info_entry *entry,
continue;
}
- snd_iprintf(buffer, "Client %3d : \"%s\" [%s]\n",
+ snd_iprintf(buffer, "Client %3d : \"%s\" [%s %s]\n",
c, client->name,
- client->type == USER_CLIENT ? "User" : "Kernel");
+ client->type == USER_CLIENT ? "User" : "Kernel",
+ midi_version_string(client->midi_version));
+#if IS_ENABLED(CONFIG_SND_SEQ_UMP)
+ dump_ump_info(buffer, client);
+#endif
snd_seq_info_dump_ports(buffer, client);
if (snd_seq_write_pool_allocated(client)) {
snd_iprintf(buffer, " Output pool :\n");
diff --git a/sound/core/seq/seq_clientmgr.h b/sound/core/seq/seq_clientmgr.h
index 8cdd0ee53fb1..915b1017286e 100644
--- a/sound/core/seq/seq_clientmgr.h
+++ b/sound/core/seq/seq_clientmgr.h
@@ -12,7 +12,6 @@
#include "seq_ports.h"
#include "seq_lock.h"
-
/* client manager */
struct snd_seq_user_client {
@@ -35,10 +34,13 @@ struct snd_seq_client {
snd_seq_client_type_t type;
unsigned int accept_input: 1,
accept_output: 1;
+ unsigned int midi_version;
+ unsigned int user_pversion;
char name[64]; /* client name */
int number; /* client number */
unsigned int filter; /* filter flags */
DECLARE_BITMAP(event_filter, 256);
+ unsigned short group_filter;
snd_use_lock_t use_lock;
int event_lost;
/* ports */
@@ -48,6 +50,7 @@ struct snd_seq_client {
struct mutex ports_mutex;
struct mutex ioctl_mutex;
int convert32; /* convert 32->64bit */
+ int ump_endpoint_port;
/* output pool */
struct snd_seq_pool *pool; /* memory pool for this client */
@@ -56,6 +59,9 @@ struct snd_seq_client {
struct snd_seq_user_client user;
struct snd_seq_kernel_client kernel;
} data;
+
+ /* for UMP */
+ void **ump_info;
};
/* usage statistics */
@@ -82,10 +88,29 @@ int snd_seq_kernel_client_write_poll(int clientid, struct file *file, poll_table
int snd_seq_client_notify_subscription(int client, int port,
struct snd_seq_port_subscribe *info, int evtype);
+int __snd_seq_deliver_single_event(struct snd_seq_client *dest,
+ struct snd_seq_client_port *dest_port,
+ struct snd_seq_event *event,
+ int atomic, int hop);
+
/* only for OSS sequencer */
bool snd_seq_client_ioctl_lock(int clientid);
void snd_seq_client_ioctl_unlock(int clientid);
extern int seq_client_load[15];
+/* for internal use between kernel sequencer clients */
+struct snd_seq_client *snd_seq_kernel_client_get(int client);
+void snd_seq_kernel_client_put(struct snd_seq_client *cptr);
+
+static inline bool snd_seq_client_is_ump(struct snd_seq_client *c)
+{
+ return c->midi_version != SNDRV_SEQ_CLIENT_LEGACY_MIDI;
+}
+
+static inline bool snd_seq_client_is_midi2(struct snd_seq_client *c)
+{
+ return c->midi_version == SNDRV_SEQ_CLIENT_UMP_MIDI_2_0;
+}
+
#endif
diff --git a/sound/core/seq/seq_compat.c b/sound/core/seq/seq_compat.c
index 54723566ce24..1e35bf086a51 100644
--- a/sound/core/seq/seq_compat.c
+++ b/sound/core/seq/seq_compat.c
@@ -81,10 +81,13 @@ static long snd_seq_ioctl_compat(struct file *file, unsigned int cmd, unsigned l
switch (cmd) {
case SNDRV_SEQ_IOCTL_PVERSION:
+ case SNDRV_SEQ_IOCTL_USER_PVERSION:
case SNDRV_SEQ_IOCTL_CLIENT_ID:
case SNDRV_SEQ_IOCTL_SYSTEM_INFO:
case SNDRV_SEQ_IOCTL_GET_CLIENT_INFO:
case SNDRV_SEQ_IOCTL_SET_CLIENT_INFO:
+ case SNDRV_SEQ_IOCTL_GET_CLIENT_UMP_INFO:
+ case SNDRV_SEQ_IOCTL_SET_CLIENT_UMP_INFO:
case SNDRV_SEQ_IOCTL_SUBSCRIBE_PORT:
case SNDRV_SEQ_IOCTL_UNSUBSCRIBE_PORT:
case SNDRV_SEQ_IOCTL_CREATE_QUEUE:
diff --git a/sound/core/seq/seq_dummy.c b/sound/core/seq/seq_dummy.c
index 8c18d8c4177e..9308194b2d9a 100644
--- a/sound/core/seq/seq_dummy.c
+++ b/sound/core/seq/seq_dummy.c
@@ -127,6 +127,7 @@ create_port(int idx, int type)
pinfo.capability |= SNDRV_SEQ_PORT_CAP_WRITE | SNDRV_SEQ_PORT_CAP_SUBS_WRITE;
if (duplex)
pinfo.capability |= SNDRV_SEQ_PORT_CAP_DUPLEX;
+ pinfo.direction = SNDRV_SEQ_PORT_DIR_BIDIRECTION;
pinfo.type = SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC
| SNDRV_SEQ_PORT_TYPE_SOFTWARE
| SNDRV_SEQ_PORT_TYPE_PORT;
@@ -151,6 +152,7 @@ static int __init
register_client(void)
{
struct snd_seq_dummy_port *rec1, *rec2;
+ struct snd_seq_client *client;
int i;
if (ports < 1) {
@@ -164,6 +166,13 @@ register_client(void)
if (my_client < 0)
return my_client;
+ /* don't convert events but just pass-through */
+ client = snd_seq_kernel_client_get(my_client);
+ if (!client)
+ return -EINVAL;
+ client->filter = SNDRV_SEQ_FILTER_NO_CONVERT;
+ snd_seq_kernel_client_put(client);
+
/* create ports */
for (i = 0; i < ports; i++) {
rec1 = create_port(i, 0);
diff --git a/sound/core/seq/seq_memory.c b/sound/core/seq/seq_memory.c
index 47ef6bc30c0e..174585bf59d2 100644
--- a/sound/core/seq/seq_memory.c
+++ b/sound/core/seq/seq_memory.c
@@ -63,8 +63,9 @@ static int get_var_len(const struct snd_seq_event *event)
return event->data.ext.len & ~SNDRV_SEQ_EXT_MASK;
}
-int snd_seq_dump_var_event(const struct snd_seq_event *event,
- snd_seq_dump_func_t func, void *private_data)
+static int dump_var_event(const struct snd_seq_event *event,
+ snd_seq_dump_func_t func, void *private_data,
+ int offset, int maxlen)
{
int len, err;
struct snd_seq_event_cell *cell;
@@ -72,10 +73,16 @@ int snd_seq_dump_var_event(const struct snd_seq_event *event,
len = get_var_len(event);
if (len <= 0)
return len;
+ if (len <= offset)
+ return 0;
+ if (maxlen && len > offset + maxlen)
+ len = offset + maxlen;
if (event->data.ext.len & SNDRV_SEQ_EXT_USRPTR) {
char buf[32];
char __user *curptr = (char __force __user *)event->data.ext.ptr;
+ curptr += offset;
+ len -= offset;
while (len > 0) {
int size = sizeof(buf);
if (len < size)
@@ -91,20 +98,35 @@ int snd_seq_dump_var_event(const struct snd_seq_event *event,
return 0;
}
if (!(event->data.ext.len & SNDRV_SEQ_EXT_CHAINED))
- return func(private_data, event->data.ext.ptr, len);
+ return func(private_data, event->data.ext.ptr + offset,
+ len - offset);
cell = (struct snd_seq_event_cell *)event->data.ext.ptr;
for (; len > 0 && cell; cell = cell->next) {
int size = sizeof(struct snd_seq_event);
+ char *curptr = (char *)&cell->event;
+
+ if (offset >= size) {
+ offset -= size;
+ len -= size;
+ continue;
+ }
if (len < size)
size = len;
- err = func(private_data, &cell->event, size);
+ err = func(private_data, curptr + offset, size - offset);
if (err < 0)
return err;
+ offset = 0;
len -= size;
}
return 0;
}
+
+int snd_seq_dump_var_event(const struct snd_seq_event *event,
+ snd_seq_dump_func_t func, void *private_data)
+{
+ return dump_var_event(event, func, private_data, 0, 0);
+}
EXPORT_SYMBOL(snd_seq_dump_var_event);
@@ -132,11 +154,27 @@ static int seq_copy_in_user(void *ptr, void *src, int size)
return 0;
}
+static int expand_var_event(const struct snd_seq_event *event,
+ int offset, int size, char *buf, bool in_kernel)
+{
+ if (event->data.ext.len & SNDRV_SEQ_EXT_USRPTR) {
+ if (! in_kernel)
+ return -EINVAL;
+ if (copy_from_user(buf,
+ (char __force __user *)event->data.ext.ptr + offset,
+ size))
+ return -EFAULT;
+ return 0;
+ }
+ return dump_var_event(event,
+ in_kernel ? seq_copy_in_kernel : seq_copy_in_user,
+ &buf, offset, size);
+}
+
int snd_seq_expand_var_event(const struct snd_seq_event *event, int count, char *buf,
int in_kernel, int size_aligned)
{
- int len, newlen;
- int err;
+ int len, newlen, err;
len = get_var_len(event);
if (len < 0)
@@ -146,21 +184,35 @@ int snd_seq_expand_var_event(const struct snd_seq_event *event, int count, char
newlen = roundup(len, size_aligned);
if (count < newlen)
return -EAGAIN;
-
- if (event->data.ext.len & SNDRV_SEQ_EXT_USRPTR) {
- if (! in_kernel)
- return -EINVAL;
- if (copy_from_user(buf, (void __force __user *)event->data.ext.ptr, len))
- return -EFAULT;
- return newlen;
- }
- err = snd_seq_dump_var_event(event,
- in_kernel ? seq_copy_in_kernel : seq_copy_in_user,
- &buf);
- return err < 0 ? err : newlen;
+ err = expand_var_event(event, 0, len, buf, in_kernel);
+ if (err < 0)
+ return err;
+ if (len != newlen)
+ memset(buf + len, 0, newlen - len);
+ return newlen;
}
EXPORT_SYMBOL(snd_seq_expand_var_event);
+int snd_seq_expand_var_event_at(const struct snd_seq_event *event, int count,
+ char *buf, int offset)
+{
+ int len, err;
+
+ len = get_var_len(event);
+ if (len < 0)
+ return len;
+ if (len <= offset)
+ return 0;
+ len -= offset;
+ if (len > count)
+ len = count;
+ err = expand_var_event(event, offset, count, buf, true);
+ if (err < 0)
+ return err;
+ return len;
+}
+EXPORT_SYMBOL_GPL(snd_seq_expand_var_event_at);
+
/*
* release this cell, free extended data if available
*/
@@ -288,6 +340,7 @@ int snd_seq_event_dup(struct snd_seq_pool *pool, struct snd_seq_event *event,
int ncells, err;
unsigned int extlen;
struct snd_seq_event_cell *cell;
+ int size;
*cellp = NULL;
@@ -305,7 +358,12 @@ int snd_seq_event_dup(struct snd_seq_pool *pool, struct snd_seq_event *event,
return err;
/* copy the event */
- cell->event = *event;
+ size = snd_seq_event_packet_size(event);
+ memcpy(&cell->ump, event, size);
+#if IS_ENABLED(CONFIG_SND_SEQ_UMP)
+ if (size < sizeof(cell->event))
+ cell->ump.raw.extra = 0;
+#endif
/* decompose */
if (snd_seq_ev_is_variable(event)) {
@@ -323,7 +381,7 @@ int snd_seq_event_dup(struct snd_seq_pool *pool, struct snd_seq_event *event,
tail = NULL;
while (ncells-- > 0) {
- int size = sizeof(struct snd_seq_event);
+ size = sizeof(struct snd_seq_event);
if (len < size)
size = len;
err = snd_seq_cell_alloc(pool, &tmp, nonblock, file,
diff --git a/sound/core/seq/seq_memory.h b/sound/core/seq/seq_memory.h
index 7d7ff80f915e..7f7a2c0b187d 100644
--- a/sound/core/seq/seq_memory.h
+++ b/sound/core/seq/seq_memory.h
@@ -11,9 +11,26 @@
struct snd_info_buffer;
+/* aliasing for legacy and UMP event packet handling */
+union __snd_seq_event {
+ struct snd_seq_event legacy;
+#if IS_ENABLED(CONFIG_SND_SEQ_UMP)
+ struct snd_seq_ump_event ump;
+#endif
+ struct {
+ struct snd_seq_event event;
+#if IS_ENABLED(CONFIG_SND_SEQ_UMP)
+ u32 extra;
+#endif
+ } __packed raw;
+};
+
/* container for sequencer event (internal use) */
struct snd_seq_event_cell {
- struct snd_seq_event event;
+ union {
+ struct snd_seq_event event;
+ union __snd_seq_event ump;
+ };
struct snd_seq_pool *pool; /* used pool */
struct snd_seq_event_cell *next; /* next cell */
};
diff --git a/sound/core/seq/seq_midi.c b/sound/core/seq/seq_midi.c
index 4589aac09154..44302d98950e 100644
--- a/sound/core/seq/seq_midi.c
+++ b/sound/core/seq/seq_midi.c
@@ -38,6 +38,7 @@ MODULE_PARM_DESC(input_buffer_size, "Input buffer size in bytes.");
/* data for this midi synth driver */
struct seq_midisynth {
struct snd_card *card;
+ struct snd_rawmidi *rmidi;
int device;
int subdevice;
struct snd_rawmidi_file input_rfile;
@@ -168,8 +169,7 @@ static int midisynth_subscribe(void *private_data, struct snd_seq_port_subscribe
struct snd_rawmidi_params params;
/* open midi port */
- err = snd_rawmidi_kernel_open(msynth->card, msynth->device,
- msynth->subdevice,
+ err = snd_rawmidi_kernel_open(msynth->rmidi, msynth->subdevice,
SNDRV_RAWMIDI_LFLG_INPUT,
&msynth->input_rfile);
if (err < 0) {
@@ -212,8 +212,7 @@ static int midisynth_use(void *private_data, struct snd_seq_port_subscribe *info
struct snd_rawmidi_params params;
/* open midi port */
- err = snd_rawmidi_kernel_open(msynth->card, msynth->device,
- msynth->subdevice,
+ err = snd_rawmidi_kernel_open(msynth->rmidi, msynth->subdevice,
SNDRV_RAWMIDI_LFLG_OUTPUT,
&msynth->output_rfile);
if (err < 0) {
@@ -328,6 +327,7 @@ snd_seq_midisynth_probe(struct device *_dev)
for (p = 0; p < ports; p++) {
ms = &msynth[p];
+ ms->rmidi = rmidi;
if (snd_seq_midisynth_new(ms, card, device, p) < 0)
goto __nomem;
@@ -367,6 +367,10 @@ snd_seq_midisynth_probe(struct device *_dev)
if ((port->capability & (SNDRV_SEQ_PORT_CAP_WRITE|SNDRV_SEQ_PORT_CAP_READ)) == (SNDRV_SEQ_PORT_CAP_WRITE|SNDRV_SEQ_PORT_CAP_READ) &&
info->flags & SNDRV_RAWMIDI_INFO_DUPLEX)
port->capability |= SNDRV_SEQ_PORT_CAP_DUPLEX;
+ if (port->capability & SNDRV_SEQ_PORT_CAP_READ)
+ port->direction |= SNDRV_SEQ_PORT_DIR_INPUT;
+ if (port->capability & SNDRV_SEQ_PORT_CAP_WRITE)
+ port->direction |= SNDRV_SEQ_PORT_DIR_OUTPUT;
port->type = SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC
| SNDRV_SEQ_PORT_TYPE_HARDWARE
| SNDRV_SEQ_PORT_TYPE_PORT;
diff --git a/sound/core/seq/seq_ports.c b/sound/core/seq/seq_ports.c
index 25fcf5a2c71c..9b80f8275026 100644
--- a/sound/core/seq/seq_ports.c
+++ b/sound/core/seq/seq_ports.c
@@ -69,11 +69,15 @@ struct snd_seq_client_port *snd_seq_port_query_nearest(struct snd_seq_client *cl
{
int num;
struct snd_seq_client_port *port, *found;
+ bool check_inactive = (pinfo->capability & SNDRV_SEQ_PORT_CAP_INACTIVE);
num = pinfo->addr.port;
found = NULL;
read_lock(&client->ports_lock);
list_for_each_entry(port, &client->ports_list_head, list) {
+ if ((port->capability & SNDRV_SEQ_PORT_CAP_INACTIVE) &&
+ !check_inactive)
+ continue; /* skip inactive ports */
if (port->addr.port < num)
continue;
if (port->addr.port == num) {
@@ -107,33 +111,34 @@ static void port_subs_info_init(struct snd_seq_port_subs_info *grp)
}
-/* create a port, port number is returned (-1 on failure);
+/* create a port, port number or a negative error code is returned
* the caller needs to unref the port via snd_seq_port_unlock() appropriately
*/
-struct snd_seq_client_port *snd_seq_create_port(struct snd_seq_client *client,
- int port)
+int snd_seq_create_port(struct snd_seq_client *client, int port,
+ struct snd_seq_client_port **port_ret)
{
struct snd_seq_client_port *new_port, *p;
- int num = -1;
+ int num;
+ *port_ret = NULL;
+
/* sanity check */
if (snd_BUG_ON(!client))
- return NULL;
+ return -EINVAL;
if (client->num_ports >= SNDRV_SEQ_MAX_PORTS) {
pr_warn("ALSA: seq: too many ports for client %d\n", client->number);
- return NULL;
+ return -EINVAL;
}
/* create a new port */
new_port = kzalloc(sizeof(*new_port), GFP_KERNEL);
if (!new_port)
- return NULL; /* failure, out of memory */
+ return -ENOMEM; /* failure, out of memory */
/* init port data */
new_port->addr.client = client->number;
new_port->addr.port = -1;
new_port->owner = THIS_MODULE;
- sprintf(new_port->name, "port-%d", num);
snd_use_lock_init(&new_port->use_lock);
port_subs_info_init(&new_port->c_src);
port_subs_info_init(&new_port->c_dest);
@@ -143,6 +148,10 @@ struct snd_seq_client_port *snd_seq_create_port(struct snd_seq_client *client,
mutex_lock(&client->ports_mutex);
write_lock_irq(&client->ports_lock);
list_for_each_entry(p, &client->ports_list_head, list) {
+ if (p->addr.port == port) {
+ num = -EBUSY;
+ goto unlock;
+ }
if (p->addr.port > num)
break;
if (port < 0) /* auto-probe mode */
@@ -153,10 +162,12 @@ struct snd_seq_client_port *snd_seq_create_port(struct snd_seq_client *client,
client->num_ports++;
new_port->addr.port = num; /* store the port number in the port */
sprintf(new_port->name, "port-%d", num);
+ *port_ret = new_port;
+ unlock:
write_unlock_irq(&client->ports_lock);
mutex_unlock(&client->ports_mutex);
- return new_port;
+ return num;
}
/* */
@@ -345,6 +356,20 @@ int snd_seq_set_port_info(struct snd_seq_client_port * port,
port->time_real = (info->flags & SNDRV_SEQ_PORT_FLG_TIME_REAL) ? 1 : 0;
port->time_queue = info->time_queue;
+ /* UMP direction and group */
+ port->direction = info->direction;
+ port->ump_group = info->ump_group;
+ if (port->ump_group > SNDRV_UMP_MAX_GROUPS)
+ port->ump_group = 0;
+
+ /* fill default port direction */
+ if (!port->direction) {
+ if (info->capability & SNDRV_SEQ_PORT_CAP_READ)
+ port->direction |= SNDRV_SEQ_PORT_DIR_INPUT;
+ if (info->capability & SNDRV_SEQ_PORT_CAP_WRITE)
+ port->direction |= SNDRV_SEQ_PORT_DIR_OUTPUT;
+ }
+
return 0;
}
@@ -382,6 +407,10 @@ int snd_seq_get_port_info(struct snd_seq_client_port * port,
info->time_queue = port->time_queue;
}
+ /* UMP direction and group */
+ info->direction = port->direction;
+ info->ump_group = port->ump_group;
+
return 0;
}
diff --git a/sound/core/seq/seq_ports.h b/sound/core/seq/seq_ports.h
index b1f2c4943174..b111382f697a 100644
--- a/sound/core/seq/seq_ports.h
+++ b/sound/core/seq/seq_ports.h
@@ -42,6 +42,17 @@ struct snd_seq_port_subs_info {
int (*close)(void *private_data, struct snd_seq_port_subscribe *info);
};
+/* context for converting from legacy control event to UMP packet */
+struct snd_seq_ump_midi2_bank {
+ bool rpn_set;
+ bool nrpn_set;
+ bool bank_set;
+ unsigned char cc_rpn_msb, cc_rpn_lsb;
+ unsigned char cc_nrpn_msb, cc_nrpn_lsb;
+ unsigned char cc_data_msb, cc_data_lsb;
+ unsigned char cc_bank_msb, cc_bank_lsb;
+};
+
struct snd_seq_client_port {
struct snd_seq_addr addr; /* client/port number */
@@ -72,6 +83,13 @@ struct snd_seq_client_port {
int midi_voices;
int synth_voices;
+ /* UMP direction and group */
+ unsigned char direction;
+ unsigned char ump_group;
+
+#if IS_ENABLED(CONFIG_SND_SEQ_UMP)
+ struct snd_seq_ump_midi2_bank midi2_bank[16]; /* per channel */
+#endif
};
struct snd_seq_client;
@@ -86,8 +104,9 @@ struct snd_seq_client_port *snd_seq_port_query_nearest(struct snd_seq_client *cl
/* unlock the port */
#define snd_seq_port_unlock(port) snd_use_lock_free(&(port)->use_lock)
-/* create a port, port number is returned (-1 on failure) */
-struct snd_seq_client_port *snd_seq_create_port(struct snd_seq_client *client, int port_index);
+/* create a port, port number or a negative error code is returned */
+int snd_seq_create_port(struct snd_seq_client *client, int port_index,
+ struct snd_seq_client_port **port_ret);
/* delete a port */
int snd_seq_delete_port(struct snd_seq_client *client, int port);
diff --git a/sound/core/seq/seq_ump_client.c b/sound/core/seq/seq_ump_client.c
new file mode 100644
index 000000000000..e24833804094
--- /dev/null
+++ b/sound/core/seq/seq_ump_client.c
@@ -0,0 +1,464 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/* ALSA sequencer binding for UMP device */
+
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/errno.h>
+#include <linux/mutex.h>
+#include <linux/string.h>
+#include <linux/module.h>
+#include <asm/byteorder.h>
+#include <sound/core.h>
+#include <sound/ump.h>
+#include <sound/seq_kernel.h>
+#include <sound/seq_device.h>
+#include "seq_clientmgr.h"
+
+struct seq_ump_client;
+struct seq_ump_group;
+
+enum {
+ STR_IN = SNDRV_RAWMIDI_STREAM_INPUT,
+ STR_OUT = SNDRV_RAWMIDI_STREAM_OUTPUT
+};
+
+/* object per UMP group; corresponding to a sequencer port */
+struct seq_ump_group {
+ int group; /* group index (0-based) */
+ unsigned int dir_bits; /* directions */
+ bool active; /* activeness */
+ char name[64]; /* seq port name */
+};
+
+/* context for UMP input parsing, per EP */
+struct seq_ump_input_buffer {
+ unsigned char len; /* total length in words */
+ unsigned char pending; /* pending words */
+ unsigned char type; /* parsed UMP packet type */
+ unsigned char group; /* parsed UMP packet group */
+ u32 buf[4]; /* incoming UMP packet */
+};
+
+/* sequencer client, per UMP EP (rawmidi) */
+struct seq_ump_client {
+ struct snd_ump_endpoint *ump; /* assigned endpoint */
+ int seq_client; /* sequencer client id */
+ int opened[2]; /* current opens for each direction */
+ struct snd_rawmidi_file out_rfile; /* rawmidi for output */
+ struct seq_ump_input_buffer input; /* input parser context */
+ struct seq_ump_group groups[SNDRV_UMP_MAX_GROUPS]; /* table of groups */
+ void *ump_info[SNDRV_UMP_MAX_BLOCKS + 1]; /* shadow of seq client ump_info */
+};
+
+/* number of 32bit words for each UMP message type */
+static unsigned char ump_packet_words[0x10] = {
+ 1, 1, 1, 2, 2, 4, 1, 1, 2, 2, 2, 3, 3, 4, 4, 4
+};
+
+/* conversion between UMP group and seq port;
+ * assume the port number is equal with UMP group number (1-based)
+ */
+static unsigned char ump_group_to_seq_port(unsigned char group)
+{
+ return group + 1;
+}
+
+/* process the incoming rawmidi stream */
+static void seq_ump_input_receive(struct snd_ump_endpoint *ump,
+ const u32 *val, int words)
+{
+ struct seq_ump_client *client = ump->seq_client;
+ struct snd_seq_ump_event ev = {};
+
+ if (!client->opened[STR_IN])
+ return;
+
+ ev.source.port = ump_group_to_seq_port(ump_message_group(*val));
+ ev.dest.client = SNDRV_SEQ_ADDRESS_SUBSCRIBERS;
+ ev.flags = SNDRV_SEQ_EVENT_UMP;
+ memcpy(ev.ump, val, words << 2);
+ snd_seq_kernel_client_dispatch(client->seq_client,
+ (struct snd_seq_event *)&ev,
+ true, 0);
+}
+
+/* process an input sequencer event; only deal with UMP types */
+static int seq_ump_process_event(struct snd_seq_event *ev, int direct,
+ void *private_data, int atomic, int hop)
+{
+ struct seq_ump_client *client = private_data;
+ struct snd_rawmidi_substream *substream;
+ struct snd_seq_ump_event *ump_ev;
+ unsigned char type;
+ int len;
+
+ substream = client->out_rfile.output;
+ if (!substream)
+ return -ENODEV;
+ if (!snd_seq_ev_is_ump(ev))
+ return 0; /* invalid event, skip */
+ ump_ev = (struct snd_seq_ump_event *)ev;
+ type = ump_message_type(ump_ev->ump[0]);
+ len = ump_packet_words[type];
+ if (len > 4)
+ return 0; // invalid - skip
+ snd_rawmidi_kernel_write(substream, ev->data.raw8.d, len << 2);
+ return 0;
+}
+
+/* open the rawmidi */
+static int seq_ump_client_open(struct seq_ump_client *client, int dir)
+{
+ struct snd_ump_endpoint *ump = client->ump;
+ int err = 0;
+
+ mutex_lock(&ump->open_mutex);
+ if (dir == STR_OUT && !client->opened[dir]) {
+ err = snd_rawmidi_kernel_open(&ump->core, 0,
+ SNDRV_RAWMIDI_LFLG_OUTPUT |
+ SNDRV_RAWMIDI_LFLG_APPEND,
+ &client->out_rfile);
+ if (err < 0)
+ goto unlock;
+ }
+ client->opened[dir]++;
+ unlock:
+ mutex_unlock(&ump->open_mutex);
+ return err;
+}
+
+/* close the rawmidi */
+static int seq_ump_client_close(struct seq_ump_client *client, int dir)
+{
+ struct snd_ump_endpoint *ump = client->ump;
+
+ mutex_lock(&ump->open_mutex);
+ if (!--client->opened[dir])
+ if (dir == STR_OUT)
+ snd_rawmidi_kernel_release(&client->out_rfile);
+ mutex_unlock(&ump->open_mutex);
+ return 0;
+}
+
+/* sequencer subscription ops for each client */
+static int seq_ump_subscribe(void *pdata, struct snd_seq_port_subscribe *info)
+{
+ struct seq_ump_client *client = pdata;
+
+ return seq_ump_client_open(client, STR_IN);
+}
+
+static int seq_ump_unsubscribe(void *pdata, struct snd_seq_port_subscribe *info)
+{
+ struct seq_ump_client *client = pdata;
+
+ return seq_ump_client_close(client, STR_IN);
+}
+
+static int seq_ump_use(void *pdata, struct snd_seq_port_subscribe *info)
+{
+ struct seq_ump_client *client = pdata;
+
+ return seq_ump_client_open(client, STR_OUT);
+}
+
+static int seq_ump_unuse(void *pdata, struct snd_seq_port_subscribe *info)
+{
+ struct seq_ump_client *client = pdata;
+
+ return seq_ump_client_close(client, STR_OUT);
+}
+
+/* fill port_info from the given UMP EP and group info */
+static void fill_port_info(struct snd_seq_port_info *port,
+ struct seq_ump_client *client,
+ struct seq_ump_group *group)
+{
+ unsigned int rawmidi_info = client->ump->core.info_flags;
+
+ port->addr.client = client->seq_client;
+ port->addr.port = ump_group_to_seq_port(group->group);
+ port->capability = 0;
+ if (rawmidi_info & SNDRV_RAWMIDI_INFO_OUTPUT)
+ port->capability |= SNDRV_SEQ_PORT_CAP_WRITE |
+ SNDRV_SEQ_PORT_CAP_SYNC_WRITE |
+ SNDRV_SEQ_PORT_CAP_SUBS_WRITE;
+ if (rawmidi_info & SNDRV_RAWMIDI_INFO_INPUT)
+ port->capability |= SNDRV_SEQ_PORT_CAP_READ |
+ SNDRV_SEQ_PORT_CAP_SYNC_READ |
+ SNDRV_SEQ_PORT_CAP_SUBS_READ;
+ if (rawmidi_info & SNDRV_RAWMIDI_INFO_DUPLEX)
+ port->capability |= SNDRV_SEQ_PORT_CAP_DUPLEX;
+ if (group->dir_bits & (1 << STR_IN))
+ port->direction |= SNDRV_SEQ_PORT_DIR_INPUT;
+ if (group->dir_bits & (1 << STR_OUT))
+ port->direction |= SNDRV_SEQ_PORT_DIR_OUTPUT;
+ port->ump_group = group->group + 1;
+ if (!group->active)
+ port->capability |= SNDRV_SEQ_PORT_CAP_INACTIVE;
+ port->type = SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC |
+ SNDRV_SEQ_PORT_TYPE_MIDI_UMP |
+ SNDRV_SEQ_PORT_TYPE_HARDWARE |
+ SNDRV_SEQ_PORT_TYPE_PORT;
+ port->midi_channels = 16;
+ if (*group->name)
+ snprintf(port->name, sizeof(port->name), "Group %d (%s)",
+ group->group + 1, group->name);
+ else
+ sprintf(port->name, "Group %d", group->group + 1);
+}
+
+/* create a new sequencer port per UMP group */
+static int seq_ump_group_init(struct seq_ump_client *client, int group_index)
+{
+ struct seq_ump_group *group = &client->groups[group_index];
+ struct snd_seq_port_info *port;
+ struct snd_seq_port_callback pcallbacks;
+ int err;
+
+ port = kzalloc(sizeof(*port), GFP_KERNEL);
+ if (!port) {
+ err = -ENOMEM;
+ goto error;
+ }
+
+ fill_port_info(port, client, group);
+ port->flags = SNDRV_SEQ_PORT_FLG_GIVEN_PORT;
+ memset(&pcallbacks, 0, sizeof(pcallbacks));
+ pcallbacks.owner = THIS_MODULE;
+ pcallbacks.private_data = client;
+ pcallbacks.subscribe = seq_ump_subscribe;
+ pcallbacks.unsubscribe = seq_ump_unsubscribe;
+ pcallbacks.use = seq_ump_use;
+ pcallbacks.unuse = seq_ump_unuse;
+ pcallbacks.event_input = seq_ump_process_event;
+ port->kernel = &pcallbacks;
+ err = snd_seq_kernel_client_ctl(client->seq_client,
+ SNDRV_SEQ_IOCTL_CREATE_PORT,
+ port);
+ error:
+ kfree(port);
+ return err;
+}
+
+/* update dir_bits and active flag for all groups in the client */
+static void update_group_attrs(struct seq_ump_client *client)
+{
+ struct snd_ump_block *fb;
+ struct seq_ump_group *group;
+ int i;
+
+ for (i = 0; i < SNDRV_UMP_MAX_GROUPS; i++) {
+ group = &client->groups[i];
+ *group->name = 0;
+ group->dir_bits = 0;
+ group->active = 0;
+ group->group = i;
+ }
+
+ list_for_each_entry(fb, &client->ump->block_list, list) {
+ if (fb->info.first_group < 0 ||
+ fb->info.first_group + fb->info.num_groups > SNDRV_UMP_MAX_GROUPS)
+ break;
+ group = &client->groups[fb->info.first_group];
+ for (i = 0; i < fb->info.num_groups; i++, group++) {
+ if (fb->info.active)
+ group->active = 1;
+ switch (fb->info.direction) {
+ case SNDRV_UMP_DIR_INPUT:
+ group->dir_bits |= (1 << STR_IN);
+ break;
+ case SNDRV_UMP_DIR_OUTPUT:
+ group->dir_bits |= (1 << STR_OUT);
+ break;
+ case SNDRV_UMP_DIR_BIDIRECTION:
+ group->dir_bits |= (1 << STR_OUT) | (1 << STR_IN);
+ break;
+ }
+ if (!*fb->info.name)
+ continue;
+ if (!*group->name) {
+ /* store the first matching name */
+ strscpy(group->name, fb->info.name,
+ sizeof(group->name));
+ } else {
+ /* when overlapping, concat names */
+ strlcat(group->name, ", ", sizeof(group->name));
+ strlcat(group->name, fb->info.name,
+ sizeof(group->name));
+ }
+ }
+ }
+}
+
+/* create a UMP Endpoint port */
+static int create_ump_endpoint_port(struct seq_ump_client *client)
+{
+ struct snd_seq_port_info *port;
+ struct snd_seq_port_callback pcallbacks;
+ unsigned int rawmidi_info = client->ump->core.info_flags;
+ int err;
+
+ port = kzalloc(sizeof(*port), GFP_KERNEL);
+ if (!port)
+ return -ENOMEM;
+
+ port->addr.client = client->seq_client;
+ port->addr.port = 0; /* fixed */
+ port->flags = SNDRV_SEQ_PORT_FLG_GIVEN_PORT;
+ port->capability = SNDRV_SEQ_PORT_CAP_UMP_ENDPOINT;
+ if (rawmidi_info & SNDRV_RAWMIDI_INFO_INPUT) {
+ port->capability |= SNDRV_SEQ_PORT_CAP_READ |
+ SNDRV_SEQ_PORT_CAP_SYNC_READ |
+ SNDRV_SEQ_PORT_CAP_SUBS_READ;
+ port->direction |= SNDRV_SEQ_PORT_DIR_INPUT;
+ }
+ if (rawmidi_info & SNDRV_RAWMIDI_INFO_OUTPUT) {
+ port->capability |= SNDRV_SEQ_PORT_CAP_WRITE |
+ SNDRV_SEQ_PORT_CAP_SYNC_WRITE |
+ SNDRV_SEQ_PORT_CAP_SUBS_WRITE;
+ port->direction |= SNDRV_SEQ_PORT_DIR_OUTPUT;
+ }
+ if (rawmidi_info & SNDRV_RAWMIDI_INFO_DUPLEX)
+ port->capability |= SNDRV_SEQ_PORT_CAP_DUPLEX;
+ port->ump_group = 0; /* no associated group, no conversion */
+ port->type = SNDRV_SEQ_PORT_TYPE_MIDI_UMP |
+ SNDRV_SEQ_PORT_TYPE_HARDWARE |
+ SNDRV_SEQ_PORT_TYPE_PORT;
+ port->midi_channels = 16;
+ strcpy(port->name, "MIDI 2.0");
+ memset(&pcallbacks, 0, sizeof(pcallbacks));
+ pcallbacks.owner = THIS_MODULE;
+ pcallbacks.private_data = client;
+ if (rawmidi_info & SNDRV_RAWMIDI_INFO_INPUT) {
+ pcallbacks.subscribe = seq_ump_subscribe;
+ pcallbacks.unsubscribe = seq_ump_unsubscribe;
+ }
+ if (rawmidi_info & SNDRV_RAWMIDI_INFO_OUTPUT) {
+ pcallbacks.use = seq_ump_use;
+ pcallbacks.unuse = seq_ump_unuse;
+ pcallbacks.event_input = seq_ump_process_event;
+ }
+ port->kernel = &pcallbacks;
+ err = snd_seq_kernel_client_ctl(client->seq_client,
+ SNDRV_SEQ_IOCTL_CREATE_PORT,
+ port);
+ kfree(port);
+ return err;
+}
+
+/* release the client resources */
+static void seq_ump_client_free(struct seq_ump_client *client)
+{
+ if (client->seq_client >= 0)
+ snd_seq_delete_kernel_client(client->seq_client);
+
+ client->ump->seq_ops = NULL;
+ client->ump->seq_client = NULL;
+
+ kfree(client);
+}
+
+/* update the MIDI version for the given client */
+static void setup_client_midi_version(struct seq_ump_client *client)
+{
+ struct snd_seq_client *cptr;
+
+ cptr = snd_seq_kernel_client_get(client->seq_client);
+ if (!cptr)
+ return;
+ if (client->ump->info.protocol & SNDRV_UMP_EP_INFO_PROTO_MIDI2)
+ cptr->midi_version = SNDRV_SEQ_CLIENT_UMP_MIDI_2_0;
+ else
+ cptr->midi_version = SNDRV_SEQ_CLIENT_UMP_MIDI_1_0;
+ snd_seq_kernel_client_put(cptr);
+}
+
+static const struct snd_seq_ump_ops seq_ump_ops = {
+ .input_receive = seq_ump_input_receive,
+};
+
+/* create a sequencer client and ports for the given UMP endpoint */
+static int snd_seq_ump_probe(struct device *_dev)
+{
+ struct snd_seq_device *dev = to_seq_dev(_dev);
+ struct snd_ump_endpoint *ump = dev->private_data;
+ struct snd_card *card = dev->card;
+ struct seq_ump_client *client;
+ struct snd_ump_block *fb;
+ struct snd_seq_client *cptr;
+ int p, err;
+
+ client = kzalloc(sizeof(*client), GFP_KERNEL);
+ if (!client)
+ return -ENOMEM;
+
+ client->ump = ump;
+
+ client->seq_client =
+ snd_seq_create_kernel_client(card, ump->core.device,
+ ump->core.name);
+ if (client->seq_client < 0) {
+ err = client->seq_client;
+ goto error;
+ }
+
+ client->ump_info[0] = &ump->info;
+ list_for_each_entry(fb, &ump->block_list, list)
+ client->ump_info[fb->info.block_id + 1] = &fb->info;
+
+ setup_client_midi_version(client);
+ update_group_attrs(client);
+
+ for (p = 0; p < SNDRV_UMP_MAX_GROUPS; p++) {
+ err = seq_ump_group_init(client, p);
+ if (err < 0)
+ goto error;
+ }
+
+ err = create_ump_endpoint_port(client);
+ if (err < 0)
+ goto error;
+
+ cptr = snd_seq_kernel_client_get(client->seq_client);
+ if (!cptr) {
+ err = -EINVAL;
+ goto error;
+ }
+ cptr->ump_info = client->ump_info;
+ snd_seq_kernel_client_put(cptr);
+
+ ump->seq_client = client;
+ ump->seq_ops = &seq_ump_ops;
+ return 0;
+
+ error:
+ seq_ump_client_free(client);
+ return err;
+}
+
+/* remove a sequencer client */
+static int snd_seq_ump_remove(struct device *_dev)
+{
+ struct snd_seq_device *dev = to_seq_dev(_dev);
+ struct snd_ump_endpoint *ump = dev->private_data;
+
+ if (ump->seq_client)
+ seq_ump_client_free(ump->seq_client);
+ return 0;
+}
+
+static struct snd_seq_driver seq_ump_driver = {
+ .driver = {
+ .name = KBUILD_MODNAME,
+ .probe = snd_seq_ump_probe,
+ .remove = snd_seq_ump_remove,
+ },
+ .id = SNDRV_SEQ_DEV_ID_UMP,
+ .argsize = 0,
+};
+
+module_snd_seq_driver(seq_ump_driver);
+
+MODULE_DESCRIPTION("ALSA sequencer client for UMP rawmidi");
+MODULE_LICENSE("GPL");
diff --git a/sound/core/seq/seq_ump_convert.c b/sound/core/seq/seq_ump_convert.c
new file mode 100644
index 000000000000..14ba6fed9dd1
--- /dev/null
+++ b/sound/core/seq/seq_ump_convert.c
@@ -0,0 +1,1203 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * ALSA sequencer event conversion between UMP and legacy clients
+ */
+
+#include <linux/init.h>
+#include <linux/errno.h>
+#include <linux/string.h>
+#include <sound/core.h>
+#include <sound/ump.h>
+#include <sound/ump_msg.h>
+#include "seq_ump_convert.h"
+
+/*
+ * Upgrade / downgrade value bits
+ */
+static u8 downscale_32_to_7bit(u32 src)
+{
+ return src >> 25;
+}
+
+static u16 downscale_32_to_14bit(u32 src)
+{
+ return src >> 18;
+}
+
+static u8 downscale_16_to_7bit(u16 src)
+{
+ return src >> 9;
+}
+
+static u16 upscale_7_to_16bit(u8 src)
+{
+ u16 val, repeat;
+
+ val = (u16)src << 9;
+ if (src <= 0x40)
+ return val;
+ repeat = src & 0x3f;
+ return val | (repeat << 3) | (repeat >> 3);
+}
+
+static u32 upscale_7_to_32bit(u8 src)
+{
+ u32 val, repeat;
+
+ val = src << 25;
+ if (src <= 0x40)
+ return val;
+ repeat = src & 0x3f;
+ return val | (repeat << 19) | (repeat << 13) |
+ (repeat << 7) | (repeat << 1) | (repeat >> 5);
+}
+
+static u32 upscale_14_to_32bit(u16 src)
+{
+ u32 val, repeat;
+
+ val = src << 18;
+ if (src <= 0x2000)
+ return val;
+ repeat = src & 0x1fff;
+ return val | (repeat << 5) | (repeat >> 8);
+}
+
+static unsigned char get_ump_group(struct snd_seq_client_port *port)
+{
+ return port->ump_group ? (port->ump_group - 1) : 0;
+}
+
+/* create a UMP header */
+#define make_raw_ump(port, type) \
+ ump_compose(type, get_ump_group(port), 0, 0)
+
+/*
+ * UMP -> MIDI1 sequencer event
+ */
+
+/* MIDI 1.0 CVM */
+
+/* encode note event */
+static void ump_midi1_to_note_ev(const union snd_ump_midi1_msg *val,
+ struct snd_seq_event *ev)
+{
+ ev->data.note.channel = val->note.channel;
+ ev->data.note.note = val->note.note;
+ ev->data.note.velocity = val->note.velocity;
+}
+
+/* encode one parameter controls */
+static void ump_midi1_to_ctrl_ev(const union snd_ump_midi1_msg *val,
+ struct snd_seq_event *ev)
+{
+ ev->data.control.channel = val->caf.channel;
+ ev->data.control.value = val->caf.data;
+}
+
+/* encode pitch wheel change */
+static void ump_midi1_to_pitchbend_ev(const union snd_ump_midi1_msg *val,
+ struct snd_seq_event *ev)
+{
+ ev->data.control.channel = val->pb.channel;
+ ev->data.control.value = (val->pb.data_msb << 7) | val->pb.data_lsb;
+ ev->data.control.value -= 8192;
+}
+
+/* encode midi control change */
+static void ump_midi1_to_cc_ev(const union snd_ump_midi1_msg *val,
+ struct snd_seq_event *ev)
+{
+ ev->data.control.channel = val->cc.channel;
+ ev->data.control.param = val->cc.index;
+ ev->data.control.value = val->cc.data;
+}
+
+/* Encoding MIDI 1.0 UMP packet */
+struct seq_ump_midi1_to_ev {
+ int seq_type;
+ void (*encode)(const union snd_ump_midi1_msg *val, struct snd_seq_event *ev);
+};
+
+/* Encoders for MIDI1 status 0x80-0xe0 */
+static struct seq_ump_midi1_to_ev midi1_msg_encoders[] = {
+ {SNDRV_SEQ_EVENT_NOTEOFF, ump_midi1_to_note_ev}, /* 0x80 */
+ {SNDRV_SEQ_EVENT_NOTEON, ump_midi1_to_note_ev}, /* 0x90 */
+ {SNDRV_SEQ_EVENT_KEYPRESS, ump_midi1_to_note_ev}, /* 0xa0 */
+ {SNDRV_SEQ_EVENT_CONTROLLER, ump_midi1_to_cc_ev}, /* 0xb0 */
+ {SNDRV_SEQ_EVENT_PGMCHANGE, ump_midi1_to_ctrl_ev}, /* 0xc0 */
+ {SNDRV_SEQ_EVENT_CHANPRESS, ump_midi1_to_ctrl_ev}, /* 0xd0 */
+ {SNDRV_SEQ_EVENT_PITCHBEND, ump_midi1_to_pitchbend_ev}, /* 0xe0 */
+};
+
+static int cvt_ump_midi1_to_event(const union snd_ump_midi1_msg *val,
+ struct snd_seq_event *ev)
+{
+ unsigned char status = val->note.status;
+
+ if (status < 0x8 || status > 0xe)
+ return 0; /* invalid - skip */
+ status -= 8;
+ ev->type = midi1_msg_encoders[status].seq_type;
+ ev->flags = SNDRV_SEQ_EVENT_LENGTH_FIXED;
+ midi1_msg_encoders[status].encode(val, ev);
+ return 1;
+}
+
+/* MIDI System message */
+
+/* encode one parameter value*/
+static void ump_system_to_one_param_ev(const union snd_ump_midi1_msg *val,
+ struct snd_seq_event *ev)
+{
+ ev->data.control.value = val->system.parm1;
+}
+
+/* encode song position */
+static void ump_system_to_songpos_ev(const union snd_ump_midi1_msg *val,
+ struct snd_seq_event *ev)
+{
+ ev->data.control.value = (val->system.parm1 << 7) | val->system.parm2;
+}
+
+/* Encoders for 0xf0 - 0xff */
+static struct seq_ump_midi1_to_ev system_msg_encoders[] = {
+ {SNDRV_SEQ_EVENT_NONE, NULL}, /* 0xf0 */
+ {SNDRV_SEQ_EVENT_QFRAME, ump_system_to_one_param_ev}, /* 0xf1 */
+ {SNDRV_SEQ_EVENT_SONGPOS, ump_system_to_songpos_ev}, /* 0xf2 */
+ {SNDRV_SEQ_EVENT_SONGSEL, ump_system_to_one_param_ev}, /* 0xf3 */
+ {SNDRV_SEQ_EVENT_NONE, NULL}, /* 0xf4 */
+ {SNDRV_SEQ_EVENT_NONE, NULL}, /* 0xf5 */
+ {SNDRV_SEQ_EVENT_TUNE_REQUEST, NULL}, /* 0xf6 */
+ {SNDRV_SEQ_EVENT_NONE, NULL}, /* 0xf7 */
+ {SNDRV_SEQ_EVENT_CLOCK, NULL}, /* 0xf8 */
+ {SNDRV_SEQ_EVENT_NONE, NULL}, /* 0xf9 */
+ {SNDRV_SEQ_EVENT_START, NULL}, /* 0xfa */
+ {SNDRV_SEQ_EVENT_CONTINUE, NULL}, /* 0xfb */
+ {SNDRV_SEQ_EVENT_STOP, NULL}, /* 0xfc */
+ {SNDRV_SEQ_EVENT_NONE, NULL}, /* 0xfd */
+ {SNDRV_SEQ_EVENT_SENSING, NULL}, /* 0xfe */
+ {SNDRV_SEQ_EVENT_RESET, NULL}, /* 0xff */
+};
+
+static int cvt_ump_system_to_event(const union snd_ump_midi1_msg *val,
+ struct snd_seq_event *ev)
+{
+ unsigned char status = val->system.status;
+
+ if ((status & 0xf0) != UMP_MIDI1_MSG_REALTIME)
+ return 0; /* invalid status - skip */
+ status &= 0x0f;
+ ev->type = system_msg_encoders[status].seq_type;
+ ev->flags = SNDRV_SEQ_EVENT_LENGTH_FIXED;
+ if (ev->type == SNDRV_SEQ_EVENT_NONE)
+ return 0;
+ if (system_msg_encoders[status].encode)
+ system_msg_encoders[status].encode(val, ev);
+ return 1;
+}
+
+/* MIDI 2.0 CVM */
+
+/* encode note event */
+static int ump_midi2_to_note_ev(const union snd_ump_midi2_msg *val,
+ struct snd_seq_event *ev)
+{
+ ev->data.note.channel = val->note.channel;
+ ev->data.note.note = val->note.note;
+ ev->data.note.velocity = downscale_16_to_7bit(val->note.velocity);
+ /* correct note-on velocity 0 to 1;
+ * it's no longer equivalent as not-off for MIDI 2.0
+ */
+ if (ev->type == SNDRV_SEQ_EVENT_NOTEON &&
+ !ev->data.note.velocity)
+ ev->data.note.velocity = 1;
+ return 1;
+}
+
+/* encode pitch wheel change */
+static int ump_midi2_to_pitchbend_ev(const union snd_ump_midi2_msg *val,
+ struct snd_seq_event *ev)
+{
+ ev->data.control.channel = val->pb.channel;
+ ev->data.control.value = downscale_32_to_14bit(val->pb.data);
+ ev->data.control.value -= 8192;
+ return 1;
+}
+
+/* encode midi control change */
+static int ump_midi2_to_cc_ev(const union snd_ump_midi2_msg *val,
+ struct snd_seq_event *ev)
+{
+ ev->data.control.channel = val->cc.channel;
+ ev->data.control.param = val->cc.index;
+ ev->data.control.value = downscale_32_to_7bit(val->cc.data);
+ return 1;
+}
+
+/* encode midi program change */
+static int ump_midi2_to_pgm_ev(const union snd_ump_midi2_msg *val,
+ struct snd_seq_event *ev)
+{
+ int size = 1;
+
+ ev->data.control.channel = val->pg.channel;
+ if (val->pg.bank_valid) {
+ ev->type = SNDRV_SEQ_EVENT_CONTROL14;
+ ev->data.control.param = UMP_CC_BANK_SELECT;
+ ev->data.control.value = (val->pg.bank_msb << 7) | val->pg.bank_lsb;
+ ev[1] = ev[0];
+ ev++;
+ ev->type = SNDRV_SEQ_EVENT_PGMCHANGE;
+ size = 2;
+ }
+ ev->data.control.value = val->pg.program;
+ return size;
+}
+
+/* encode one parameter controls */
+static int ump_midi2_to_ctrl_ev(const union snd_ump_midi2_msg *val,
+ struct snd_seq_event *ev)
+{
+ ev->data.control.channel = val->caf.channel;
+ ev->data.control.value = downscale_32_to_7bit(val->caf.data);
+ return 1;
+}
+
+/* encode RPN/NRPN */
+static int ump_midi2_to_rpn_ev(const union snd_ump_midi2_msg *val,
+ struct snd_seq_event *ev)
+{
+ ev->data.control.channel = val->rpn.channel;
+ ev->data.control.param = (val->rpn.bank << 7) | val->rpn.index;
+ ev->data.control.value = downscale_32_to_14bit(val->rpn.data);
+ return 1;
+}
+
+/* Encoding MIDI 2.0 UMP Packet */
+struct seq_ump_midi2_to_ev {
+ int seq_type;
+ int (*encode)(const union snd_ump_midi2_msg *val, struct snd_seq_event *ev);
+};
+
+/* Encoders for MIDI2 status 0x00-0xf0 */
+static struct seq_ump_midi2_to_ev midi2_msg_encoders[] = {
+ {SNDRV_SEQ_EVENT_NONE, NULL}, /* 0x00 */
+ {SNDRV_SEQ_EVENT_NONE, NULL}, /* 0x10 */
+ {SNDRV_SEQ_EVENT_REGPARAM, ump_midi2_to_rpn_ev}, /* 0x20 */
+ {SNDRV_SEQ_EVENT_NONREGPARAM, ump_midi2_to_rpn_ev}, /* 0x30 */
+ {SNDRV_SEQ_EVENT_NONE, NULL}, /* 0x40 */
+ {SNDRV_SEQ_EVENT_NONE, NULL}, /* 0x50 */
+ {SNDRV_SEQ_EVENT_NONE, NULL}, /* 0x60 */
+ {SNDRV_SEQ_EVENT_NONE, NULL}, /* 0x70 */
+ {SNDRV_SEQ_EVENT_NOTEOFF, ump_midi2_to_note_ev}, /* 0x80 */
+ {SNDRV_SEQ_EVENT_NOTEON, ump_midi2_to_note_ev}, /* 0x90 */
+ {SNDRV_SEQ_EVENT_KEYPRESS, ump_midi2_to_note_ev}, /* 0xa0 */
+ {SNDRV_SEQ_EVENT_CONTROLLER, ump_midi2_to_cc_ev}, /* 0xb0 */
+ {SNDRV_SEQ_EVENT_PGMCHANGE, ump_midi2_to_pgm_ev}, /* 0xc0 */
+ {SNDRV_SEQ_EVENT_CHANPRESS, ump_midi2_to_ctrl_ev}, /* 0xd0 */
+ {SNDRV_SEQ_EVENT_PITCHBEND, ump_midi2_to_pitchbend_ev}, /* 0xe0 */
+ {SNDRV_SEQ_EVENT_NONE, NULL}, /* 0xf0 */
+};
+
+static int cvt_ump_midi2_to_event(const union snd_ump_midi2_msg *val,
+ struct snd_seq_event *ev)
+{
+ unsigned char status = val->note.status;
+
+ ev->type = midi2_msg_encoders[status].seq_type;
+ if (ev->type == SNDRV_SEQ_EVENT_NONE)
+ return 0; /* skip */
+ ev->flags = SNDRV_SEQ_EVENT_LENGTH_FIXED;
+ return midi2_msg_encoders[status].encode(val, ev);
+}
+
+/* parse and compose for a sysex var-length event */
+static int cvt_ump_sysex7_to_event(const u32 *data, unsigned char *buf,
+ struct snd_seq_event *ev)
+{
+ unsigned char status;
+ unsigned char bytes;
+ u32 val;
+ int size = 0;
+
+ val = data[0];
+ status = ump_sysex_message_status(val);
+ bytes = ump_sysex_message_length(val);
+ if (bytes > 6)
+ return 0; // skip
+
+ if (status == UMP_SYSEX_STATUS_SINGLE ||
+ status == UMP_SYSEX_STATUS_START) {
+ buf[0] = UMP_MIDI1_MSG_SYSEX_START;
+ size = 1;
+ }
+
+ if (bytes > 0)
+ buf[size++] = (val >> 8) & 0x7f;
+ if (bytes > 1)
+ buf[size++] = val & 0x7f;
+ val = data[1];
+ if (bytes > 2)
+ buf[size++] = (val >> 24) & 0x7f;
+ if (bytes > 3)
+ buf[size++] = (val >> 16) & 0x7f;
+ if (bytes > 4)
+ buf[size++] = (val >> 8) & 0x7f;
+ if (bytes > 5)
+ buf[size++] = val & 0x7f;
+
+ if (status == UMP_SYSEX_STATUS_SINGLE ||
+ status == UMP_SYSEX_STATUS_END)
+ buf[size++] = UMP_MIDI1_MSG_SYSEX_END;
+
+ ev->type = SNDRV_SEQ_EVENT_SYSEX;
+ ev->flags = SNDRV_SEQ_EVENT_LENGTH_VARIABLE;
+ ev->data.ext.len = size;
+ ev->data.ext.ptr = buf;
+ return 1;
+}
+
+/* convert UMP packet from MIDI 1.0 to MIDI 2.0 and deliver it */
+static int cvt_ump_midi1_to_midi2(struct snd_seq_client *dest,
+ struct snd_seq_client_port *dest_port,
+ struct snd_seq_event *__event,
+ int atomic, int hop)
+{
+ struct snd_seq_ump_event *event = (struct snd_seq_ump_event *)__event;
+ struct snd_seq_ump_event ev_cvt;
+ const union snd_ump_midi1_msg *midi1 = (const union snd_ump_midi1_msg *)event->ump;
+ union snd_ump_midi2_msg *midi2 = (union snd_ump_midi2_msg *)ev_cvt.ump;
+
+ ev_cvt = *event;
+ memset(&ev_cvt.ump, 0, sizeof(ev_cvt.ump));
+
+ midi2->note.type = UMP_MSG_TYPE_MIDI2_CHANNEL_VOICE;
+ midi2->note.group = midi1->note.group;
+ midi2->note.status = midi1->note.status;
+ midi2->note.channel = midi1->note.channel;
+ switch (midi1->note.status) {
+ case UMP_MSG_STATUS_NOTE_ON:
+ case UMP_MSG_STATUS_NOTE_OFF:
+ midi2->note.note = midi1->note.note;
+ midi2->note.velocity = upscale_7_to_16bit(midi1->note.velocity);
+ break;
+ case UMP_MSG_STATUS_POLY_PRESSURE:
+ midi2->paf.note = midi1->paf.note;
+ midi2->paf.data = upscale_7_to_32bit(midi1->paf.data);
+ break;
+ case UMP_MSG_STATUS_CC:
+ midi2->cc.index = midi1->cc.index;
+ midi2->cc.data = upscale_7_to_32bit(midi1->cc.data);
+ break;
+ case UMP_MSG_STATUS_PROGRAM:
+ midi2->pg.program = midi1->pg.program;
+ break;
+ case UMP_MSG_STATUS_CHANNEL_PRESSURE:
+ midi2->caf.data = upscale_7_to_32bit(midi1->caf.data);
+ break;
+ case UMP_MSG_STATUS_PITCH_BEND:
+ midi2->pb.data = upscale_14_to_32bit((midi1->pb.data_msb << 7) |
+ midi1->pb.data_lsb);
+ break;
+ default:
+ return 0;
+ }
+
+ return __snd_seq_deliver_single_event(dest, dest_port,
+ (struct snd_seq_event *)&ev_cvt,
+ atomic, hop);
+}
+
+/* convert UMP packet from MIDI 2.0 to MIDI 1.0 and deliver it */
+static int cvt_ump_midi2_to_midi1(struct snd_seq_client *dest,
+ struct snd_seq_client_port *dest_port,
+ struct snd_seq_event *__event,
+ int atomic, int hop)
+{
+ struct snd_seq_ump_event *event = (struct snd_seq_ump_event *)__event;
+ struct snd_seq_ump_event ev_cvt;
+ union snd_ump_midi1_msg *midi1 = (union snd_ump_midi1_msg *)ev_cvt.ump;
+ const union snd_ump_midi2_msg *midi2 = (const union snd_ump_midi2_msg *)event->ump;
+ u16 v;
+
+ ev_cvt = *event;
+ memset(&ev_cvt.ump, 0, sizeof(ev_cvt.ump));
+
+ midi1->note.type = UMP_MSG_TYPE_MIDI1_CHANNEL_VOICE;
+ midi1->note.group = midi2->note.group;
+ midi1->note.status = midi2->note.status;
+ midi1->note.channel = midi2->note.channel;
+ switch (midi2->note.status << 4) {
+ case UMP_MSG_STATUS_NOTE_ON:
+ case UMP_MSG_STATUS_NOTE_OFF:
+ midi1->note.note = midi2->note.note;
+ midi1->note.velocity = downscale_16_to_7bit(midi2->note.velocity);
+ break;
+ case UMP_MSG_STATUS_POLY_PRESSURE:
+ midi1->paf.note = midi2->paf.note;
+ midi1->paf.data = downscale_32_to_7bit(midi2->paf.data);
+ break;
+ case UMP_MSG_STATUS_CC:
+ midi1->cc.index = midi2->cc.index;
+ midi1->cc.data = downscale_32_to_7bit(midi2->cc.data);
+ break;
+ case UMP_MSG_STATUS_PROGRAM:
+ midi1->pg.program = midi2->pg.program;
+ break;
+ case UMP_MSG_STATUS_CHANNEL_PRESSURE:
+ midi1->caf.data = downscale_32_to_7bit(midi2->caf.data);
+ break;
+ case UMP_MSG_STATUS_PITCH_BEND:
+ v = downscale_32_to_14bit(midi2->pb.data);
+ midi1->pb.data_msb = v >> 7;
+ midi1->pb.data_lsb = v & 0x7f;
+ break;
+ default:
+ return 0;
+ }
+
+ return __snd_seq_deliver_single_event(dest, dest_port,
+ (struct snd_seq_event *)&ev_cvt,
+ atomic, hop);
+}
+
+/* convert UMP to a legacy ALSA seq event and deliver it */
+static int cvt_ump_to_any(struct snd_seq_client *dest,
+ struct snd_seq_client_port *dest_port,
+ struct snd_seq_event *event,
+ unsigned char type,
+ int atomic, int hop)
+{
+ struct snd_seq_event ev_cvt[2]; /* up to two events */
+ struct snd_seq_ump_event *ump_ev = (struct snd_seq_ump_event *)event;
+ /* use the second event as a temp buffer for saving stack usage */
+ unsigned char *sysex_buf = (unsigned char *)(ev_cvt + 1);
+ unsigned char flags = event->flags & ~SNDRV_SEQ_EVENT_UMP;
+ int i, len, err;
+
+ ev_cvt[0] = ev_cvt[1] = *event;
+ ev_cvt[0].flags = flags;
+ ev_cvt[1].flags = flags;
+ switch (type) {
+ case UMP_MSG_TYPE_SYSTEM:
+ len = cvt_ump_system_to_event((union snd_ump_midi1_msg *)ump_ev->ump,
+ ev_cvt);
+ break;
+ case UMP_MSG_TYPE_MIDI1_CHANNEL_VOICE:
+ len = cvt_ump_midi1_to_event((union snd_ump_midi1_msg *)ump_ev->ump,
+ ev_cvt);
+ break;
+ case UMP_MSG_TYPE_MIDI2_CHANNEL_VOICE:
+ len = cvt_ump_midi2_to_event((union snd_ump_midi2_msg *)ump_ev->ump,
+ ev_cvt);
+ break;
+ case UMP_MSG_TYPE_DATA:
+ len = cvt_ump_sysex7_to_event(ump_ev->ump, sysex_buf, ev_cvt);
+ break;
+ default:
+ return 0;
+ }
+
+ for (i = 0; i < len; i++) {
+ err = __snd_seq_deliver_single_event(dest, dest_port,
+ &ev_cvt[i], atomic, hop);
+ if (err < 0)
+ return err;
+ }
+
+ return 0;
+}
+
+/* Replace UMP group field with the destination and deliver */
+static int deliver_with_group_convert(struct snd_seq_client *dest,
+ struct snd_seq_client_port *dest_port,
+ struct snd_seq_ump_event *ump_ev,
+ int atomic, int hop)
+{
+ struct snd_seq_ump_event ev = *ump_ev;
+
+ /* rewrite the group to the destination port */
+ ev.ump[0] &= ~(0xfU << 24);
+ /* fill with the new group; the dest_port->ump_group field is 1-based */
+ ev.ump[0] |= ((dest_port->ump_group - 1) << 24);
+
+ return __snd_seq_deliver_single_event(dest, dest_port,
+ (struct snd_seq_event *)&ev,
+ atomic, hop);
+}
+
+/* apply the UMP event filter; return true to skip the event */
+static bool ump_event_filtered(struct snd_seq_client *dest,
+ const struct snd_seq_ump_event *ev)
+{
+ unsigned char group;
+
+ group = ump_message_group(ev->ump[0]);
+ /* check the bitmap for 1-based group number */
+ return dest->group_filter & (1U << (group + 1));
+}
+
+/* Convert from UMP packet and deliver */
+int snd_seq_deliver_from_ump(struct snd_seq_client *source,
+ struct snd_seq_client *dest,
+ struct snd_seq_client_port *dest_port,
+ struct snd_seq_event *event,
+ int atomic, int hop)
+{
+ struct snd_seq_ump_event *ump_ev = (struct snd_seq_ump_event *)event;
+ unsigned char type;
+
+ if (snd_seq_ev_is_variable(event))
+ return 0; // skip, no variable event for UMP, so far
+ if (ump_event_filtered(dest, ump_ev))
+ return 0; // skip if group filter is set and matching
+ type = ump_message_type(ump_ev->ump[0]);
+
+ if (snd_seq_client_is_ump(dest)) {
+ if (snd_seq_client_is_midi2(dest) &&
+ type == UMP_MSG_TYPE_MIDI1_CHANNEL_VOICE)
+ return cvt_ump_midi1_to_midi2(dest, dest_port,
+ event, atomic, hop);
+ else if (!snd_seq_client_is_midi2(dest) &&
+ type == UMP_MSG_TYPE_MIDI2_CHANNEL_VOICE)
+ return cvt_ump_midi2_to_midi1(dest, dest_port,
+ event, atomic, hop);
+ /* non-EP port and different group is set? */
+ if (dest_port->ump_group &&
+ ump_message_group(*ump_ev->ump) + 1 != dest_port->ump_group)
+ return deliver_with_group_convert(dest, dest_port,
+ ump_ev, atomic, hop);
+ /* copy as-is */
+ return __snd_seq_deliver_single_event(dest, dest_port,
+ event, atomic, hop);
+ }
+
+ return cvt_ump_to_any(dest, dest_port, event, type, atomic, hop);
+}
+
+/*
+ * MIDI1 sequencer event -> UMP conversion
+ */
+
+/* Conversion to UMP MIDI 1.0 */
+
+/* convert note on/off event to MIDI 1.0 UMP */
+static int note_ev_to_ump_midi1(const struct snd_seq_event *event,
+ struct snd_seq_client_port *dest_port,
+ union snd_ump_midi1_msg *data,
+ unsigned char status)
+{
+ if (!event->data.note.velocity)
+ status = UMP_MSG_STATUS_NOTE_OFF;
+ data->note.status = status;
+ data->note.channel = event->data.note.channel & 0x0f;
+ data->note.velocity = event->data.note.velocity & 0x7f;
+ data->note.note = event->data.note.note & 0x7f;
+ return 1;
+}
+
+/* convert CC event to MIDI 1.0 UMP */
+static int cc_ev_to_ump_midi1(const struct snd_seq_event *event,
+ struct snd_seq_client_port *dest_port,
+ union snd_ump_midi1_msg *data,
+ unsigned char status)
+{
+ data->cc.status = status;
+ data->cc.channel = event->data.control.channel & 0x0f;
+ data->cc.index = event->data.control.param;
+ data->cc.data = event->data.control.value;
+ return 1;
+}
+
+/* convert one-parameter control event to MIDI 1.0 UMP */
+static int ctrl_ev_to_ump_midi1(const struct snd_seq_event *event,
+ struct snd_seq_client_port *dest_port,
+ union snd_ump_midi1_msg *data,
+ unsigned char status)
+{
+ data->caf.status = status;
+ data->caf.channel = event->data.control.channel & 0x0f;
+ data->caf.data = event->data.control.value & 0x7f;
+ return 1;
+}
+
+/* convert pitchbend event to MIDI 1.0 UMP */
+static int pitchbend_ev_to_ump_midi1(const struct snd_seq_event *event,
+ struct snd_seq_client_port *dest_port,
+ union snd_ump_midi1_msg *data,
+ unsigned char status)
+{
+ int val = event->data.control.value + 8192;
+
+ val = clamp(val, 0, 0x3fff);
+ data->pb.status = status;
+ data->pb.channel = event->data.control.channel & 0x0f;
+ data->pb.data_msb = (val >> 7) & 0x7f;
+ data->pb.data_lsb = val & 0x7f;
+ return 1;
+}
+
+/* convert 14bit control event to MIDI 1.0 UMP; split to two events */
+static int ctrl14_ev_to_ump_midi1(const struct snd_seq_event *event,
+ struct snd_seq_client_port *dest_port,
+ union snd_ump_midi1_msg *data,
+ unsigned char status)
+{
+ data->cc.status = UMP_MSG_STATUS_CC;
+ data->cc.channel = event->data.control.channel & 0x0f;
+ data->cc.index = event->data.control.param & 0x7f;
+ if (event->data.control.param < 0x20) {
+ data->cc.data = (event->data.control.value >> 7) & 0x7f;
+ data[1] = data[0];
+ data[1].cc.index = event->data.control.param | 0x20;
+ data[1].cc.data = event->data.control.value & 0x7f;
+ return 2;
+ }
+
+ data->cc.data = event->data.control.value & 0x7f;
+ return 1;
+}
+
+/* convert RPN/NRPN event to MIDI 1.0 UMP; split to four events */
+static int rpn_ev_to_ump_midi1(const struct snd_seq_event *event,
+ struct snd_seq_client_port *dest_port,
+ union snd_ump_midi1_msg *data,
+ unsigned char status)
+{
+ bool is_rpn = (status == UMP_MSG_STATUS_RPN);
+
+ data->cc.status = UMP_MSG_STATUS_CC;
+ data->cc.channel = event->data.control.channel & 0x0f;
+ data[1] = data[2] = data[3] = data[0];
+
+ data[0].cc.index = is_rpn ? UMP_CC_RPN_MSB : UMP_CC_NRPN_MSB;
+ data[0].cc.data = (event->data.control.param >> 7) & 0x7f;
+ data[1].cc.index = is_rpn ? UMP_CC_RPN_LSB : UMP_CC_NRPN_LSB;
+ data[1].cc.data = event->data.control.param & 0x7f;
+ data[2].cc.index = UMP_CC_DATA;
+ data[2].cc.data = (event->data.control.value >> 7) & 0x7f;
+ data[3].cc.index = UMP_CC_DATA_LSB;
+ data[3].cc.data = event->data.control.value & 0x7f;
+ return 4;
+}
+
+/* convert system / RT message to UMP */
+static int system_ev_to_ump_midi1(const struct snd_seq_event *event,
+ struct snd_seq_client_port *dest_port,
+ union snd_ump_midi1_msg *data,
+ unsigned char status)
+{
+ data->system.status = status;
+ return 1;
+}
+
+/* convert system / RT message with 1 parameter to UMP */
+static int system_1p_ev_to_ump_midi1(const struct snd_seq_event *event,
+ struct snd_seq_client_port *dest_port,
+ union snd_ump_midi1_msg *data,
+ unsigned char status)
+{
+ data->system.status = status;
+ data->system.parm1 = event->data.control.value & 0x7f;
+ return 1;
+}
+
+/* convert system / RT message with two parameters to UMP */
+static int system_2p_ev_to_ump_midi1(const struct snd_seq_event *event,
+ struct snd_seq_client_port *dest_port,
+ union snd_ump_midi1_msg *data,
+ unsigned char status)
+{
+ data->system.status = status;
+ data->system.parm1 = (event->data.control.value >> 7) & 0x7f;
+ data->system.parm1 = event->data.control.value & 0x7f;
+ return 1;
+}
+
+/* Conversion to UMP MIDI 2.0 */
+
+/* convert note on/off event to MIDI 2.0 UMP */
+static int note_ev_to_ump_midi2(const struct snd_seq_event *event,
+ struct snd_seq_client_port *dest_port,
+ union snd_ump_midi2_msg *data,
+ unsigned char status)
+{
+ if (!event->data.note.velocity)
+ status = UMP_MSG_STATUS_NOTE_OFF;
+ data->note.status = status;
+ data->note.channel = event->data.note.channel & 0x0f;
+ data->note.note = event->data.note.note & 0x7f;
+ data->note.velocity = upscale_7_to_16bit(event->data.note.velocity & 0x7f);
+ return 1;
+}
+
+/* convert PAF event to MIDI 2.0 UMP */
+static int paf_ev_to_ump_midi2(const struct snd_seq_event *event,
+ struct snd_seq_client_port *dest_port,
+ union snd_ump_midi2_msg *data,
+ unsigned char status)
+{
+ data->paf.status = status;
+ data->paf.channel = event->data.note.channel & 0x0f;
+ data->paf.note = event->data.note.note & 0x7f;
+ data->paf.data = upscale_7_to_32bit(event->data.note.velocity & 0x7f);
+ return 1;
+}
+
+/* set up the MIDI2 RPN/NRPN packet data from the parsed info */
+static void fill_rpn(struct snd_seq_ump_midi2_bank *cc,
+ union snd_ump_midi2_msg *data)
+{
+ if (cc->rpn_set) {
+ data->rpn.status = UMP_MSG_STATUS_RPN;
+ data->rpn.bank = cc->cc_rpn_msb;
+ data->rpn.index = cc->cc_rpn_lsb;
+ cc->rpn_set = 0;
+ cc->cc_rpn_msb = cc->cc_rpn_lsb = 0;
+ } else {
+ data->rpn.status = UMP_MSG_STATUS_NRPN;
+ data->rpn.bank = cc->cc_nrpn_msb;
+ data->rpn.index = cc->cc_nrpn_lsb;
+ cc->nrpn_set = 0;
+ cc->cc_nrpn_msb = cc->cc_nrpn_lsb = 0;
+ }
+ data->rpn.data = upscale_14_to_32bit((cc->cc_data_msb << 7) |
+ cc->cc_data_lsb);
+ cc->cc_data_msb = cc->cc_data_lsb = 0;
+}
+
+/* convert CC event to MIDI 2.0 UMP */
+static int cc_ev_to_ump_midi2(const struct snd_seq_event *event,
+ struct snd_seq_client_port *dest_port,
+ union snd_ump_midi2_msg *data,
+ unsigned char status)
+{
+ unsigned char channel = event->data.control.channel & 0x0f;
+ unsigned char index = event->data.control.param & 0x7f;
+ unsigned char val = event->data.control.value & 0x7f;
+ struct snd_seq_ump_midi2_bank *cc = &dest_port->midi2_bank[channel];
+
+ /* process special CC's (bank/rpn/nrpn) */
+ switch (index) {
+ case UMP_CC_RPN_MSB:
+ cc->rpn_set = 1;
+ cc->cc_rpn_msb = val;
+ return 0; // skip
+ case UMP_CC_RPN_LSB:
+ cc->rpn_set = 1;
+ cc->cc_rpn_lsb = val;
+ return 0; // skip
+ case UMP_CC_NRPN_MSB:
+ cc->nrpn_set = 1;
+ cc->cc_nrpn_msb = val;
+ return 0; // skip
+ case UMP_CC_NRPN_LSB:
+ cc->nrpn_set = 1;
+ cc->cc_nrpn_lsb = val;
+ return 0; // skip
+ case UMP_CC_DATA:
+ cc->cc_data_msb = val;
+ return 0; // skip
+ case UMP_CC_BANK_SELECT:
+ cc->bank_set = 1;
+ cc->cc_bank_msb = val;
+ return 0; // skip
+ case UMP_CC_BANK_SELECT_LSB:
+ cc->bank_set = 1;
+ cc->cc_bank_lsb = val;
+ return 0; // skip
+ case UMP_CC_DATA_LSB:
+ cc->cc_data_lsb = val;
+ if (!(cc->rpn_set || cc->nrpn_set))
+ return 0; // skip
+ fill_rpn(cc, data);
+ return 1;
+ }
+
+ data->cc.status = status;
+ data->cc.channel = channel;
+ data->cc.index = index;
+ data->cc.data = upscale_7_to_32bit(event->data.control.value & 0x7f);
+ return 1;
+}
+
+/* convert one-parameter control event to MIDI 2.0 UMP */
+static int ctrl_ev_to_ump_midi2(const struct snd_seq_event *event,
+ struct snd_seq_client_port *dest_port,
+ union snd_ump_midi2_msg *data,
+ unsigned char status)
+{
+ data->caf.status = status;
+ data->caf.channel = event->data.control.channel & 0x0f;
+ data->caf.data = upscale_7_to_32bit(event->data.control.value & 0x7f);
+ return 1;
+}
+
+/* convert program change event to MIDI 2.0 UMP */
+static int pgm_ev_to_ump_midi2(const struct snd_seq_event *event,
+ struct snd_seq_client_port *dest_port,
+ union snd_ump_midi2_msg *data,
+ unsigned char status)
+{
+ unsigned char channel = event->data.control.channel & 0x0f;
+ struct snd_seq_ump_midi2_bank *cc = &dest_port->midi2_bank[channel];
+
+ data->pg.status = status;
+ data->pg.channel = channel;
+ data->pg.program = event->data.control.value & 0x7f;
+ if (cc->bank_set) {
+ data->pg.bank_valid = 1;
+ data->pg.bank_msb = cc->cc_bank_msb;
+ data->pg.bank_lsb = cc->cc_bank_lsb;
+ cc->bank_set = 0;
+ cc->cc_bank_msb = cc->cc_bank_lsb = 0;
+ }
+ return 1;
+}
+
+/* convert pitchbend event to MIDI 2.0 UMP */
+static int pitchbend_ev_to_ump_midi2(const struct snd_seq_event *event,
+ struct snd_seq_client_port *dest_port,
+ union snd_ump_midi2_msg *data,
+ unsigned char status)
+{
+ int val = event->data.control.value + 8192;
+
+ val = clamp(val, 0, 0x3fff);
+ data->pb.status = status;
+ data->pb.channel = event->data.control.channel & 0x0f;
+ data->pb.data = upscale_14_to_32bit(val);
+ return 1;
+}
+
+/* convert 14bit control event to MIDI 2.0 UMP; split to two events */
+static int ctrl14_ev_to_ump_midi2(const struct snd_seq_event *event,
+ struct snd_seq_client_port *dest_port,
+ union snd_ump_midi2_msg *data,
+ unsigned char status)
+{
+ unsigned char channel = event->data.control.channel & 0x0f;
+ unsigned char index = event->data.control.param & 0x7f;
+ struct snd_seq_ump_midi2_bank *cc = &dest_port->midi2_bank[channel];
+ unsigned char msb, lsb;
+
+ msb = (event->data.control.value >> 7) & 0x7f;
+ lsb = event->data.control.value & 0x7f;
+ /* process special CC's (bank/rpn/nrpn) */
+ switch (index) {
+ case UMP_CC_BANK_SELECT:
+ cc->cc_bank_msb = msb;
+ fallthrough;
+ case UMP_CC_BANK_SELECT_LSB:
+ cc->bank_set = 1;
+ cc->cc_bank_lsb = lsb;
+ return 0; // skip
+ case UMP_CC_RPN_MSB:
+ cc->cc_rpn_msb = msb;
+ fallthrough;
+ case UMP_CC_RPN_LSB:
+ cc->rpn_set = 1;
+ cc->cc_rpn_lsb = lsb;
+ return 0; // skip
+ case UMP_CC_NRPN_MSB:
+ cc->cc_nrpn_msb = msb;
+ fallthrough;
+ case UMP_CC_NRPN_LSB:
+ cc->nrpn_set = 1;
+ cc->cc_nrpn_lsb = lsb;
+ return 0; // skip
+ case UMP_CC_DATA:
+ cc->cc_data_msb = msb;
+ fallthrough;
+ case UMP_CC_DATA_LSB:
+ cc->cc_data_lsb = lsb;
+ if (!(cc->rpn_set || cc->nrpn_set))
+ return 0; // skip
+ fill_rpn(cc, data);
+ return 1;
+ }
+
+ data->cc.status = UMP_MSG_STATUS_CC;
+ data->cc.channel = channel;
+ data->cc.index = index;
+ if (event->data.control.param < 0x20) {
+ data->cc.data = upscale_7_to_32bit(msb);
+ data[1] = data[0];
+ data[1].cc.index = event->data.control.param | 0x20;
+ data[1].cc.data = upscale_7_to_32bit(lsb);
+ return 2;
+ }
+
+ data->cc.data = upscale_7_to_32bit(lsb);
+ return 1;
+}
+
+/* convert RPN/NRPN event to MIDI 2.0 UMP */
+static int rpn_ev_to_ump_midi2(const struct snd_seq_event *event,
+ struct snd_seq_client_port *dest_port,
+ union snd_ump_midi2_msg *data,
+ unsigned char status)
+{
+ data->rpn.status = status;
+ data->rpn.channel = event->data.control.channel;
+ data->rpn.bank = (event->data.control.param >> 7) & 0x7f;
+ data->rpn.index = event->data.control.param & 0x7f;
+ data->rpn.data = upscale_14_to_32bit(event->data.control.value & 0x3fff);
+ return 1;
+}
+
+/* convert system / RT message to UMP */
+static int system_ev_to_ump_midi2(const struct snd_seq_event *event,
+ struct snd_seq_client_port *dest_port,
+ union snd_ump_midi2_msg *data,
+ unsigned char status)
+{
+ return system_ev_to_ump_midi1(event, dest_port,
+ (union snd_ump_midi1_msg *)data,
+ status);
+}
+
+/* convert system / RT message with 1 parameter to UMP */
+static int system_1p_ev_to_ump_midi2(const struct snd_seq_event *event,
+ struct snd_seq_client_port *dest_port,
+ union snd_ump_midi2_msg *data,
+ unsigned char status)
+{
+ return system_1p_ev_to_ump_midi1(event, dest_port,
+ (union snd_ump_midi1_msg *)data,
+ status);
+}
+
+/* convert system / RT message with two parameters to UMP */
+static int system_2p_ev_to_ump_midi2(const struct snd_seq_event *event,
+ struct snd_seq_client_port *dest_port,
+ union snd_ump_midi2_msg *data,
+ unsigned char status)
+{
+ return system_1p_ev_to_ump_midi1(event, dest_port,
+ (union snd_ump_midi1_msg *)data,
+ status);
+}
+
+struct seq_ev_to_ump {
+ int seq_type;
+ unsigned char status;
+ int (*midi1_encode)(const struct snd_seq_event *event,
+ struct snd_seq_client_port *dest_port,
+ union snd_ump_midi1_msg *data,
+ unsigned char status);
+ int (*midi2_encode)(const struct snd_seq_event *event,
+ struct snd_seq_client_port *dest_port,
+ union snd_ump_midi2_msg *data,
+ unsigned char status);
+};
+
+static const struct seq_ev_to_ump seq_ev_ump_encoders[] = {
+ { SNDRV_SEQ_EVENT_NOTEON, UMP_MSG_STATUS_NOTE_ON,
+ note_ev_to_ump_midi1, note_ev_to_ump_midi2 },
+ { SNDRV_SEQ_EVENT_NOTEOFF, UMP_MSG_STATUS_NOTE_OFF,
+ note_ev_to_ump_midi1, note_ev_to_ump_midi2 },
+ { SNDRV_SEQ_EVENT_KEYPRESS, UMP_MSG_STATUS_POLY_PRESSURE,
+ note_ev_to_ump_midi1, paf_ev_to_ump_midi2 },
+ { SNDRV_SEQ_EVENT_CONTROLLER, UMP_MSG_STATUS_CC,
+ cc_ev_to_ump_midi1, cc_ev_to_ump_midi2 },
+ { SNDRV_SEQ_EVENT_PGMCHANGE, UMP_MSG_STATUS_PROGRAM,
+ ctrl_ev_to_ump_midi1, pgm_ev_to_ump_midi2 },
+ { SNDRV_SEQ_EVENT_CHANPRESS, UMP_MSG_STATUS_CHANNEL_PRESSURE,
+ ctrl_ev_to_ump_midi1, ctrl_ev_to_ump_midi2 },
+ { SNDRV_SEQ_EVENT_PITCHBEND, UMP_MSG_STATUS_PITCH_BEND,
+ pitchbend_ev_to_ump_midi1, pitchbend_ev_to_ump_midi2 },
+ { SNDRV_SEQ_EVENT_CONTROL14, 0,
+ ctrl14_ev_to_ump_midi1, ctrl14_ev_to_ump_midi2 },
+ { SNDRV_SEQ_EVENT_NONREGPARAM, UMP_MSG_STATUS_NRPN,
+ rpn_ev_to_ump_midi1, rpn_ev_to_ump_midi2 },
+ { SNDRV_SEQ_EVENT_REGPARAM, UMP_MSG_STATUS_RPN,
+ rpn_ev_to_ump_midi1, rpn_ev_to_ump_midi2 },
+ { SNDRV_SEQ_EVENT_QFRAME, UMP_SYSTEM_STATUS_MIDI_TIME_CODE,
+ system_1p_ev_to_ump_midi1, system_1p_ev_to_ump_midi2 },
+ { SNDRV_SEQ_EVENT_SONGPOS, UMP_SYSTEM_STATUS_SONG_POSITION,
+ system_2p_ev_to_ump_midi1, system_2p_ev_to_ump_midi2 },
+ { SNDRV_SEQ_EVENT_SONGSEL, UMP_SYSTEM_STATUS_SONG_SELECT,
+ system_1p_ev_to_ump_midi1, system_1p_ev_to_ump_midi2 },
+ { SNDRV_SEQ_EVENT_TUNE_REQUEST, UMP_SYSTEM_STATUS_TUNE_REQUEST,
+ system_ev_to_ump_midi1, system_ev_to_ump_midi2 },
+ { SNDRV_SEQ_EVENT_CLOCK, UMP_SYSTEM_STATUS_TIMING_CLOCK,
+ system_ev_to_ump_midi1, system_ev_to_ump_midi2 },
+ { SNDRV_SEQ_EVENT_START, UMP_SYSTEM_STATUS_START,
+ system_ev_to_ump_midi1, system_ev_to_ump_midi2 },
+ { SNDRV_SEQ_EVENT_CONTINUE, UMP_SYSTEM_STATUS_CONTINUE,
+ system_ev_to_ump_midi1, system_ev_to_ump_midi2 },
+ { SNDRV_SEQ_EVENT_STOP, UMP_SYSTEM_STATUS_STOP,
+ system_ev_to_ump_midi1, system_ev_to_ump_midi2 },
+ { SNDRV_SEQ_EVENT_SENSING, UMP_SYSTEM_STATUS_ACTIVE_SENSING,
+ system_ev_to_ump_midi1, system_ev_to_ump_midi2 },
+};
+
+static const struct seq_ev_to_ump *find_ump_encoder(int type)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(seq_ev_ump_encoders); i++)
+ if (seq_ev_ump_encoders[i].seq_type == type)
+ return &seq_ev_ump_encoders[i];
+
+ return NULL;
+}
+
+static void setup_ump_event(struct snd_seq_ump_event *dest,
+ const struct snd_seq_event *src)
+{
+ memcpy(dest, src, sizeof(*src));
+ dest->type = 0;
+ dest->flags |= SNDRV_SEQ_EVENT_UMP;
+ dest->flags &= ~SNDRV_SEQ_EVENT_LENGTH_MASK;
+ memset(dest->ump, 0, sizeof(dest->ump));
+}
+
+/* Convert ALSA seq event to UMP MIDI 1.0 and deliver it */
+static int cvt_to_ump_midi1(struct snd_seq_client *dest,
+ struct snd_seq_client_port *dest_port,
+ struct snd_seq_event *event,
+ int atomic, int hop)
+{
+ const struct seq_ev_to_ump *encoder;
+ struct snd_seq_ump_event ev_cvt;
+ union snd_ump_midi1_msg data[4];
+ int i, n, err;
+
+ encoder = find_ump_encoder(event->type);
+ if (!encoder)
+ return __snd_seq_deliver_single_event(dest, dest_port,
+ event, atomic, hop);
+
+ data->raw = make_raw_ump(dest_port, UMP_MSG_TYPE_MIDI1_CHANNEL_VOICE);
+ n = encoder->midi1_encode(event, dest_port, data, encoder->status);
+ if (!n)
+ return 0;
+
+ setup_ump_event(&ev_cvt, event);
+ for (i = 0; i < n; i++) {
+ ev_cvt.ump[0] = data[i].raw;
+ err = __snd_seq_deliver_single_event(dest, dest_port,
+ (struct snd_seq_event *)&ev_cvt,
+ atomic, hop);
+ if (err < 0)
+ return err;
+ }
+
+ return 0;
+}
+
+/* Convert ALSA seq event to UMP MIDI 2.0 and deliver it */
+static int cvt_to_ump_midi2(struct snd_seq_client *dest,
+ struct snd_seq_client_port *dest_port,
+ struct snd_seq_event *event,
+ int atomic, int hop)
+{
+ const struct seq_ev_to_ump *encoder;
+ struct snd_seq_ump_event ev_cvt;
+ union snd_ump_midi2_msg data[2];
+ int i, n, err;
+
+ encoder = find_ump_encoder(event->type);
+ if (!encoder)
+ return __snd_seq_deliver_single_event(dest, dest_port,
+ event, atomic, hop);
+
+ data->raw[0] = make_raw_ump(dest_port, UMP_MSG_TYPE_MIDI2_CHANNEL_VOICE);
+ data->raw[1] = 0;
+ n = encoder->midi2_encode(event, dest_port, data, encoder->status);
+ if (!n)
+ return 0;
+
+ setup_ump_event(&ev_cvt, event);
+ for (i = 0; i < n; i++) {
+ memcpy(ev_cvt.ump, &data[i], sizeof(data[i]));
+ err = __snd_seq_deliver_single_event(dest, dest_port,
+ (struct snd_seq_event *)&ev_cvt,
+ atomic, hop);
+ if (err < 0)
+ return err;
+ }
+
+ return 0;
+}
+
+/* Fill up a sysex7 UMP from the byte stream */
+static void fill_sysex7_ump(struct snd_seq_client_port *dest_port,
+ u32 *val, u8 status, u8 *buf, int len)
+{
+ memset(val, 0, 8);
+ memcpy((u8 *)val + 2, buf, len);
+#ifdef __LITTLE_ENDIAN
+ swab32_array(val, 2);
+#endif
+ val[0] |= ump_compose(UMP_MSG_TYPE_DATA, get_ump_group(dest_port),
+ status, len);
+}
+
+/* Convert sysex var event to UMP sysex7 packets and deliver them */
+static int cvt_sysex_to_ump(struct snd_seq_client *dest,
+ struct snd_seq_client_port *dest_port,
+ struct snd_seq_event *event,
+ int atomic, int hop)
+{
+ struct snd_seq_ump_event ev_cvt;
+ unsigned char status;
+ u8 buf[6], *xbuf;
+ int offset = 0;
+ int len, err;
+
+ if (!snd_seq_ev_is_variable(event))
+ return 0;
+
+ setup_ump_event(&ev_cvt, event);
+ for (;;) {
+ len = snd_seq_expand_var_event_at(event, sizeof(buf), buf, offset);
+ if (len <= 0)
+ break;
+ if (WARN_ON(len > 6))
+ break;
+ offset += len;
+ xbuf = buf;
+ if (*xbuf == UMP_MIDI1_MSG_SYSEX_START) {
+ status = UMP_SYSEX_STATUS_START;
+ xbuf++;
+ len--;
+ if (len > 0 && xbuf[len - 1] == UMP_MIDI1_MSG_SYSEX_END) {
+ status = UMP_SYSEX_STATUS_SINGLE;
+ len--;
+ }
+ } else {
+ if (xbuf[len - 1] == UMP_MIDI1_MSG_SYSEX_END) {
+ status = UMP_SYSEX_STATUS_END;
+ len--;
+ } else {
+ status = UMP_SYSEX_STATUS_CONTINUE;
+ }
+ }
+ fill_sysex7_ump(dest_port, ev_cvt.ump, status, xbuf, len);
+ err = __snd_seq_deliver_single_event(dest, dest_port,
+ (struct snd_seq_event *)&ev_cvt,
+ atomic, hop);
+ if (err < 0)
+ return err;
+ }
+ return 0;
+}
+
+/* Convert to UMP packet and deliver */
+int snd_seq_deliver_to_ump(struct snd_seq_client *source,
+ struct snd_seq_client *dest,
+ struct snd_seq_client_port *dest_port,
+ struct snd_seq_event *event,
+ int atomic, int hop)
+{
+ if (event->type == SNDRV_SEQ_EVENT_SYSEX)
+ return cvt_sysex_to_ump(dest, dest_port, event, atomic, hop);
+ else if (snd_seq_client_is_midi2(dest))
+ return cvt_to_ump_midi2(dest, dest_port, event, atomic, hop);
+ else
+ return cvt_to_ump_midi1(dest, dest_port, event, atomic, hop);
+}
diff --git a/sound/core/seq/seq_ump_convert.h b/sound/core/seq/seq_ump_convert.h
new file mode 100644
index 000000000000..6c146d803280
--- /dev/null
+++ b/sound/core/seq/seq_ump_convert.h
@@ -0,0 +1,22 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * ALSA sequencer event conversion between UMP and legacy clients
+ */
+#ifndef __SEQ_UMP_CONVERT_H
+#define __SEQ_UMP_CONVERT_H
+
+#include "seq_clientmgr.h"
+#include "seq_ports.h"
+
+int snd_seq_deliver_from_ump(struct snd_seq_client *source,
+ struct snd_seq_client *dest,
+ struct snd_seq_client_port *dest_port,
+ struct snd_seq_event *event,
+ int atomic, int hop);
+int snd_seq_deliver_to_ump(struct snd_seq_client *source,
+ struct snd_seq_client *dest,
+ struct snd_seq_client_port *dest_port,
+ struct snd_seq_event *event,
+ int atomic, int hop);
+
+#endif /* __SEQ_UMP_CONVERT_H */
diff --git a/sound/core/seq/seq_virmidi.c b/sound/core/seq/seq_virmidi.c
index f5cae49500c8..1b9260108e48 100644
--- a/sound/core/seq/seq_virmidi.c
+++ b/sound/core/seq/seq_virmidi.c
@@ -385,6 +385,7 @@ static int snd_virmidi_dev_attach_seq(struct snd_virmidi_dev *rdev)
pinfo->capability |= SNDRV_SEQ_PORT_CAP_WRITE | SNDRV_SEQ_PORT_CAP_SYNC_WRITE | SNDRV_SEQ_PORT_CAP_SUBS_WRITE;
pinfo->capability |= SNDRV_SEQ_PORT_CAP_READ | SNDRV_SEQ_PORT_CAP_SYNC_READ | SNDRV_SEQ_PORT_CAP_SUBS_READ;
pinfo->capability |= SNDRV_SEQ_PORT_CAP_DUPLEX;
+ pinfo->direction = SNDRV_SEQ_PORT_DIR_BIDIRECTION;
pinfo->type = SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC
| SNDRV_SEQ_PORT_TYPE_SOFTWARE
| SNDRV_SEQ_PORT_TYPE_PORT;
diff --git a/sound/core/ump.c b/sound/core/ump.c
new file mode 100644
index 000000000000..69993cad6772
--- /dev/null
+++ b/sound/core/ump.c
@@ -0,0 +1,668 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Universal MIDI Packet (UMP) support
+ */
+
+#include <linux/list.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/export.h>
+#include <linux/mm.h>
+#include <sound/core.h>
+#include <sound/rawmidi.h>
+#include <sound/ump.h>
+#include "ump_convert.h"
+
+#define ump_err(ump, fmt, args...) dev_err(&(ump)->core.dev, fmt, ##args)
+#define ump_warn(ump, fmt, args...) dev_warn(&(ump)->core.dev, fmt, ##args)
+#define ump_info(ump, fmt, args...) dev_info(&(ump)->core.dev, fmt, ##args)
+#define ump_dbg(ump, fmt, args...) dev_dbg(&(ump)->core.dev, fmt, ##args)
+
+static int snd_ump_dev_register(struct snd_rawmidi *rmidi);
+static int snd_ump_dev_unregister(struct snd_rawmidi *rmidi);
+static long snd_ump_ioctl(struct snd_rawmidi *rmidi, unsigned int cmd,
+ void __user *argp);
+static void snd_ump_proc_read(struct snd_info_entry *entry,
+ struct snd_info_buffer *buffer);
+static int snd_ump_rawmidi_open(struct snd_rawmidi_substream *substream);
+static int snd_ump_rawmidi_close(struct snd_rawmidi_substream *substream);
+static void snd_ump_rawmidi_trigger(struct snd_rawmidi_substream *substream,
+ int up);
+static void snd_ump_rawmidi_drain(struct snd_rawmidi_substream *substream);
+
+#if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI)
+static int process_legacy_output(struct snd_ump_endpoint *ump,
+ u32 *buffer, int count);
+static void process_legacy_input(struct snd_ump_endpoint *ump, const u32 *src,
+ int words);
+#else
+static inline int process_legacy_output(struct snd_ump_endpoint *ump,
+ u32 *buffer, int count)
+{
+ return 0;
+}
+static inline void process_legacy_input(struct snd_ump_endpoint *ump,
+ const u32 *src, int words)
+{
+}
+#endif
+
+static const struct snd_rawmidi_global_ops snd_ump_rawmidi_ops = {
+ .dev_register = snd_ump_dev_register,
+ .dev_unregister = snd_ump_dev_unregister,
+ .ioctl = snd_ump_ioctl,
+ .proc_read = snd_ump_proc_read,
+};
+
+static const struct snd_rawmidi_ops snd_ump_rawmidi_input_ops = {
+ .open = snd_ump_rawmidi_open,
+ .close = snd_ump_rawmidi_close,
+ .trigger = snd_ump_rawmidi_trigger,
+};
+
+static const struct snd_rawmidi_ops snd_ump_rawmidi_output_ops = {
+ .open = snd_ump_rawmidi_open,
+ .close = snd_ump_rawmidi_close,
+ .trigger = snd_ump_rawmidi_trigger,
+ .drain = snd_ump_rawmidi_drain,
+};
+
+static void snd_ump_endpoint_free(struct snd_rawmidi *rmidi)
+{
+ struct snd_ump_endpoint *ump = rawmidi_to_ump(rmidi);
+ struct snd_ump_block *fb;
+
+ while (!list_empty(&ump->block_list)) {
+ fb = list_first_entry(&ump->block_list, struct snd_ump_block,
+ list);
+ list_del(&fb->list);
+ if (fb->private_free)
+ fb->private_free(fb);
+ kfree(fb);
+ }
+
+ if (ump->private_free)
+ ump->private_free(ump);
+
+#if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI)
+ snd_ump_convert_free(ump);
+#endif
+}
+
+/**
+ * snd_ump_endpoint_new - create a UMP Endpoint object
+ * @card: the card instance
+ * @id: the id string for rawmidi
+ * @device: the device index for rawmidi
+ * @output: 1 for enabling output
+ * @input: 1 for enabling input
+ * @ump_ret: the pointer to store the new UMP instance
+ *
+ * Creates a new UMP Endpoint object. A UMP Endpoint is tied with one rawmidi
+ * instance with one input and/or one output rawmidi stream (either uni-
+ * or bi-directional). A UMP Endpoint may contain one or multiple UMP Blocks
+ * that consist of one or multiple UMP Groups.
+ *
+ * Use snd_rawmidi_set_ops() to set the operators to the new instance.
+ * Unlike snd_rawmidi_new(), this function sets up the info_flags by itself
+ * depending on the given @output and @input.
+ *
+ * The device has SNDRV_RAWMIDI_INFO_UMP flag set and a different device
+ * file ("umpCxDx") than a standard MIDI 1.x device ("midiCxDx") is
+ * created.
+ *
+ * Return: Zero if successful, or a negative error code on failure.
+ */
+int snd_ump_endpoint_new(struct snd_card *card, char *id, int device,
+ int output, int input,
+ struct snd_ump_endpoint **ump_ret)
+{
+ unsigned int info_flags = SNDRV_RAWMIDI_INFO_UMP;
+ struct snd_ump_endpoint *ump;
+ int err;
+
+ if (input)
+ info_flags |= SNDRV_RAWMIDI_INFO_INPUT;
+ if (output)
+ info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT;
+ if (input && output)
+ info_flags |= SNDRV_RAWMIDI_INFO_DUPLEX;
+
+ ump = kzalloc(sizeof(*ump), GFP_KERNEL);
+ if (!ump)
+ return -ENOMEM;
+ INIT_LIST_HEAD(&ump->block_list);
+ mutex_init(&ump->open_mutex);
+#if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI)
+ spin_lock_init(&ump->legacy_locks[0]);
+ spin_lock_init(&ump->legacy_locks[1]);
+#endif
+ err = snd_rawmidi_init(&ump->core, card, id, device,
+ output, input, info_flags);
+ if (err < 0) {
+ snd_rawmidi_free(&ump->core);
+ return err;
+ }
+
+ ump->info.card = card->number;
+ ump->info.device = device;
+
+ ump->core.private_free = snd_ump_endpoint_free;
+ ump->core.ops = &snd_ump_rawmidi_ops;
+ if (input)
+ snd_rawmidi_set_ops(&ump->core, SNDRV_RAWMIDI_STREAM_INPUT,
+ &snd_ump_rawmidi_input_ops);
+ if (output)
+ snd_rawmidi_set_ops(&ump->core, SNDRV_RAWMIDI_STREAM_OUTPUT,
+ &snd_ump_rawmidi_output_ops);
+
+ ump_dbg(ump, "Created a UMP EP #%d (%s)\n", device, id);
+ *ump_ret = ump;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_ump_endpoint_new);
+
+/*
+ * Device register / unregister hooks;
+ * do nothing, placeholders for avoiding the default rawmidi handling
+ */
+
+#if IS_ENABLED(CONFIG_SND_SEQUENCER)
+static void snd_ump_dev_seq_free(struct snd_seq_device *device)
+{
+ struct snd_ump_endpoint *ump = device->private_data;
+
+ ump->seq_dev = NULL;
+}
+#endif
+
+static int snd_ump_dev_register(struct snd_rawmidi *rmidi)
+{
+#if IS_ENABLED(CONFIG_SND_SEQUENCER)
+ struct snd_ump_endpoint *ump = rawmidi_to_ump(rmidi);
+ int err;
+
+ err = snd_seq_device_new(ump->core.card, ump->core.device,
+ SNDRV_SEQ_DEV_ID_UMP, 0, &ump->seq_dev);
+ if (err < 0)
+ return err;
+ ump->seq_dev->private_data = ump;
+ ump->seq_dev->private_free = snd_ump_dev_seq_free;
+ snd_device_register(ump->core.card, ump->seq_dev);
+#endif
+ return 0;
+}
+
+static int snd_ump_dev_unregister(struct snd_rawmidi *rmidi)
+{
+ return 0;
+}
+
+static struct snd_ump_block *
+snd_ump_get_block(struct snd_ump_endpoint *ump, unsigned char id)
+{
+ struct snd_ump_block *fb;
+
+ list_for_each_entry(fb, &ump->block_list, list) {
+ if (fb->info.block_id == id)
+ return fb;
+ }
+ return NULL;
+}
+
+/*
+ * rawmidi ops for UMP endpoint
+ */
+static int snd_ump_rawmidi_open(struct snd_rawmidi_substream *substream)
+{
+ struct snd_ump_endpoint *ump = rawmidi_to_ump(substream->rmidi);
+ int dir = substream->stream;
+ int err;
+
+ if (ump->substreams[dir])
+ return -EBUSY;
+ err = ump->ops->open(ump, dir);
+ if (err < 0)
+ return err;
+ ump->substreams[dir] = substream;
+ return 0;
+}
+
+static int snd_ump_rawmidi_close(struct snd_rawmidi_substream *substream)
+{
+ struct snd_ump_endpoint *ump = rawmidi_to_ump(substream->rmidi);
+ int dir = substream->stream;
+
+ ump->substreams[dir] = NULL;
+ ump->ops->close(ump, dir);
+ return 0;
+}
+
+static void snd_ump_rawmidi_trigger(struct snd_rawmidi_substream *substream,
+ int up)
+{
+ struct snd_ump_endpoint *ump = rawmidi_to_ump(substream->rmidi);
+ int dir = substream->stream;
+
+ ump->ops->trigger(ump, dir, up);
+}
+
+static void snd_ump_rawmidi_drain(struct snd_rawmidi_substream *substream)
+{
+ struct snd_ump_endpoint *ump = rawmidi_to_ump(substream->rmidi);
+
+ if (ump->ops->drain)
+ ump->ops->drain(ump, SNDRV_RAWMIDI_STREAM_OUTPUT);
+}
+
+/* number of 32bit words per message type */
+static unsigned char ump_packet_words[0x10] = {
+ 1, 1, 1, 2, 2, 4, 1, 1, 2, 2, 2, 3, 3, 4, 4, 4
+};
+
+/* parse the UMP packet data;
+ * the data is copied onto ump->input_buf[].
+ * When a full packet is completed, returns the number of words (from 1 to 4).
+ * OTOH, if the packet is incomplete, returns 0.
+ */
+static int snd_ump_receive_ump_val(struct snd_ump_endpoint *ump, u32 val)
+{
+ int words;
+
+ if (!ump->input_pending)
+ ump->input_pending = ump_packet_words[ump_message_type(val)];
+
+ ump->input_buf[ump->input_buf_head++] = val;
+ ump->input_pending--;
+ if (!ump->input_pending) {
+ words = ump->input_buf_head;
+ ump->input_buf_head = 0;
+ return words;
+ }
+ return 0;
+}
+
+/**
+ * snd_ump_receive - transfer UMP packets from the device
+ * @ump: the UMP endpoint
+ * @buffer: the buffer pointer to transfer
+ * @count: byte size to transfer
+ *
+ * Called from the driver to submit the received UMP packets from the device
+ * to user-space. It's essentially a wrapper of rawmidi_receive().
+ * The data to receive is in CPU-native endianness.
+ */
+int snd_ump_receive(struct snd_ump_endpoint *ump, const u32 *buffer, int count)
+{
+ struct snd_rawmidi_substream *substream;
+ const u32 *p = buffer;
+ int n, words = count >> 2;
+
+ while (words--) {
+ n = snd_ump_receive_ump_val(ump, *p++);
+ if (!n)
+ continue;
+#if IS_ENABLED(CONFIG_SND_SEQUENCER)
+ if (ump->seq_ops)
+ ump->seq_ops->input_receive(ump, ump->input_buf, n);
+#endif
+ process_legacy_input(ump, ump->input_buf, n);
+ }
+
+ substream = ump->substreams[SNDRV_RAWMIDI_STREAM_INPUT];
+ if (!substream)
+ return 0;
+ return snd_rawmidi_receive(substream, (const char *)buffer, count);
+}
+EXPORT_SYMBOL_GPL(snd_ump_receive);
+
+/**
+ * snd_ump_transmit - transmit UMP packets
+ * @ump: the UMP endpoint
+ * @buffer: the buffer pointer to transfer
+ * @count: byte size to transfer
+ *
+ * Called from the driver to obtain the UMP packets from user-space to the
+ * device. It's essentially a wrapper of rawmidi_transmit().
+ * The data to transmit is in CPU-native endianness.
+ */
+int snd_ump_transmit(struct snd_ump_endpoint *ump, u32 *buffer, int count)
+{
+ struct snd_rawmidi_substream *substream =
+ ump->substreams[SNDRV_RAWMIDI_STREAM_OUTPUT];
+ int err;
+
+ if (!substream)
+ return -ENODEV;
+ err = snd_rawmidi_transmit(substream, (char *)buffer, count);
+ /* received either data or an error? */
+ if (err)
+ return err;
+ return process_legacy_output(ump, buffer, count);
+}
+EXPORT_SYMBOL_GPL(snd_ump_transmit);
+
+/**
+ * snd_ump_block_new - Create a UMP block
+ * @ump: UMP object
+ * @blk: block ID number to create
+ * @direction: direction (in/out/bidirection)
+ * @first_group: the first group ID (0-based)
+ * @num_groups: the number of groups in this block
+ * @blk_ret: the pointer to store the resultant block object
+ */
+int snd_ump_block_new(struct snd_ump_endpoint *ump, unsigned int blk,
+ unsigned int direction, unsigned int first_group,
+ unsigned int num_groups, struct snd_ump_block **blk_ret)
+{
+ struct snd_ump_block *fb, *p;
+
+ if (blk < 0 || blk >= SNDRV_UMP_MAX_BLOCKS)
+ return -EINVAL;
+
+ if (snd_ump_get_block(ump, blk))
+ return -EBUSY;
+
+ fb = kzalloc(sizeof(*fb), GFP_KERNEL);
+ if (!fb)
+ return -ENOMEM;
+
+ fb->ump = ump;
+ fb->info.card = ump->info.card;
+ fb->info.device = ump->info.device;
+ fb->info.block_id = blk;
+ if (blk >= ump->info.num_blocks)
+ ump->info.num_blocks = blk + 1;
+ fb->info.direction = direction;
+ fb->info.active = 1;
+ fb->info.first_group = first_group;
+ fb->info.num_groups = num_groups;
+ /* fill the default name, may be overwritten to a better name */
+ snprintf(fb->info.name, sizeof(fb->info.name), "Group %d-%d",
+ first_group + 1, first_group + num_groups);
+
+ /* put the entry in the ordered list */
+ list_for_each_entry(p, &ump->block_list, list) {
+ if (p->info.block_id > blk) {
+ list_add_tail(&fb->list, &p->list);
+ goto added;
+ }
+ }
+ list_add_tail(&fb->list, &ump->block_list);
+
+ added:
+ ump_dbg(ump, "Created a UMP Block #%d (%s)\n", blk, fb->info.name);
+ *blk_ret = fb;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_ump_block_new);
+
+static int snd_ump_ioctl_block(struct snd_ump_endpoint *ump,
+ struct snd_ump_block_info __user *argp)
+{
+ struct snd_ump_block *fb;
+ unsigned char id;
+
+ if (get_user(id, &argp->block_id))
+ return -EFAULT;
+ fb = snd_ump_get_block(ump, id);
+ if (!fb)
+ return -ENOENT;
+ if (copy_to_user(argp, &fb->info, sizeof(fb->info)))
+ return -EFAULT;
+ return 0;
+}
+
+/*
+ * Handle UMP-specific ioctls; called from snd_rawmidi_ioctl()
+ */
+static long snd_ump_ioctl(struct snd_rawmidi *rmidi, unsigned int cmd,
+ void __user *argp)
+{
+ struct snd_ump_endpoint *ump = rawmidi_to_ump(rmidi);
+
+ switch (cmd) {
+ case SNDRV_UMP_IOCTL_ENDPOINT_INFO:
+ if (copy_to_user(argp, &ump->info, sizeof(ump->info)))
+ return -EFAULT;
+ return 0;
+ case SNDRV_UMP_IOCTL_BLOCK_INFO:
+ return snd_ump_ioctl_block(ump, argp);
+ default:
+ ump_dbg(ump, "rawmidi: unknown command = 0x%x\n", cmd);
+ return -ENOTTY;
+ }
+}
+
+static const char *ump_direction_string(int dir)
+{
+ switch (dir) {
+ case SNDRV_UMP_DIR_INPUT:
+ return "input";
+ case SNDRV_UMP_DIR_OUTPUT:
+ return "output";
+ case SNDRV_UMP_DIR_BIDIRECTION:
+ return "bidirection";
+ default:
+ return "unknown";
+ }
+}
+
+/* Additional proc file output */
+static void snd_ump_proc_read(struct snd_info_entry *entry,
+ struct snd_info_buffer *buffer)
+{
+ struct snd_rawmidi *rmidi = entry->private_data;
+ struct snd_ump_endpoint *ump = rawmidi_to_ump(rmidi);
+ struct snd_ump_block *fb;
+
+ snd_iprintf(buffer, "EP Name: %s\n", ump->info.name);
+ snd_iprintf(buffer, "EP Product ID: %s\n", ump->info.product_id);
+ snd_iprintf(buffer, "UMP Version: 0x%04x\n", ump->info.version);
+ snd_iprintf(buffer, "Protocol Caps: 0x%08x\n", ump->info.protocol_caps);
+ snd_iprintf(buffer, "Protocol: 0x%08x\n", ump->info.protocol);
+ snd_iprintf(buffer, "Num Blocks: %d\n\n", ump->info.num_blocks);
+
+ list_for_each_entry(fb, &ump->block_list, list) {
+ snd_iprintf(buffer, "Block %d (%s)\n", fb->info.block_id,
+ fb->info.name);
+ snd_iprintf(buffer, " Direction: %s\n",
+ ump_direction_string(fb->info.direction));
+ snd_iprintf(buffer, " Active: %s\n",
+ fb->info.active ? "Yes" : "No");
+ snd_iprintf(buffer, " Groups: %d-%d\n",
+ fb->info.first_group + 1,
+ fb->info.first_group + fb->info.num_groups);
+ snd_iprintf(buffer, " Is MIDI1: %s%s\n",
+ (fb->info.flags & SNDRV_UMP_BLOCK_IS_MIDI1) ? "Yes" : "No",
+ (fb->info.flags & SNDRV_UMP_BLOCK_IS_LOWSPEED) ? " (Low Speed)" : "");
+ snd_iprintf(buffer, "\n");
+ }
+}
+
+#if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI)
+/*
+ * Legacy rawmidi support
+ */
+static int snd_ump_legacy_open(struct snd_rawmidi_substream *substream)
+{
+ struct snd_ump_endpoint *ump = substream->rmidi->private_data;
+ int dir = substream->stream;
+ int group = substream->number;
+ int err;
+
+ mutex_lock(&ump->open_mutex);
+ if (ump->legacy_substreams[dir][group]) {
+ err = -EBUSY;
+ goto unlock;
+ }
+ if (dir == SNDRV_RAWMIDI_STREAM_OUTPUT) {
+ if (!ump->legacy_out_opens) {
+ err = snd_rawmidi_kernel_open(&ump->core, 0,
+ SNDRV_RAWMIDI_LFLG_OUTPUT |
+ SNDRV_RAWMIDI_LFLG_APPEND,
+ &ump->legacy_out_rfile);
+ if (err < 0)
+ goto unlock;
+ }
+ ump->legacy_out_opens++;
+ snd_ump_reset_convert_to_ump(ump, group);
+ }
+ spin_lock_irq(&ump->legacy_locks[dir]);
+ ump->legacy_substreams[dir][group] = substream;
+ spin_unlock_irq(&ump->legacy_locks[dir]);
+ unlock:
+ mutex_unlock(&ump->open_mutex);
+ return 0;
+}
+
+static int snd_ump_legacy_close(struct snd_rawmidi_substream *substream)
+{
+ struct snd_ump_endpoint *ump = substream->rmidi->private_data;
+ int dir = substream->stream;
+ int group = substream->number;
+
+ mutex_lock(&ump->open_mutex);
+ spin_lock_irq(&ump->legacy_locks[dir]);
+ ump->legacy_substreams[dir][group] = NULL;
+ spin_unlock_irq(&ump->legacy_locks[dir]);
+ if (dir == SNDRV_RAWMIDI_STREAM_OUTPUT) {
+ if (!--ump->legacy_out_opens)
+ snd_rawmidi_kernel_release(&ump->legacy_out_rfile);
+ }
+ mutex_unlock(&ump->open_mutex);
+ return 0;
+}
+
+static void snd_ump_legacy_trigger(struct snd_rawmidi_substream *substream,
+ int up)
+{
+ struct snd_ump_endpoint *ump = substream->rmidi->private_data;
+ int dir = substream->stream;
+
+ ump->ops->trigger(ump, dir, up);
+}
+
+static void snd_ump_legacy_drain(struct snd_rawmidi_substream *substream)
+{
+ struct snd_ump_endpoint *ump = substream->rmidi->private_data;
+
+ if (ump->ops->drain)
+ ump->ops->drain(ump, SNDRV_RAWMIDI_STREAM_OUTPUT);
+}
+
+static int snd_ump_legacy_dev_register(struct snd_rawmidi *rmidi)
+{
+ /* dummy, just for avoiding create superfluous seq clients */
+ return 0;
+}
+
+static const struct snd_rawmidi_ops snd_ump_legacy_input_ops = {
+ .open = snd_ump_legacy_open,
+ .close = snd_ump_legacy_close,
+ .trigger = snd_ump_legacy_trigger,
+};
+
+static const struct snd_rawmidi_ops snd_ump_legacy_output_ops = {
+ .open = snd_ump_legacy_open,
+ .close = snd_ump_legacy_close,
+ .trigger = snd_ump_legacy_trigger,
+ .drain = snd_ump_legacy_drain,
+};
+
+static const struct snd_rawmidi_global_ops snd_ump_legacy_ops = {
+ .dev_register = snd_ump_legacy_dev_register,
+};
+
+static int process_legacy_output(struct snd_ump_endpoint *ump,
+ u32 *buffer, int count)
+{
+ struct snd_rawmidi_substream *substream;
+ struct ump_cvt_to_ump *ctx;
+ const int dir = SNDRV_RAWMIDI_STREAM_OUTPUT;
+ unsigned char c;
+ int group, size = 0;
+ unsigned long flags;
+
+ if (!ump->out_cvts || !ump->legacy_out_opens)
+ return 0;
+
+ spin_lock_irqsave(&ump->legacy_locks[dir], flags);
+ for (group = 0; group < SNDRV_UMP_MAX_GROUPS; group++) {
+ substream = ump->legacy_substreams[dir][group];
+ if (!substream)
+ continue;
+ ctx = &ump->out_cvts[group];
+ while (!ctx->ump_bytes &&
+ snd_rawmidi_transmit(substream, &c, 1) > 0)
+ snd_ump_convert_to_ump(ump, group, c);
+ if (ctx->ump_bytes && ctx->ump_bytes <= count) {
+ size = ctx->ump_bytes;
+ memcpy(buffer, ctx->ump, size);
+ ctx->ump_bytes = 0;
+ break;
+ }
+ }
+ spin_unlock_irqrestore(&ump->legacy_locks[dir], flags);
+ return size;
+}
+
+static void process_legacy_input(struct snd_ump_endpoint *ump, const u32 *src,
+ int words)
+{
+ struct snd_rawmidi_substream *substream;
+ unsigned char buf[16];
+ unsigned char group;
+ unsigned long flags;
+ const int dir = SNDRV_RAWMIDI_STREAM_INPUT;
+ int size;
+
+ size = snd_ump_convert_from_ump(ump, src, buf, &group);
+ if (size <= 0)
+ return;
+ spin_lock_irqsave(&ump->legacy_locks[dir], flags);
+ substream = ump->legacy_substreams[dir][group];
+ if (substream)
+ snd_rawmidi_receive(substream, buf, size);
+ spin_unlock_irqrestore(&ump->legacy_locks[dir], flags);
+}
+
+int snd_ump_attach_legacy_rawmidi(struct snd_ump_endpoint *ump,
+ char *id, int device)
+{
+ struct snd_rawmidi *rmidi;
+ bool input, output;
+ int err;
+
+ err = snd_ump_convert_init(ump);
+ if (err < 0)
+ return err;
+
+ input = ump->core.info_flags & SNDRV_RAWMIDI_INFO_INPUT;
+ output = ump->core.info_flags & SNDRV_RAWMIDI_INFO_OUTPUT;
+ err = snd_rawmidi_new(ump->core.card, id, device,
+ output ? 16 : 0, input ? 16 : 0,
+ &rmidi);
+ if (err < 0) {
+ snd_ump_convert_free(ump);
+ return err;
+ }
+
+ if (input)
+ snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT,
+ &snd_ump_legacy_input_ops);
+ if (output)
+ snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT,
+ &snd_ump_legacy_output_ops);
+ rmidi->info_flags = ump->core.info_flags & ~SNDRV_RAWMIDI_INFO_UMP;
+ rmidi->ops = &snd_ump_legacy_ops;
+ rmidi->private_data = ump;
+ ump->legacy_rmidi = rmidi;
+ ump_dbg(ump, "Created a legacy rawmidi #%d (%s)\n", device, id);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_ump_attach_legacy_rawmidi);
+#endif /* CONFIG_SND_UMP_LEGACY_RAWMIDI */
+
+MODULE_DESCRIPTION("Universal MIDI Packet (UMP) Core Driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/core/ump_convert.c b/sound/core/ump_convert.c
new file mode 100644
index 000000000000..cb7c2f959a27
--- /dev/null
+++ b/sound/core/ump_convert.c
@@ -0,0 +1,520 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Helpers for UMP <-> MIDI 1.0 byte stream conversion
+ */
+
+#include <linux/module.h>
+#include <linux/export.h>
+#include <sound/core.h>
+#include <sound/asound.h>
+#include <sound/ump.h>
+#include "ump_convert.h"
+
+/*
+ * Upgrade / downgrade value bits
+ */
+static u8 downscale_32_to_7bit(u32 src)
+{
+ return src >> 25;
+}
+
+static u16 downscale_32_to_14bit(u32 src)
+{
+ return src >> 18;
+}
+
+static u8 downscale_16_to_7bit(u16 src)
+{
+ return src >> 9;
+}
+
+static u16 upscale_7_to_16bit(u8 src)
+{
+ u16 val, repeat;
+
+ val = (u16)src << 9;
+ if (src <= 0x40)
+ return val;
+ repeat = src & 0x3f;
+ return val | (repeat << 3) | (repeat >> 3);
+}
+
+static u32 upscale_7_to_32bit(u8 src)
+{
+ u32 val, repeat;
+
+ val = src << 25;
+ if (src <= 0x40)
+ return val;
+ repeat = src & 0x3f;
+ return val | (repeat << 19) | (repeat << 13) |
+ (repeat << 7) | (repeat << 1) | (repeat >> 5);
+}
+
+static u32 upscale_14_to_32bit(u16 src)
+{
+ u32 val, repeat;
+
+ val = src << 18;
+ if (src <= 0x2000)
+ return val;
+ repeat = src & 0x1fff;
+ return val | (repeat << 5) | (repeat >> 8);
+}
+
+/*
+ * UMP -> MIDI 1 byte stream conversion
+ */
+/* convert a UMP System message to MIDI 1.0 byte stream */
+static int cvt_ump_system_to_legacy(u32 data, unsigned char *buf)
+{
+ buf[0] = ump_message_status_channel(data);
+ switch (ump_message_status_code(data)) {
+ case UMP_SYSTEM_STATUS_MIDI_TIME_CODE:
+ case UMP_SYSTEM_STATUS_SONG_SELECT:
+ buf[1] = (data >> 8) & 0x7f;
+ return 1;
+ case UMP_SYSTEM_STATUS_SONG_POSITION:
+ buf[1] = (data >> 8) & 0x7f;
+ buf[2] = data & 0x7f;
+ return 3;
+ default:
+ return 1;
+ }
+}
+
+/* convert a UMP MIDI 1.0 Channel Voice message to MIDI 1.0 byte stream */
+static int cvt_ump_midi1_to_legacy(u32 data, unsigned char *buf)
+{
+ buf[0] = ump_message_status_channel(data);
+ buf[1] = (data >> 8) & 0xff;
+ switch (ump_message_status_code(data)) {
+ case UMP_MSG_STATUS_PROGRAM:
+ case UMP_MSG_STATUS_CHANNEL_PRESSURE:
+ return 2;
+ default:
+ buf[2] = data & 0xff;
+ return 3;
+ }
+}
+
+/* convert a UMP MIDI 2.0 Channel Voice message to MIDI 1.0 byte stream */
+static int cvt_ump_midi2_to_legacy(const union snd_ump_midi2_msg *midi2,
+ unsigned char *buf)
+{
+ unsigned char status = midi2->note.status;
+ unsigned char channel = midi2->note.channel;
+ u16 v;
+
+ buf[0] = (status << 4) | channel;
+ switch (status) {
+ case UMP_MSG_STATUS_NOTE_OFF:
+ case UMP_MSG_STATUS_NOTE_ON:
+ buf[1] = midi2->note.note;
+ buf[2] = downscale_16_to_7bit(midi2->note.velocity);
+ if (status == UMP_MSG_STATUS_NOTE_ON && !buf[2])
+ buf[2] = 1;
+ return 3;
+ case UMP_MSG_STATUS_POLY_PRESSURE:
+ buf[1] = midi2->paf.note;
+ buf[2] = downscale_32_to_7bit(midi2->paf.data);
+ return 3;
+ case UMP_MSG_STATUS_CC:
+ buf[1] = midi2->cc.index;
+ buf[2] = downscale_32_to_7bit(midi2->cc.data);
+ return 3;
+ case UMP_MSG_STATUS_CHANNEL_PRESSURE:
+ buf[1] = downscale_32_to_7bit(midi2->caf.data);
+ return 2;
+ case UMP_MSG_STATUS_PROGRAM:
+ if (midi2->pg.bank_valid) {
+ buf[0] = channel | (UMP_MSG_STATUS_CC << 4);
+ buf[1] = UMP_CC_BANK_SELECT;
+ buf[2] = midi2->pg.bank_msb;
+ buf[3] = channel | (UMP_MSG_STATUS_CC << 4);
+ buf[4] = UMP_CC_BANK_SELECT_LSB;
+ buf[5] = midi2->pg.bank_lsb;
+ buf[6] = channel | (UMP_MSG_STATUS_PROGRAM << 4);
+ buf[7] = midi2->pg.program;
+ return 8;
+ }
+ buf[1] = midi2->pg.program;
+ return 2;
+ case UMP_MSG_STATUS_PITCH_BEND:
+ v = downscale_32_to_14bit(midi2->pb.data);
+ buf[1] = v & 0x7f;
+ buf[2] = v >> 7;
+ return 3;
+ case UMP_MSG_STATUS_RPN:
+ case UMP_MSG_STATUS_NRPN:
+ buf[0] = channel | (UMP_MSG_STATUS_CC << 4);
+ buf[1] = status == UMP_MSG_STATUS_RPN ? UMP_CC_RPN_MSB : UMP_CC_NRPN_MSB;
+ buf[2] = midi2->rpn.bank;
+ buf[3] = buf[0];
+ buf[4] = status == UMP_MSG_STATUS_RPN ? UMP_CC_RPN_LSB : UMP_CC_NRPN_LSB;
+ buf[5] = midi2->rpn.index;
+ buf[6] = buf[0];
+ buf[7] = UMP_CC_DATA;
+ v = downscale_32_to_14bit(midi2->rpn.data);
+ buf[8] = v >> 7;
+ buf[9] = buf[0];
+ buf[10] = UMP_CC_DATA_LSB;
+ buf[11] = v & 0x7f;
+ return 12;
+ default:
+ return 0;
+ }
+}
+
+/* convert a UMP 7-bit SysEx message to MIDI 1.0 byte stream */
+static int cvt_ump_sysex7_to_legacy(const u32 *data, unsigned char *buf)
+{
+ unsigned char status;
+ unsigned char bytes;
+ int size, offset;
+
+ status = ump_sysex_message_status(*data);
+ if (status > UMP_SYSEX_STATUS_END)
+ return 0; // unsupported, skip
+ bytes = ump_sysex_message_length(*data);
+ if (bytes > 6)
+ return 0; // skip
+
+ size = 0;
+ if (status == UMP_SYSEX_STATUS_SINGLE ||
+ status == UMP_SYSEX_STATUS_START) {
+ buf[0] = UMP_MIDI1_MSG_SYSEX_START;
+ size = 1;
+ }
+
+ offset = 8;
+ for (; bytes; bytes--, size++) {
+ buf[size] = (*data >> offset) & 0x7f;
+ if (!offset) {
+ offset = 24;
+ data++;
+ } else {
+ offset -= 8;
+ }
+ }
+
+ if (status == UMP_SYSEX_STATUS_SINGLE ||
+ status == UMP_SYSEX_STATUS_END)
+ buf[size++] = UMP_MIDI1_MSG_SYSEX_END;
+
+ return size;
+}
+
+/* convert from a UMP packet @data to MIDI 1.0 bytes at @buf;
+ * the target group is stored at @group_ret,
+ * returns the number of bytes of MIDI 1.0 stream
+ */
+int snd_ump_convert_from_ump(struct snd_ump_endpoint *ump,
+ const u32 *data,
+ unsigned char *buf,
+ unsigned char *group_ret)
+{
+ *group_ret = ump_message_group(*data);
+
+ switch (ump_message_type(*data)) {
+ case UMP_MSG_TYPE_SYSTEM:
+ return cvt_ump_system_to_legacy(*data, buf);
+ case UMP_MSG_TYPE_MIDI1_CHANNEL_VOICE:
+ return cvt_ump_midi1_to_legacy(*data, buf);
+ case UMP_MSG_TYPE_MIDI2_CHANNEL_VOICE:
+ return cvt_ump_midi2_to_legacy((const union snd_ump_midi2_msg *)data,
+ buf);
+ case UMP_MSG_TYPE_DATA:
+ return cvt_ump_sysex7_to_legacy(data, buf);
+ }
+
+ return 0;
+}
+
+/*
+ * MIDI 1 byte stream -> UMP conversion
+ */
+/* convert MIDI 1.0 SysEx to a UMP packet */
+static int cvt_legacy_sysex_to_ump(struct ump_cvt_to_ump *cvt,
+ unsigned char group, u32 *data, bool finish)
+{
+ unsigned char status;
+ bool start = cvt->in_sysex == 1;
+ int i, offset;
+
+ if (start && finish)
+ status = UMP_SYSEX_STATUS_SINGLE;
+ else if (start)
+ status = UMP_SYSEX_STATUS_START;
+ else if (finish)
+ status = UMP_SYSEX_STATUS_END;
+ else
+ status = UMP_SYSEX_STATUS_CONTINUE;
+ *data = ump_compose(UMP_MSG_TYPE_DATA, group, status, cvt->len);
+ offset = 8;
+ for (i = 0; i < cvt->len; i++) {
+ *data |= cvt->buf[i] << offset;
+ if (!offset) {
+ offset = 24;
+ data++;
+ } else
+ offset -= 8;
+ }
+ cvt->len = 0;
+ if (finish)
+ cvt->in_sysex = 0;
+ else
+ cvt->in_sysex++;
+ return 8;
+}
+
+/* convert to a UMP System message */
+static int cvt_legacy_system_to_ump(struct ump_cvt_to_ump *cvt,
+ unsigned char group, u32 *data)
+{
+ data[0] = ump_compose(UMP_MSG_TYPE_SYSTEM, group, 0, cvt->buf[0]);
+ if (cvt->cmd_bytes > 1)
+ data[0] |= cvt->buf[1] << 8;
+ if (cvt->cmd_bytes > 2)
+ data[0] |= cvt->buf[2];
+ return 4;
+}
+
+static void fill_rpn(struct ump_cvt_to_ump_bank *cc,
+ union snd_ump_midi2_msg *midi2)
+{
+ if (cc->rpn_set) {
+ midi2->rpn.status = UMP_MSG_STATUS_RPN;
+ midi2->rpn.bank = cc->cc_rpn_msb;
+ midi2->rpn.index = cc->cc_rpn_lsb;
+ cc->rpn_set = 0;
+ cc->cc_rpn_msb = cc->cc_rpn_lsb = 0;
+ } else {
+ midi2->rpn.status = UMP_MSG_STATUS_NRPN;
+ midi2->rpn.bank = cc->cc_nrpn_msb;
+ midi2->rpn.index = cc->cc_nrpn_lsb;
+ cc->nrpn_set = 0;
+ cc->cc_nrpn_msb = cc->cc_nrpn_lsb = 0;
+ }
+ midi2->rpn.data = upscale_14_to_32bit((cc->cc_data_msb << 7) |
+ cc->cc_data_lsb);
+ cc->cc_data_msb = cc->cc_data_lsb = 0;
+}
+
+/* convert to a MIDI 1.0 Channel Voice message */
+static int cvt_legacy_cmd_to_ump(struct snd_ump_endpoint *ump,
+ struct ump_cvt_to_ump *cvt,
+ unsigned char group, u32 *data,
+ unsigned char bytes)
+{
+ const unsigned char *buf = cvt->buf;
+ struct ump_cvt_to_ump_bank *cc;
+ union snd_ump_midi2_msg *midi2 = (union snd_ump_midi2_msg *)data;
+ unsigned char status, channel;
+
+ BUILD_BUG_ON(sizeof(union snd_ump_midi1_msg) != 4);
+ BUILD_BUG_ON(sizeof(union snd_ump_midi2_msg) != 8);
+
+ /* for MIDI 1.0 UMP, it's easy, just pack it into UMP */
+ if (ump->info.protocol & SNDRV_UMP_EP_INFO_PROTO_MIDI1) {
+ data[0] = ump_compose(UMP_MSG_TYPE_MIDI1_CHANNEL_VOICE,
+ group, 0, buf[0]);
+ data[0] |= buf[1] << 8;
+ if (bytes > 2)
+ data[0] |= buf[2];
+ return 4;
+ }
+
+ status = *buf >> 4;
+ channel = *buf & 0x0f;
+ cc = &cvt->bank[channel];
+
+ /* special handling: treat note-on with 0 velocity as note-off */
+ if (status == UMP_MSG_STATUS_NOTE_ON && !buf[2])
+ status = UMP_MSG_STATUS_NOTE_OFF;
+
+ /* initialize the packet */
+ data[0] = ump_compose(UMP_MSG_TYPE_MIDI2_CHANNEL_VOICE,
+ group, status, channel);
+ data[1] = 0;
+
+ switch (status) {
+ case UMP_MSG_STATUS_NOTE_ON:
+ if (!buf[2])
+ status = UMP_MSG_STATUS_NOTE_OFF;
+ fallthrough;
+ case UMP_MSG_STATUS_NOTE_OFF:
+ midi2->note.note = buf[1];
+ midi2->note.velocity = upscale_7_to_16bit(buf[2]);
+ break;
+ case UMP_MSG_STATUS_POLY_PRESSURE:
+ midi2->paf.note = buf[1];
+ midi2->paf.data = upscale_7_to_32bit(buf[2]);
+ break;
+ case UMP_MSG_STATUS_CC:
+ switch (buf[1]) {
+ case UMP_CC_RPN_MSB:
+ cc->rpn_set = 1;
+ cc->cc_rpn_msb = buf[2];
+ return 0; // skip
+ case UMP_CC_RPN_LSB:
+ cc->rpn_set = 1;
+ cc->cc_rpn_lsb = buf[2];
+ return 0; // skip
+ case UMP_CC_NRPN_MSB:
+ cc->nrpn_set = 1;
+ cc->cc_nrpn_msb = buf[2];
+ return 0; // skip
+ case UMP_CC_NRPN_LSB:
+ cc->nrpn_set = 1;
+ cc->cc_nrpn_lsb = buf[2];
+ return 0; // skip
+ case UMP_CC_DATA:
+ cc->cc_data_msb = buf[2];
+ return 0; // skip
+ case UMP_CC_BANK_SELECT:
+ cc->bank_set = 1;
+ cc->cc_bank_msb = buf[2];
+ return 0; // skip
+ case UMP_CC_BANK_SELECT_LSB:
+ cc->bank_set = 1;
+ cc->cc_bank_lsb = buf[2];
+ return 0; // skip
+ case UMP_CC_DATA_LSB:
+ cc->cc_data_lsb = buf[2];
+ if (cc->rpn_set || cc->nrpn_set)
+ fill_rpn(cc, midi2);
+ else
+ return 0; // skip
+ break;
+ default:
+ midi2->cc.index = buf[1];
+ midi2->cc.data = upscale_7_to_32bit(buf[2]);
+ break;
+ }
+ break;
+ case UMP_MSG_STATUS_PROGRAM:
+ midi2->pg.program = buf[1];
+ if (cc->bank_set) {
+ midi2->pg.bank_valid = 1;
+ midi2->pg.bank_msb = cc->cc_bank_msb;
+ midi2->pg.bank_lsb = cc->cc_bank_lsb;
+ cc->bank_set = 0;
+ cc->cc_bank_msb = cc->cc_bank_lsb = 0;
+ }
+ break;
+ case UMP_MSG_STATUS_CHANNEL_PRESSURE:
+ midi2->caf.data = upscale_7_to_32bit(buf[1]);
+ break;
+ case UMP_MSG_STATUS_PITCH_BEND:
+ midi2->pb.data = upscale_14_to_32bit(buf[1] | (buf[2] << 7));
+ break;
+ default:
+ return 0;
+ }
+
+ return 8;
+}
+
+static int do_convert_to_ump(struct snd_ump_endpoint *ump,
+ unsigned char group, unsigned char c, u32 *data)
+{
+ /* bytes for 0x80-0xf0 */
+ static unsigned char cmd_bytes[8] = {
+ 3, 3, 3, 3, 2, 2, 3, 0
+ };
+ /* bytes for 0xf0-0xff */
+ static unsigned char system_bytes[16] = {
+ 0, 2, 3, 2, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1
+ };
+ struct ump_cvt_to_ump *cvt = &ump->out_cvts[group];
+ unsigned char bytes;
+
+ if (c == UMP_MIDI1_MSG_SYSEX_START) {
+ cvt->in_sysex = 1;
+ cvt->len = 0;
+ return 0;
+ }
+ if (c == UMP_MIDI1_MSG_SYSEX_END) {
+ if (!cvt->in_sysex)
+ return 0; /* skip */
+ return cvt_legacy_sysex_to_ump(cvt, group, data, true);
+ }
+
+ if ((c & 0xf0) == UMP_MIDI1_MSG_REALTIME) {
+ bytes = system_bytes[c & 0x0f];
+ if (!bytes)
+ return 0; /* skip */
+ if (bytes == 1) {
+ data[0] = ump_compose(UMP_MSG_TYPE_SYSTEM, group, 0, c);
+ return 4;
+ }
+ cvt->buf[0] = c;
+ cvt->len = 1;
+ cvt->cmd_bytes = bytes;
+ cvt->in_sysex = 0; /* abort SysEx */
+ return 0;
+ }
+
+ if (c & 0x80) {
+ bytes = cmd_bytes[(c >> 8) & 7];
+ cvt->buf[0] = c;
+ cvt->len = 1;
+ cvt->cmd_bytes = bytes;
+ cvt->in_sysex = 0; /* abort SysEx */
+ return 0;
+ }
+
+ if (cvt->in_sysex) {
+ cvt->buf[cvt->len++] = c;
+ if (cvt->len == 6)
+ return cvt_legacy_sysex_to_ump(cvt, group, data, false);
+ return 0;
+ }
+
+ if (!cvt->len)
+ return 0;
+
+ cvt->buf[cvt->len++] = c;
+ if (cvt->len < cvt->cmd_bytes)
+ return 0;
+ cvt->len = 1;
+ if ((cvt->buf[0] & 0xf0) == UMP_MIDI1_MSG_REALTIME)
+ return cvt_legacy_system_to_ump(cvt, group, data);
+ return cvt_legacy_cmd_to_ump(ump, cvt, group, data, cvt->cmd_bytes);
+}
+
+/* feed a MIDI 1.0 byte @c and convert to a UMP packet;
+ * the target group is @group,
+ * the result is stored in out_cvts[group].ump[] and out_cvts[group].ump_bytes
+ */
+void snd_ump_convert_to_ump(struct snd_ump_endpoint *ump,
+ unsigned char group, unsigned char c)
+{
+ struct ump_cvt_to_ump *cvt = &ump->out_cvts[group];
+
+ cvt->ump_bytes = do_convert_to_ump(ump, group, c, cvt->ump);
+}
+
+/* reset the converter context, called at each open */
+void snd_ump_reset_convert_to_ump(struct snd_ump_endpoint *ump,
+ unsigned char group)
+{
+ memset(&ump->out_cvts[group], 0, sizeof(*ump->out_cvts));
+}
+
+/* initialize converters */
+int snd_ump_convert_init(struct snd_ump_endpoint *ump)
+{
+ ump->out_cvts = kcalloc(16, sizeof(*ump->out_cvts), GFP_KERNEL);
+ if (!ump->out_cvts)
+ return -ENOMEM;
+ return 0;
+}
+
+/* release resources */
+void snd_ump_convert_free(struct snd_ump_endpoint *ump)
+{
+ kfree(ump->out_cvts);
+ ump->out_cvts = NULL;
+}
diff --git a/sound/core/ump_convert.h b/sound/core/ump_convert.h
new file mode 100644
index 000000000000..bbfe96084779
--- /dev/null
+++ b/sound/core/ump_convert.h
@@ -0,0 +1,43 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef __UMP_CONVERT_H
+#define __UMP_CONVERT_H
+
+#include <sound/ump_msg.h>
+
+/* context for converting from legacy control messages to UMP packet */
+struct ump_cvt_to_ump_bank {
+ bool rpn_set;
+ bool nrpn_set;
+ bool bank_set;
+ unsigned char cc_rpn_msb, cc_rpn_lsb;
+ unsigned char cc_nrpn_msb, cc_nrpn_lsb;
+ unsigned char cc_data_msb, cc_data_lsb;
+ unsigned char cc_bank_msb, cc_bank_lsb;
+};
+
+/* context for converting from MIDI1 byte stream to UMP packet */
+struct ump_cvt_to_ump {
+ /* MIDI1 intermediate buffer */
+ unsigned char buf[4];
+ int len;
+ int cmd_bytes;
+
+ /* UMP output packet */
+ u32 ump[4];
+ int ump_bytes;
+
+ /* various status */
+ unsigned int in_sysex;
+ struct ump_cvt_to_ump_bank bank[16]; /* per channel */
+};
+
+int snd_ump_convert_init(struct snd_ump_endpoint *ump);
+void snd_ump_convert_free(struct snd_ump_endpoint *ump);
+int snd_ump_convert_from_ump(struct snd_ump_endpoint *ump,
+ const u32 *data, unsigned char *dst,
+ unsigned char *group_ret);
+void snd_ump_convert_to_ump(struct snd_ump_endpoint *ump,
+ unsigned char group, unsigned char c);
+void snd_ump_reset_convert_to_ump(struct snd_ump_endpoint *ump,
+ unsigned char group);
+#endif /* __UMP_CONVERT_H */