diff options
author | Takashi Iwai <tiwai@suse.de> | 2023-06-26 15:23:15 +0200 |
---|---|---|
committer | Takashi Iwai <tiwai@suse.de> | 2023-06-26 15:23:23 +0200 |
commit | a15b51375684c2bfa6017bb185139477e7a3b96c (patch) | |
tree | 6a4d7b4e7f09b858782cf2e5c0601d9f778cab15 /sound/core | |
parent | ALSA: hda/realtek: Enable mute/micmute LEDs and limit mic boost on EliteBook (diff) | |
parent | ASoC: hdmi-codec: fix channel info for compressed formats (diff) | |
download | linux-a15b51375684c2bfa6017bb185139477e7a3b96c.tar.xz linux-a15b51375684c2bfa6017bb185139477e7a3b96c.zip |
Merge branch 'for-next' into for-linus
Pull the 6.5-devel branch for upstreaming.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
Diffstat (limited to 'sound/core')
30 files changed, 4338 insertions, 314 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/compress_offload.c b/sound/core/compress_offload.c index 243acad89fd3..30f73097447b 100644 --- a/sound/core/compress_offload.c +++ b/sound/core/compress_offload.c @@ -589,7 +589,7 @@ snd_compr_set_params(struct snd_compr_stream *stream, unsigned long arg) struct snd_compr_params *params; int retval; - if (stream->runtime->state == SNDRV_PCM_STATE_OPEN) { + if (stream->runtime->state == SNDRV_PCM_STATE_OPEN || stream->next_track) { /* * we should allow parameter change only when stream has been * opened not in other cases @@ -612,6 +612,9 @@ snd_compr_set_params(struct snd_compr_stream *stream, unsigned long arg) if (retval) goto out; + if (stream->next_track) + goto out; + stream->metadata_set = false; stream->next_track = false; diff --git a/sound/core/control.c b/sound/core/control.c index 82aa1af1d1d8..8386b53acdcd 100644 --- a/sound/core/control.c +++ b/sound/core/control.c @@ -730,12 +730,20 @@ EXPORT_SYMBOL_GPL(snd_ctl_activate_id); * Finds the control with the old id from the card, and replaces the * id with the new one. * + * The function tries to keep the already assigned numid while replacing + * the rest. + * + * Note that this function should be used only in the card initialization + * phase. Calling after the card instantiation may cause issues with + * user-space expecting persistent numids. + * * Return: Zero if successful, or a negative error code on failure. */ int snd_ctl_rename_id(struct snd_card *card, struct snd_ctl_elem_id *src_id, struct snd_ctl_elem_id *dst_id) { struct snd_kcontrol *kctl; + int saved_numid; down_write(&card->controls_rwsem); kctl = snd_ctl_find_id(card, src_id); @@ -743,10 +751,10 @@ int snd_ctl_rename_id(struct snd_card *card, struct snd_ctl_elem_id *src_id, up_write(&card->controls_rwsem); return -ENOENT; } + saved_numid = kctl->id.numid; remove_hash_entries(card, kctl); kctl->id = *dst_id; - kctl->id.numid = card->last_numid + 1; - card->last_numid += kctl->count; + kctl->id.numid = saved_numid; add_hash_entries(card, kctl); up_write(&card->controls_rwsem); return 0; diff --git a/sound/core/control_compat.c b/sound/core/control_compat.c index d8a86d1a99d6..9cae5d74335c 100644 --- a/sound/core/control_compat.c +++ b/sound/core/control_compat.c @@ -197,7 +197,7 @@ static int get_ctl_type(struct snd_card *card, struct snd_ctl_elem_id *id, return err; } -static int get_elem_size(int type, int count) +static int get_elem_size(snd_ctl_elem_type_t type, int count) { switch (type) { case SNDRV_CTL_ELEM_TYPE_INTEGER64: @@ -234,8 +234,8 @@ static int copy_ctl_value_from_user(struct snd_card *card, if (type < 0) return type; - if (type == SNDRV_CTL_ELEM_TYPE_BOOLEAN || - type == SNDRV_CTL_ELEM_TYPE_INTEGER) { + if (type == (__force int)SNDRV_CTL_ELEM_TYPE_BOOLEAN || + type == (__force int)SNDRV_CTL_ELEM_TYPE_INTEGER) { for (i = 0; i < count; i++) { s32 __user *intp = valuep; int val; @@ -244,7 +244,7 @@ static int copy_ctl_value_from_user(struct snd_card *card, data->value.integer.value[i] = val; } } else { - size = get_elem_size(type, count); + size = get_elem_size((__force snd_ctl_elem_type_t)type, count); if (size < 0) { dev_err(card->dev, "snd_ioctl32_ctl_elem_value: unknown type %d\n", type); return -EINVAL; @@ -267,8 +267,8 @@ static int copy_ctl_value_to_user(void __user *userdata, struct snd_ctl_elem_value32 __user *data32 = userdata; int i, size; - if (type == SNDRV_CTL_ELEM_TYPE_BOOLEAN || - type == SNDRV_CTL_ELEM_TYPE_INTEGER) { + if (type == (__force int)SNDRV_CTL_ELEM_TYPE_BOOLEAN || + type == (__force int)SNDRV_CTL_ELEM_TYPE_INTEGER) { for (i = 0; i < count; i++) { s32 __user *intp = valuep; int val; @@ -277,7 +277,7 @@ static int copy_ctl_value_to_user(void __user *userdata, return -EFAULT; } } else { - size = get_elem_size(type, count); + size = get_elem_size((__force snd_ctl_elem_type_t)type, count); if (copy_to_user(valuep, data->value.bytes.data, size)) return -EFAULT; } diff --git a/sound/core/control_led.c b/sound/core/control_led.c index 3cadd40100f3..ee77547bf8dc 100644 --- a/sound/core/control_led.c +++ b/sound/core/control_led.c @@ -737,7 +737,7 @@ static int __init snd_ctl_led_init(void) unsigned int group; device_initialize(&snd_ctl_led_dev); - snd_ctl_led_dev.class = sound_class; + snd_ctl_led_dev.class = &sound_class; snd_ctl_led_dev.release = snd_ctl_led_dev_release; dev_set_name(&snd_ctl_led_dev, "ctl-led"); if (device_add(&snd_ctl_led_dev)) { diff --git a/sound/core/init.c b/sound/core/init.c index df0c22480375..baef2688d0cf 100644 --- a/sound/core/init.c +++ b/sound/core/init.c @@ -129,7 +129,7 @@ void snd_device_initialize(struct device *dev, struct snd_card *card) device_initialize(dev); if (card) dev->parent = &card->card_dev; - dev->class = sound_class; + dev->class = &sound_class; dev->release = default_release; } EXPORT_SYMBOL_GPL(snd_device_initialize); @@ -331,7 +331,7 @@ static int snd_card_init(struct snd_card *card, struct device *parent, device_initialize(&card->card_dev); card->card_dev.parent = parent; - card->card_dev.class = sound_class; + card->card_dev.class = &sound_class; card->card_dev.release = release_card_device; card->card_dev.groups = card->dev_groups; card->dev_groups[0] = &card_dev_attr_group; diff --git a/sound/core/pcm_drm_eld.c b/sound/core/pcm_drm_eld.c index 4b5faae5d16e..07075071972d 100644 --- a/sound/core/pcm_drm_eld.c +++ b/sound/core/pcm_drm_eld.c @@ -2,11 +2,25 @@ /* * PCM DRM helpers */ +#include <linux/bitfield.h> #include <linux/export.h> +#include <linux/hdmi.h> #include <drm/drm_edid.h> #include <sound/pcm.h> #include <sound/pcm_drm_eld.h> +#define SAD0_CHANNELS_MASK GENMASK(2, 0) /* max number of channels - 1 */ +#define SAD0_FORMAT_MASK GENMASK(6, 3) /* audio format */ + +#define SAD1_RATE_MASK GENMASK(6, 0) /* bitfield of supported rates */ +#define SAD1_RATE_32000_MASK BIT(0) +#define SAD1_RATE_44100_MASK BIT(1) +#define SAD1_RATE_48000_MASK BIT(2) +#define SAD1_RATE_88200_MASK BIT(3) +#define SAD1_RATE_96000_MASK BIT(4) +#define SAD1_RATE_176400_MASK BIT(5) +#define SAD1_RATE_192000_MASK BIT(6) + static const unsigned int eld_rates[] = { 32000, 44100, @@ -17,9 +31,62 @@ static const unsigned int eld_rates[] = { 192000, }; +static unsigned int map_rate_families(const u8 *sad, + unsigned int mask_32000, + unsigned int mask_44100, + unsigned int mask_48000) +{ + unsigned int rate_mask = 0; + + if (sad[1] & SAD1_RATE_32000_MASK) + rate_mask |= mask_32000; + if (sad[1] & (SAD1_RATE_44100_MASK | SAD1_RATE_88200_MASK | SAD1_RATE_176400_MASK)) + rate_mask |= mask_44100; + if (sad[1] & (SAD1_RATE_48000_MASK | SAD1_RATE_96000_MASK | SAD1_RATE_192000_MASK)) + rate_mask |= mask_48000; + return rate_mask; +} + +static unsigned int sad_rate_mask(const u8 *sad) +{ + switch (FIELD_GET(SAD0_FORMAT_MASK, sad[0])) { + case HDMI_AUDIO_CODING_TYPE_PCM: + return sad[1] & SAD1_RATE_MASK; + case HDMI_AUDIO_CODING_TYPE_AC3: + case HDMI_AUDIO_CODING_TYPE_DTS: + return map_rate_families(sad, + SAD1_RATE_32000_MASK, + SAD1_RATE_44100_MASK, + SAD1_RATE_48000_MASK); + case HDMI_AUDIO_CODING_TYPE_EAC3: + case HDMI_AUDIO_CODING_TYPE_DTS_HD: + case HDMI_AUDIO_CODING_TYPE_MLP: + return map_rate_families(sad, + 0, + SAD1_RATE_176400_MASK, + SAD1_RATE_192000_MASK); + default: + /* TODO adjust for other compressed formats as well */ + return sad[1] & SAD1_RATE_MASK; + } +} + static unsigned int sad_max_channels(const u8 *sad) { - return 1 + (sad[0] & 7); + switch (FIELD_GET(SAD0_FORMAT_MASK, sad[0])) { + case HDMI_AUDIO_CODING_TYPE_PCM: + return 1 + FIELD_GET(SAD0_CHANNELS_MASK, sad[0]); + case HDMI_AUDIO_CODING_TYPE_AC3: + case HDMI_AUDIO_CODING_TYPE_DTS: + case HDMI_AUDIO_CODING_TYPE_EAC3: + return 2; + case HDMI_AUDIO_CODING_TYPE_DTS_HD: + case HDMI_AUDIO_CODING_TYPE_MLP: + return 8; + default: + /* TODO adjust for other compressed formats as well */ + return 1 + FIELD_GET(SAD0_CHANNELS_MASK, sad[0]); + } } static int eld_limit_rates(struct snd_pcm_hw_params *params, @@ -42,7 +109,7 @@ static int eld_limit_rates(struct snd_pcm_hw_params *params, * requested number of channels. */ if (c->min <= max_channels) - rate_mask |= sad[1]; + rate_mask |= sad_rate_mask(sad); } } @@ -70,7 +137,7 @@ static int eld_limit_channels(struct snd_pcm_hw_params *params, rate_mask |= BIT(i); for (i = drm_eld_sad_count(eld); i > 0; i--, sad += 3) - if (rate_mask & sad[1]) + if (rate_mask & sad_rate_mask(sad)) t.max = max(t.max, sad_max_channels(sad)); } diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c index 39a65d1415ab..95fc56e403b1 100644 --- a/sound/core/pcm_native.c +++ b/sound/core/pcm_native.c @@ -1605,10 +1605,6 @@ static int snd_pcm_do_pause(struct snd_pcm_substream *substream, { if (substream->runtime->trigger_master != substream) return 0; - /* some drivers might use hw_ptr to recover from the pause - - update the hw_ptr now */ - if (pause_pushed(state)) - snd_pcm_update_hw_ptr(substream); /* The jiffies check in snd_pcm_update_hw_ptr*() is done by * a delta between the current jiffies, this gives a large enough * delta, effectively to skip the check once. 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(®ister_mutex); - rmidi = snd_rawmidi_search(card, device); - if (!rmidi) - err = -ENODEV; - else if (!try_module_get(rmidi->card->module)) - err = -ENXIO; - mutex_unlock(®ister_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(®ister_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(®ister_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(®ister_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(®ister_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(®ister_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(®ister_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..e3f9ea67d019 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 (IS_ENABLED(CONFIG_SND_SEQ_UMP) && 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_system.c b/sound/core/seq/seq_system.c index 32c2d9b57751..80267290190d 100644 --- a/sound/core/seq/seq_system.c +++ b/sound/core/seq/seq_system.c @@ -85,6 +85,7 @@ void snd_seq_system_broadcast(int client, int port, int type) ev.type = type; snd_seq_kernel_client_dispatch(sysclient, &ev, 0, 0); } +EXPORT_SYMBOL_GPL(snd_seq_system_broadcast); /* entry points for broadcasting system events */ int snd_seq_system_notify(int client, int port, struct snd_seq_event *ev) diff --git a/sound/core/seq/seq_ump_client.c b/sound/core/seq/seq_ump_client.c new file mode 100644 index 000000000000..fe21c801af74 --- /dev/null +++ b/sound/core/seq/seq_ump_client.c @@ -0,0 +1,541 @@ +// 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" +#include "seq_system.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 */ + struct work_struct group_notify_work; /* FB change notification */ +}; + +/* 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; + + if (ump_is_groupless_msg(ump_message_type(*val))) + ev.source.port = 0; /* UMP EP port */ + else + 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 the sequencer ports; called from notify_fb_change callback */ +static void update_port_infos(struct seq_ump_client *client) +{ + struct snd_seq_port_info *old, *new; + int i, err; + + old = kzalloc(sizeof(*old), GFP_KERNEL); + new = kzalloc(sizeof(*new), GFP_KERNEL); + if (!old || !new) + goto error; + + for (i = 0; i < SNDRV_UMP_MAX_GROUPS; i++) { + old->addr.client = client->seq_client; + old->addr.port = i; + err = snd_seq_kernel_client_ctl(client->seq_client, + SNDRV_SEQ_IOCTL_GET_PORT_INFO, + old); + if (err < 0) + goto error; + fill_port_info(new, client, &client->groups[i]); + if (old->capability == new->capability && + !strcmp(old->name, new->name)) + continue; + err = snd_seq_kernel_client_ctl(client->seq_client, + SNDRV_SEQ_IOCTL_SET_PORT_INFO, + new); + if (err < 0) + goto error; + /* notify to system port */ + snd_seq_system_client_ev_port_change(client->seq_client, i); + } + error: + kfree(new); + kfree(old); +} + +/* 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) +{ + cancel_work_sync(&client->group_notify_work); + + 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); +} + +/* UMP group change notification */ +static void handle_group_notify(struct work_struct *work) +{ + struct seq_ump_client *client = + container_of(work, struct seq_ump_client, group_notify_work); + + update_group_attrs(client); + update_port_infos(client); +} + +/* UMP FB change notification */ +static int seq_ump_notify_fb_change(struct snd_ump_endpoint *ump, + struct snd_ump_block *fb) +{ + struct seq_ump_client *client = ump->seq_client; + + if (!client) + return -ENODEV; + schedule_work(&client->group_notify_work); + return 0; +} + +/* UMP protocol change notification; just update the midi_version field */ +static int seq_ump_switch_protocol(struct snd_ump_endpoint *ump) +{ + if (!ump->seq_client) + return -ENODEV; + setup_client_midi_version(ump->seq_client); + return 0; +} + +static const struct snd_seq_ump_ops seq_ump_ops = { + .input_receive = seq_ump_input_receive, + .notify_fb_change = seq_ump_notify_fb_change, + .switch_protocol = seq_ump_switch_protocol, +}; + +/* 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; + + INIT_WORK(&client->group_notify_work, handle_group_notify); + 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..eb1d86ff6166 --- /dev/null +++ b/sound/core/seq/seq_ump_convert.c @@ -0,0 +1,1206 @@ +// 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]); + if (ump_is_groupless_msg(ump_message_type(ev->ump[0]))) + return dest->group_filter & (1U << 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_is_groupless_msg(type) && + 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/timer.c b/sound/core/timer.c index e08a37c23add..9d0d2a5c2e15 100644 --- a/sound/core/timer.c +++ b/sound/core/timer.c @@ -1246,6 +1246,7 @@ static void snd_timer_proc_read(struct snd_info_entry *entry, { struct snd_timer *timer; struct snd_timer_instance *ti; + unsigned long resolution; mutex_lock(®ister_mutex); list_for_each_entry(timer, &snd_timer_list, device_list) { @@ -1269,10 +1270,13 @@ static void snd_timer_proc_read(struct snd_info_entry *entry, timer->tmr_device, timer->tmr_subdevice); } snd_iprintf(buffer, "%s :", timer->name); - if (timer->hw.resolution) + spin_lock_irq(&timer->lock); + resolution = snd_timer_hw_resolution(timer); + spin_unlock_irq(&timer->lock); + if (resolution) snd_iprintf(buffer, " %lu.%03luus (%lu ticks)", - timer->hw.resolution / 1000, - timer->hw.resolution % 1000, + resolution / 1000, + resolution % 1000, timer->hw.ticks); if (timer->hw.flags & SNDRV_TIMER_HW_SLAVE) snd_iprintf(buffer, " SLAVE"); @@ -1662,7 +1666,9 @@ static int snd_timer_user_ginfo(struct file *file, ginfo->flags |= SNDRV_TIMER_FLG_SLAVE; strscpy(ginfo->id, t->id, sizeof(ginfo->id)); strscpy(ginfo->name, t->name, sizeof(ginfo->name)); - ginfo->resolution = t->hw.resolution; + spin_lock_irq(&t->lock); + ginfo->resolution = snd_timer_hw_resolution(t); + spin_unlock_irq(&t->lock); if (t->hw.resolution_min > 0) { ginfo->resolution_min = t->hw.resolution_min; ginfo->resolution_max = t->hw.resolution_max; @@ -1817,7 +1823,9 @@ static int snd_timer_user_info(struct file *file, info->flags |= SNDRV_TIMER_FLG_SLAVE; strscpy(info->id, t->id, sizeof(info->id)); strscpy(info->name, t->name, sizeof(info->name)); - info->resolution = t->hw.resolution; + spin_lock_irq(&t->lock); + info->resolution = snd_timer_hw_resolution(t); + spin_unlock_irq(&t->lock); if (copy_to_user(_info, info, sizeof(*_info))) err = -EFAULT; kfree(info); diff --git a/sound/core/ump.c b/sound/core/ump.c new file mode 100644 index 000000000000..246348766ec1 --- /dev/null +++ b/sound/core/ump.c @@ -0,0 +1,1164 @@ +// 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 <sound/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); + +static void ump_handle_stream_msg(struct snd_ump_endpoint *ump, + const u32 *buf, int size); +#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) + kfree(ump->out_cvts); +#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); + init_waitqueue_head(&ump->stream_wait); +#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 +}; + +/** + * snd_ump_receive_ump_val - parse the UMP packet data + * @ump: UMP endpoint + * @val: 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. + */ +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; +} +EXPORT_SYMBOL_GPL(snd_ump_receive_ump_val); + +/** + * 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; + ump_handle_stream_msg(ump, ump->input_buf, n); +#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"; + } +} + +static const char *ump_ui_hint_string(int dir) +{ + switch (dir) { + case SNDRV_UMP_BLOCK_UI_HINT_RECEIVER: + return "receiver"; + case SNDRV_UMP_BLOCK_UI_HINT_SENDER: + return "sender"; + case SNDRV_UMP_BLOCK_UI_HINT_BOTH: + return "both"; + 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); + if (ump->info.version) { + snd_iprintf(buffer, "Manufacturer ID: 0x%08x\n", + ump->info.manufacturer_id); + snd_iprintf(buffer, "Family ID: 0x%04x\n", ump->info.family_id); + snd_iprintf(buffer, "Model ID: 0x%04x\n", ump->info.model_id); + snd_iprintf(buffer, "SW Revision: 0x%02x%02x%02x%02x\n", + ump->info.sw_revision[0], + ump->info.sw_revision[1], + ump->info.sw_revision[2], + ump->info.sw_revision[3]); + } + snd_iprintf(buffer, "Static Blocks: %s\n", + (ump->info.flags & SNDRV_UMP_EP_INFO_STATIC_BLOCKS) ? "Yes" : "No"); + 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)" : ""); + if (ump->info.version) { + snd_iprintf(buffer, " MIDI-CI Version: %d\n", + fb->info.midi_ci_version); + snd_iprintf(buffer, " Sysex8 Streams: %d\n", + fb->info.sysex8_streams); + snd_iprintf(buffer, " UI Hint: %s\n", + ump_ui_hint_string(fb->info.ui_hint)); + } + snd_iprintf(buffer, "\n"); + } +} + +/* + * UMP endpoint and function block handling + */ + +/* open / close UMP streams for the internal stream msg communication */ +static int ump_request_open(struct snd_ump_endpoint *ump) +{ + return snd_rawmidi_kernel_open(&ump->core, 0, + SNDRV_RAWMIDI_LFLG_OUTPUT, + &ump->stream_rfile); +} + +static void ump_request_close(struct snd_ump_endpoint *ump) +{ + snd_rawmidi_kernel_release(&ump->stream_rfile); +} + +/* request a command and wait for the given response; + * @req1 and @req2 are u32 commands + * @reply is the expected UMP stream status + */ +static int ump_req_msg(struct snd_ump_endpoint *ump, u32 req1, u32 req2, + u32 reply) +{ + u32 buf[4]; + + ump_dbg(ump, "%s: request %08x %08x, wait-for %08x\n", + __func__, req1, req2, reply); + memset(buf, 0, sizeof(buf)); + buf[0] = req1; + buf[1] = req2; + ump->stream_finished = 0; + ump->stream_wait_for = reply; + snd_rawmidi_kernel_write(ump->stream_rfile.output, + (unsigned char *)&buf, 16); + wait_event_timeout(ump->stream_wait, ump->stream_finished, + msecs_to_jiffies(500)); + if (!READ_ONCE(ump->stream_finished)) { + ump_dbg(ump, "%s: request timed out\n", __func__); + return -ETIMEDOUT; + } + ump->stream_finished = 0; + ump_dbg(ump, "%s: reply: %08x %08x %08x %08x\n", + __func__, buf[0], buf[1], buf[2], buf[3]); + return 0; +} + +/* append the received letters via UMP packet to the given string buffer; + * return 1 if the full string is received or 0 to continue + */ +static int ump_append_string(struct snd_ump_endpoint *ump, char *dest, + int maxsize, const u32 *buf, int offset) +{ + unsigned char format; + int c; + + format = ump_stream_message_format(buf[0]); + if (format == UMP_STREAM_MSG_FORMAT_SINGLE || + format == UMP_STREAM_MSG_FORMAT_START) { + c = 0; + } else { + c = strlen(dest); + if (c >= maxsize - 1) + return 1; + } + + for (; offset < 16; offset++) { + dest[c] = buf[offset / 4] >> (3 - (offset % 4)) * 8; + if (!dest[c]) + break; + if (++c >= maxsize - 1) + break; + } + dest[c] = 0; + return (format == UMP_STREAM_MSG_FORMAT_SINGLE || + format == UMP_STREAM_MSG_FORMAT_END); +} + +/* handle EP info stream message; update the UMP attributes */ +static int ump_handle_ep_info_msg(struct snd_ump_endpoint *ump, + const union snd_ump_stream_msg *buf) +{ + ump->info.version = (buf->ep_info.ump_version_major << 8) | + buf->ep_info.ump_version_minor; + ump->info.num_blocks = buf->ep_info.num_function_blocks; + if (ump->info.num_blocks > SNDRV_UMP_MAX_BLOCKS) { + ump_info(ump, "Invalid function blocks %d, fallback to 1\n", + ump->info.num_blocks); + ump->info.num_blocks = 1; + } + + if (buf->ep_info.static_function_block) + ump->info.flags |= SNDRV_UMP_EP_INFO_STATIC_BLOCKS; + + ump->info.protocol_caps = (buf->ep_info.protocol << 8) | + buf->ep_info.jrts; + + ump_dbg(ump, "EP info: version=%x, num_blocks=%x, proto_caps=%x\n", + ump->info.version, ump->info.num_blocks, ump->info.protocol_caps); + return 1; /* finished */ +} + +/* handle EP device info stream message; update the UMP attributes */ +static int ump_handle_device_info_msg(struct snd_ump_endpoint *ump, + const union snd_ump_stream_msg *buf) +{ + ump->info.manufacturer_id = buf->device_info.manufacture_id & 0x7f7f7f; + ump->info.family_id = (buf->device_info.family_msb << 8) | + buf->device_info.family_lsb; + ump->info.model_id = (buf->device_info.model_msb << 8) | + buf->device_info.model_lsb; + ump->info.sw_revision[0] = (buf->device_info.sw_revision >> 24) & 0x7f; + ump->info.sw_revision[1] = (buf->device_info.sw_revision >> 16) & 0x7f; + ump->info.sw_revision[2] = (buf->device_info.sw_revision >> 8) & 0x7f; + ump->info.sw_revision[3] = buf->device_info.sw_revision & 0x7f; + ump_dbg(ump, "EP devinfo: manid=%08x, family=%04x, model=%04x, sw=%02x%02x%02x%02x\n", + ump->info.manufacturer_id, + ump->info.family_id, + ump->info.model_id, + ump->info.sw_revision[0], + ump->info.sw_revision[1], + ump->info.sw_revision[2], + ump->info.sw_revision[3]); + return 1; /* finished */ +} + +/* handle EP name stream message; update the UMP name string */ +static int ump_handle_ep_name_msg(struct snd_ump_endpoint *ump, + const union snd_ump_stream_msg *buf) +{ + return ump_append_string(ump, ump->info.name, sizeof(ump->info.name), + buf->raw, 2); +} + +/* handle EP product id stream message; update the UMP product_id string */ +static int ump_handle_product_id_msg(struct snd_ump_endpoint *ump, + const union snd_ump_stream_msg *buf) +{ + return ump_append_string(ump, ump->info.product_id, + sizeof(ump->info.product_id), + buf->raw, 2); +} + +/* notify the protocol change to sequencer */ +static void seq_notify_protocol(struct snd_ump_endpoint *ump) +{ +#if IS_ENABLED(CONFIG_SND_SEQUENCER) + if (ump->seq_ops && ump->seq_ops->switch_protocol) + ump->seq_ops->switch_protocol(ump); +#endif /* CONFIG_SND_SEQUENCER */ +} + +/** + * snd_ump_switch_protocol - switch MIDI protocol + * @ump: UMP endpoint + * @protocol: protocol to switch to + * + * Returns 1 if the protocol is actually switched, 0 if unchanged + */ +int snd_ump_switch_protocol(struct snd_ump_endpoint *ump, unsigned int protocol) +{ + protocol &= ump->info.protocol_caps; + if (protocol == ump->info.protocol) + return 0; + + ump->info.protocol = protocol; + ump_dbg(ump, "New protocol = %x (caps = %x)\n", + protocol, ump->info.protocol_caps); + seq_notify_protocol(ump); + return 1; +} +EXPORT_SYMBOL_GPL(snd_ump_switch_protocol); + +/* handle EP stream config message; update the UMP protocol */ +static int ump_handle_stream_cfg_msg(struct snd_ump_endpoint *ump, + const union snd_ump_stream_msg *buf) +{ + unsigned int protocol = + (buf->stream_cfg.protocol << 8) | buf->stream_cfg.jrts; + + snd_ump_switch_protocol(ump, protocol); + return 1; /* finished */ +} + +/* Extract Function Block info from UMP packet */ +static void fill_fb_info(struct snd_ump_endpoint *ump, + struct snd_ump_block_info *info, + const union snd_ump_stream_msg *buf) +{ + info->direction = buf->fb_info.direction; + info->ui_hint = buf->fb_info.ui_hint; + info->first_group = buf->fb_info.first_group; + info->num_groups = buf->fb_info.num_groups; + info->flags = buf->fb_info.midi_10; + info->active = buf->fb_info.active; + info->midi_ci_version = buf->fb_info.midi_ci_version; + info->sysex8_streams = buf->fb_info.sysex8_streams; + + ump_dbg(ump, "FB %d: dir=%d, active=%d, first_gp=%d, num_gp=%d, midici=%d, sysex8=%d, flags=0x%x\n", + info->block_id, info->direction, info->active, + info->first_group, info->num_groups, info->midi_ci_version, + info->sysex8_streams, info->flags); +} + +/* check whether the FB info gets updated by the current message */ +static bool is_fb_info_updated(struct snd_ump_endpoint *ump, + struct snd_ump_block *fb, + const union snd_ump_stream_msg *buf) +{ + char tmpbuf[offsetof(struct snd_ump_block_info, name)]; + + if (ump->info.flags & SNDRV_UMP_EP_INFO_STATIC_BLOCKS) { + ump_info(ump, "Skipping static FB info update (blk#%d)\n", + fb->info.block_id); + return 0; + } + + memcpy(tmpbuf, &fb->info, sizeof(tmpbuf)); + fill_fb_info(ump, (struct snd_ump_block_info *)tmpbuf, buf); + return memcmp(&fb->info, tmpbuf, sizeof(tmpbuf)) != 0; +} + +/* notify the FB info/name change to sequencer */ +static void seq_notify_fb_change(struct snd_ump_endpoint *ump, + struct snd_ump_block *fb) +{ +#if IS_ENABLED(CONFIG_SND_SEQUENCER) + if (ump->seq_ops && ump->seq_ops->notify_fb_change) + ump->seq_ops->notify_fb_change(ump, fb); +#endif +} + +/* handle FB info message; update FB info if the block is present */ +static int ump_handle_fb_info_msg(struct snd_ump_endpoint *ump, + const union snd_ump_stream_msg *buf) +{ + unsigned char blk; + struct snd_ump_block *fb; + + blk = buf->fb_info.function_block_id; + fb = snd_ump_get_block(ump, blk); + + /* complain only if updated after parsing */ + if (!fb && ump->parsed) { + ump_info(ump, "Function Block Info Update for non-existing block %d\n", + blk); + return -ENODEV; + } + + /* When updated after the initial parse, check the FB info update */ + if (ump->parsed && !is_fb_info_updated(ump, fb, buf)) + return 1; /* no content change */ + + if (fb) { + fill_fb_info(ump, &fb->info, buf); + if (ump->parsed) + seq_notify_fb_change(ump, fb); + } + + return 1; /* finished */ +} + +/* handle FB name message; update the FB name string */ +static int ump_handle_fb_name_msg(struct snd_ump_endpoint *ump, + const union snd_ump_stream_msg *buf) +{ + unsigned char blk; + struct snd_ump_block *fb; + int ret; + + blk = buf->fb_name.function_block_id; + fb = snd_ump_get_block(ump, blk); + if (!fb) + return -ENODEV; + + ret = ump_append_string(ump, fb->info.name, sizeof(fb->info.name), + buf->raw, 3); + /* notify the FB name update to sequencer, too */ + if (ret > 0 && ump->parsed) + seq_notify_fb_change(ump, fb); + return ret; +} + +static int create_block_from_fb_info(struct snd_ump_endpoint *ump, int blk) +{ + struct snd_ump_block *fb; + unsigned char direction, first_group, num_groups; + const union snd_ump_stream_msg *buf = + (const union snd_ump_stream_msg *)ump->input_buf; + u32 msg; + int err; + + /* query the FB info once */ + msg = ump_stream_compose(UMP_STREAM_MSG_STATUS_FB_DISCOVERY, 0) | + (blk << 8) | UMP_STREAM_MSG_REQUEST_FB_INFO; + err = ump_req_msg(ump, msg, 0, UMP_STREAM_MSG_STATUS_FB_INFO); + if (err < 0) { + ump_dbg(ump, "Unable to get FB info for block %d\n", blk); + return err; + } + + /* the last input must be the FB info */ + if (buf->fb_info.status != UMP_STREAM_MSG_STATUS_FB_INFO) { + ump_dbg(ump, "Inconsistent input: 0x%x\n", *buf->raw); + return -EINVAL; + } + + direction = buf->fb_info.direction; + first_group = buf->fb_info.first_group; + num_groups = buf->fb_info.num_groups; + + err = snd_ump_block_new(ump, blk, direction, first_group, num_groups, + &fb); + if (err < 0) + return err; + + fill_fb_info(ump, &fb->info, buf); + + msg = ump_stream_compose(UMP_STREAM_MSG_STATUS_FB_DISCOVERY, 0) | + (blk << 8) | UMP_STREAM_MSG_REQUEST_FB_NAME; + err = ump_req_msg(ump, msg, 0, UMP_STREAM_MSG_STATUS_FB_NAME); + if (err) + ump_dbg(ump, "Unable to get UMP FB name string #%d\n", blk); + + return 0; +} + +/* handle stream messages, called from snd_ump_receive() */ +static void ump_handle_stream_msg(struct snd_ump_endpoint *ump, + const u32 *buf, int size) +{ + const union snd_ump_stream_msg *msg; + unsigned int status; + int ret; + + /* UMP stream message suppressed (for gadget UMP)? */ + if (ump->no_process_stream) + return; + + BUILD_BUG_ON(sizeof(*msg) != 16); + ump_dbg(ump, "Stream msg: %08x %08x %08x %08x\n", + buf[0], buf[1], buf[2], buf[3]); + + if (size != 4 || ump_message_type(*buf) != UMP_MSG_TYPE_STREAM) + return; + + msg = (const union snd_ump_stream_msg *)buf; + status = ump_stream_message_status(*buf); + switch (status) { + case UMP_STREAM_MSG_STATUS_EP_INFO: + ret = ump_handle_ep_info_msg(ump, msg); + break; + case UMP_STREAM_MSG_STATUS_DEVICE_INFO: + ret = ump_handle_device_info_msg(ump, msg); + break; + case UMP_STREAM_MSG_STATUS_EP_NAME: + ret = ump_handle_ep_name_msg(ump, msg); + break; + case UMP_STREAM_MSG_STATUS_PRODUCT_ID: + ret = ump_handle_product_id_msg(ump, msg); + break; + case UMP_STREAM_MSG_STATUS_STREAM_CFG: + ret = ump_handle_stream_cfg_msg(ump, msg); + break; + case UMP_STREAM_MSG_STATUS_FB_INFO: + ret = ump_handle_fb_info_msg(ump, msg); + break; + case UMP_STREAM_MSG_STATUS_FB_NAME: + ret = ump_handle_fb_name_msg(ump, msg); + break; + default: + return; + } + + /* when the message has been processed fully, wake up */ + if (ret > 0 && ump->stream_wait_for == status) { + WRITE_ONCE(ump->stream_finished, 1); + wake_up(&ump->stream_wait); + } +} + +/** + * snd_ump_parse_endpoint - parse endpoint and create function blocks + * @ump: UMP object + * + * Returns 0 for successful parse, -ENODEV if device doesn't respond + * (or the query is unsupported), or other error code for serious errors. + */ +int snd_ump_parse_endpoint(struct snd_ump_endpoint *ump) +{ + int blk, err; + u32 msg; + + if (!(ump->core.info_flags & SNDRV_RAWMIDI_INFO_DUPLEX)) + return -ENODEV; + + err = ump_request_open(ump); + if (err < 0) { + ump_dbg(ump, "Unable to open rawmidi device: %d\n", err); + return err; + } + + /* Check Endpoint Information */ + msg = ump_stream_compose(UMP_STREAM_MSG_STATUS_EP_DISCOVERY, 0) | + 0x0101; /* UMP version 1.1 */ + err = ump_req_msg(ump, msg, UMP_STREAM_MSG_REQUEST_EP_INFO, + UMP_STREAM_MSG_STATUS_EP_INFO); + if (err < 0) { + ump_dbg(ump, "Unable to get UMP EP info\n"); + goto error; + } + + /* Request Endpoint Device Info */ + err = ump_req_msg(ump, msg, UMP_STREAM_MSG_REQUEST_DEVICE_INFO, + UMP_STREAM_MSG_STATUS_DEVICE_INFO); + if (err < 0) + ump_dbg(ump, "Unable to get UMP EP device info\n"); + + /* Request Endpoint Name */ + err = ump_req_msg(ump, msg, UMP_STREAM_MSG_REQUEST_EP_NAME, + UMP_STREAM_MSG_STATUS_EP_NAME); + if (err < 0) + ump_dbg(ump, "Unable to get UMP EP name string\n"); + + /* Request Endpoint Product ID */ + err = ump_req_msg(ump, msg, UMP_STREAM_MSG_REQUEST_PRODUCT_ID, + UMP_STREAM_MSG_STATUS_PRODUCT_ID); + if (err < 0) + ump_dbg(ump, "Unable to get UMP EP product ID string\n"); + + /* Get the current stream configuration */ + err = ump_req_msg(ump, msg, UMP_STREAM_MSG_REQUEST_STREAM_CFG, + UMP_STREAM_MSG_STATUS_STREAM_CFG); + if (err < 0) + ump_dbg(ump, "Unable to get UMP EP stream config\n"); + + /* Query and create blocks from Function Blocks */ + for (blk = 0; blk < ump->info.num_blocks; blk++) { + err = create_block_from_fb_info(ump, blk); + if (err < 0) + continue; + } + + error: + ump->parsed = true; + ump_request_close(ump); + if (err == -ETIMEDOUT) + err = -ENODEV; + return err; +} +EXPORT_SYMBOL_GPL(snd_ump_parse_endpoint); + +#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_convert_reset(&ump->out_cvts[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(ctx, group, ump->info.protocol, 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(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; + + ump->out_cvts = kcalloc(16, sizeof(*ump->out_cvts), GFP_KERNEL); + if (!ump->out_cvts) + return -ENOMEM; + + 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) { + kfree(ump->out_cvts); + 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..fb61df424a87 --- /dev/null +++ b/sound/core/ump_convert.c @@ -0,0 +1,505 @@ +// 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 <sound/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; +} + +/** + * snd_ump_convert_from_ump - convert from UMP to legacy MIDI + * @data: UMP packet + * @buf: buffer to store legacy MIDI data + * @group_ret: pointer to store the target group + * + * Convert from a UMP packet @data to MIDI 1.0 bytes at @buf. + * The target group is stored at @group_ret. + * + * The function returns the number of bytes of MIDI 1.0 stream. + */ +int snd_ump_convert_from_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; +} +EXPORT_SYMBOL_GPL(snd_ump_convert_from_ump); + +/* + * 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 ump_cvt_to_ump *cvt, + unsigned char group, + unsigned int protocol, + 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 (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: + 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 ump_cvt_to_ump *cvt, unsigned char group, + unsigned int protocol, 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 + }; + 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 >> 4) & 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(cvt, group, protocol, data, cvt->cmd_bytes); +} + +/** + * snd_ump_convert_to_ump - convert legacy MIDI byte to UMP packet + * @cvt: converter context + * @group: target UMP group + * @protocol: target UMP protocol + * @c: MIDI 1.0 byte data + * + * Feed a MIDI 1.0 byte @c and convert to a UMP packet if completed. + * The result is stored in the buffer in @cvt. + */ +void snd_ump_convert_to_ump(struct ump_cvt_to_ump *cvt, unsigned char group, + unsigned int protocol, unsigned char c) +{ + cvt->ump_bytes = do_convert_to_ump(cvt, group, protocol, c, cvt->ump); +} +EXPORT_SYMBOL_GPL(snd_ump_convert_to_ump); |