summaryrefslogtreecommitdiffstats
path: root/sound/core/ump.c
diff options
context:
space:
mode:
authorTakashi Iwai <tiwai@suse.de>2023-05-23 09:53:35 +0200
committerTakashi Iwai <tiwai@suse.de>2023-05-23 12:11:09 +0200
commit0b5288f5fe63eab687c14e5940b9e0d532b129f2 (patch)
tree8042905dfc0d2efc6c9cb41a08a994299123f33a /sound/core/ump.c
parent6b41e64a5d17ec01380bc7ad10afd90e63beca19 (diff)
downloadlinux-stable-0b5288f5fe63eab687c14e5940b9e0d532b129f2.tar.gz
linux-stable-0b5288f5fe63eab687c14e5940b9e0d532b129f2.tar.bz2
linux-stable-0b5288f5fe63eab687c14e5940b9e0d532b129f2.zip
ALSA: ump: Add legacy raw MIDI support
This patch extends the UMP core code to support the legacy MIDI 1.0 rawmidi devices. When the new kconfig CONFIG_SND_UMP_LEGACY_RAWMIDI is set, the UMP core allows to attach an additional rawmidi device for each UMP Endpoint. The rawmidi device contains 16 substreams where each substream corresponds to a UMP Group belonging to the EP. The device reads/writes the legacy MIDI 1.0 byte streams and translates from/to UMP packets. The legacy rawmidi devices are exclusive with the UMP rawmidi devices, hence both of them can't be opened at the same time unless the UMP rawmidi is opened in APPEND mode. Reviewed-by: Jaroslav Kysela <perex@perex.cz> Link: https://lore.kernel.org/r/20230523075358.9672-15-tiwai@suse.de Signed-off-by: Takashi Iwai <tiwai@suse.de>
Diffstat (limited to 'sound/core/ump.c')
-rw-r--r--sound/core/ump.c258
1 files changed, 255 insertions, 3 deletions
diff --git a/sound/core/ump.c b/sound/core/ump.c
index 46ec297a786c..cbe704b5d90d 100644
--- a/sound/core/ump.c
+++ b/sound/core/ump.c
@@ -11,6 +11,7 @@
#include <sound/core.h>
#include <sound/rawmidi.h>
#include <sound/ump.h>
+#include "ump_convert.h"
#define ump_err(ump, fmt, args...) dev_err(&(ump)->core.dev, fmt, ##args)
#define ump_warn(ump, fmt, args...) dev_warn(&(ump)->core.dev, fmt, ##args)
@@ -29,6 +30,23 @@ static void snd_ump_rawmidi_trigger(struct snd_rawmidi_substream *substream,
int up);
static void snd_ump_rawmidi_drain(struct snd_rawmidi_substream *substream);
+#if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI)
+static int process_legacy_output(struct snd_ump_endpoint *ump,
+ u32 *buffer, int count);
+static void process_legacy_input(struct snd_ump_endpoint *ump, const u32 *src,
+ int words);
+#else
+static inline int process_legacy_output(struct snd_ump_endpoint *ump,
+ u32 *buffer, int count)
+{
+ return 0;
+}
+static inline void process_legacy_input(struct snd_ump_endpoint *ump,
+ const u32 *src, int words)
+{
+}
+#endif
+
static const struct snd_rawmidi_global_ops snd_ump_rawmidi_ops = {
.dev_register = snd_ump_dev_register,
.dev_unregister = snd_ump_dev_unregister,
@@ -65,6 +83,10 @@ static void snd_ump_endpoint_free(struct snd_rawmidi *rmidi)
if (ump->private_free)
ump->private_free(ump);
+
+#if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI)
+ snd_ump_convert_free(ump);
+#endif
}
/**
@@ -110,6 +132,11 @@ int snd_ump_endpoint_new(struct snd_card *card, char *id, int device,
if (!ump)
return -ENOMEM;
INIT_LIST_HEAD(&ump->block_list);
+#if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI)
+ mutex_init(&ump->open_mutex);
+ 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) {
@@ -206,6 +233,33 @@ static void snd_ump_rawmidi_drain(struct snd_rawmidi_substream *substream)
ump->ops->drain(ump, SNDRV_RAWMIDI_STREAM_OUTPUT);
}
+/* number of 32bit words per message type */
+static unsigned char ump_packet_words[0x10] = {
+ 1, 1, 1, 2, 2, 4, 1, 1, 2, 2, 2, 3, 3, 4, 4, 4
+};
+
+/* parse the UMP packet data;
+ * the data is copied onto ump->input_buf[].
+ * When a full packet is completed, returns the number of words (from 1 to 4).
+ * OTOH, if the packet is incomplete, returns 0.
+ */
+static int snd_ump_receive_ump_val(struct snd_ump_endpoint *ump, u32 val)
+{
+ int words;
+
+ if (!ump->input_pending)
+ ump->input_pending = ump_packet_words[ump_message_type(val)];
+
+ ump->input_buf[ump->input_buf_head++] = val;
+ ump->input_pending--;
+ if (!ump->input_pending) {
+ words = ump->input_buf_head;
+ ump->input_buf_head = 0;
+ return words;
+ }
+ return 0;
+}
+
/**
* snd_ump_receive - transfer UMP packets from the device
* @ump: the UMP endpoint
@@ -218,9 +272,18 @@ static void snd_ump_rawmidi_drain(struct snd_rawmidi_substream *substream)
*/
int snd_ump_receive(struct snd_ump_endpoint *ump, const u32 *buffer, int count)
{
- struct snd_rawmidi_substream *substream =
- ump->substreams[SNDRV_RAWMIDI_STREAM_INPUT];
+ 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;
+ 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);
@@ -241,10 +304,15 @@ 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;
- return snd_rawmidi_transmit(substream, (char *)buffer, count);
+ 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);
@@ -386,5 +454,189 @@ static void snd_ump_proc_read(struct snd_info_entry *entry,
}
}
+#if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI)
+/*
+ * Legacy rawmidi support
+ */
+static int snd_ump_legacy_open(struct snd_rawmidi_substream *substream)
+{
+ struct snd_ump_endpoint *ump = substream->rmidi->private_data;
+ int dir = substream->stream;
+ int group = substream->number;
+ int err;
+
+ mutex_lock(&ump->open_mutex);
+ if (ump->legacy_substreams[dir][group]) {
+ err = -EBUSY;
+ goto unlock;
+ }
+ if (dir == SNDRV_RAWMIDI_STREAM_OUTPUT) {
+ if (!ump->legacy_out_opens) {
+ err = snd_rawmidi_kernel_open(&ump->core, 0,
+ SNDRV_RAWMIDI_LFLG_OUTPUT |
+ SNDRV_RAWMIDI_LFLG_APPEND,
+ &ump->legacy_out_rfile);
+ if (err < 0)
+ goto unlock;
+ }
+ ump->legacy_out_opens++;
+ snd_ump_reset_convert_to_ump(ump, group);
+ }
+ spin_lock_irq(&ump->legacy_locks[dir]);
+ ump->legacy_substreams[dir][group] = substream;
+ spin_unlock_irq(&ump->legacy_locks[dir]);
+ unlock:
+ mutex_unlock(&ump->open_mutex);
+ return 0;
+}
+
+static int snd_ump_legacy_close(struct snd_rawmidi_substream *substream)
+{
+ struct snd_ump_endpoint *ump = substream->rmidi->private_data;
+ int dir = substream->stream;
+ int group = substream->number;
+
+ mutex_lock(&ump->open_mutex);
+ spin_lock_irq(&ump->legacy_locks[dir]);
+ ump->legacy_substreams[dir][group] = NULL;
+ spin_unlock_irq(&ump->legacy_locks[dir]);
+ if (dir == SNDRV_RAWMIDI_STREAM_OUTPUT) {
+ if (!--ump->legacy_out_opens)
+ snd_rawmidi_kernel_release(&ump->legacy_out_rfile);
+ }
+ mutex_unlock(&ump->open_mutex);
+ return 0;
+}
+
+static void snd_ump_legacy_trigger(struct snd_rawmidi_substream *substream,
+ int up)
+{
+ struct snd_ump_endpoint *ump = substream->rmidi->private_data;
+ int dir = substream->stream;
+
+ ump->ops->trigger(ump, dir, up);
+}
+
+static void snd_ump_legacy_drain(struct snd_rawmidi_substream *substream)
+{
+ struct snd_ump_endpoint *ump = substream->rmidi->private_data;
+
+ if (ump->ops->drain)
+ ump->ops->drain(ump, SNDRV_RAWMIDI_STREAM_OUTPUT);
+}
+
+static int snd_ump_legacy_dev_register(struct snd_rawmidi *rmidi)
+{
+ /* dummy, just for avoiding create superfluous seq clients */
+ return 0;
+}
+
+static const struct snd_rawmidi_ops snd_ump_legacy_input_ops = {
+ .open = snd_ump_legacy_open,
+ .close = snd_ump_legacy_close,
+ .trigger = snd_ump_legacy_trigger,
+};
+
+static const struct snd_rawmidi_ops snd_ump_legacy_output_ops = {
+ .open = snd_ump_legacy_open,
+ .close = snd_ump_legacy_close,
+ .trigger = snd_ump_legacy_trigger,
+ .drain = snd_ump_legacy_drain,
+};
+
+static const struct snd_rawmidi_global_ops snd_ump_legacy_ops = {
+ .dev_register = snd_ump_legacy_dev_register,
+};
+
+static int process_legacy_output(struct snd_ump_endpoint *ump,
+ u32 *buffer, int count)
+{
+ struct snd_rawmidi_substream *substream;
+ struct ump_cvt_to_ump *ctx;
+ const int dir = SNDRV_RAWMIDI_STREAM_OUTPUT;
+ unsigned char c;
+ int group, size = 0;
+ unsigned long flags;
+
+ if (!ump->out_cvts || !ump->legacy_out_opens)
+ return 0;
+
+ spin_lock_irqsave(&ump->legacy_locks[dir], flags);
+ for (group = 0; group < SNDRV_UMP_MAX_GROUPS; group++) {
+ substream = ump->legacy_substreams[dir][group];
+ if (!substream)
+ continue;
+ ctx = &ump->out_cvts[group];
+ while (!ctx->ump_bytes &&
+ snd_rawmidi_transmit(substream, &c, 1) > 0)
+ snd_ump_convert_to_ump(ump, group, c);
+ if (ctx->ump_bytes && ctx->ump_bytes <= count) {
+ size = ctx->ump_bytes;
+ memcpy(buffer, ctx->ump, size);
+ ctx->ump_bytes = 0;
+ break;
+ }
+ }
+ spin_unlock_irqrestore(&ump->legacy_locks[dir], flags);
+ return size;
+}
+
+static void process_legacy_input(struct snd_ump_endpoint *ump, const u32 *src,
+ int words)
+{
+ struct snd_rawmidi_substream *substream;
+ unsigned char buf[16];
+ unsigned char group;
+ unsigned long flags;
+ const int dir = SNDRV_RAWMIDI_STREAM_INPUT;
+ int size;
+
+ size = snd_ump_convert_from_ump(ump, src, buf, &group);
+ if (size <= 0)
+ return;
+ spin_lock_irqsave(&ump->legacy_locks[dir], flags);
+ substream = ump->legacy_substreams[dir][group];
+ if (substream)
+ snd_rawmidi_receive(substream, buf, size);
+ spin_unlock_irqrestore(&ump->legacy_locks[dir], flags);
+}
+
+int snd_ump_attach_legacy_rawmidi(struct snd_ump_endpoint *ump,
+ char *id, int device)
+{
+ struct snd_rawmidi *rmidi;
+ bool input, output;
+ int err;
+
+ err = snd_ump_convert_init(ump);
+ if (err < 0)
+ return err;
+
+ input = ump->core.info_flags & SNDRV_RAWMIDI_INFO_INPUT;
+ output = ump->core.info_flags & SNDRV_RAWMIDI_INFO_OUTPUT;
+ err = snd_rawmidi_new(ump->core.card, id, device,
+ output ? 16 : 0, input ? 16 : 0,
+ &rmidi);
+ if (err < 0) {
+ snd_ump_convert_free(ump);
+ return err;
+ }
+
+ if (input)
+ snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT,
+ &snd_ump_legacy_input_ops);
+ if (output)
+ snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT,
+ &snd_ump_legacy_output_ops);
+ rmidi->info_flags = ump->core.info_flags & ~SNDRV_RAWMIDI_INFO_UMP;
+ rmidi->ops = &snd_ump_legacy_ops;
+ rmidi->private_data = ump;
+ ump->legacy_rmidi = rmidi;
+ ump_dbg(ump, "Created a legacy rawmidi #%d (%s)\n", device, id);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_ump_attach_legacy_rawmidi);
+#endif /* CONFIG_SND_UMP_LEGACY_RAWMIDI */
+
MODULE_DESCRIPTION("Universal MIDI Packet (UMP) Core Driver");
MODULE_LICENSE("GPL");