Commit 0b5288f5 authored by Takashi Iwai's avatar Takashi Iwai
Browse files

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: default avatarJaroslav Kysela <perex@perex.cz>
Link: https://lore.kernel.org/r/20230523075358.9672-15-tiwai@suse.de


Signed-off-by: default avatarTakashi Iwai <tiwai@suse.de>
parent 6b41e64a
Loading
Loading
Loading
Loading
+30 −0
Original line number Diff line number Diff line
@@ -10,6 +10,7 @@
struct snd_ump_endpoint;
struct snd_ump_block;
struct snd_ump_ops;
struct ump_cvt_to_ump;

struct snd_ump_endpoint {
	struct snd_rawmidi core;	/* raw UMP access */
@@ -23,6 +24,24 @@ struct snd_ump_endpoint {
	void (*private_free)(struct snd_ump_endpoint *ump);

	struct list_head block_list;	/* list of snd_ump_block objects */

	/* intermediate buffer for UMP input */
	u32 input_buf[4];
	int input_buf_head;
	int input_pending;

#if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI)
	struct mutex open_mutex;

	spinlock_t legacy_locks[2];
	struct snd_rawmidi *legacy_rmidi;
	struct snd_rawmidi_substream *legacy_substreams[2][SNDRV_UMP_MAX_GROUPS];

	/* for legacy output; need to open the actual substream unlike input */
	int legacy_out_opens;
	struct snd_rawmidi_file legacy_out_rfile;
	struct ump_cvt_to_ump *out_cvts;
#endif
};

/* ops filled by UMP drivers */
@@ -54,6 +73,17 @@ int snd_ump_block_new(struct snd_ump_endpoint *ump, unsigned int blk,
int snd_ump_receive(struct snd_ump_endpoint *ump, const u32 *buffer, int count);
int snd_ump_transmit(struct snd_ump_endpoint *ump, u32 *buffer, int count);

#if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI)
int snd_ump_attach_legacy_rawmidi(struct snd_ump_endpoint *ump,
				  char *id, int device);
#else
static inline int snd_ump_attach_legacy_rawmidi(struct snd_ump_endpoint *ump,
						char *id, int device)
{
	return 0;
}
#endif

/*
 * Some definitions for UMP
 */
+540 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Universal MIDI Packet (UMP): Message Definitions
 */
#ifndef __SOUND_UMP_MSG_H
#define __SOUND_UMP_MSG_H

/* MIDI 1.0 / 2.0 Status Code (4bit) */
enum {
	UMP_MSG_STATUS_PER_NOTE_RCC = 0x0,
	UMP_MSG_STATUS_PER_NOTE_ACC = 0x1,
	UMP_MSG_STATUS_RPN = 0x2,
	UMP_MSG_STATUS_NRPN = 0x3,
	UMP_MSG_STATUS_RELATIVE_RPN = 0x4,
	UMP_MSG_STATUS_RELATIVE_NRPN = 0x5,
	UMP_MSG_STATUS_PER_NOTE_PITCH_BEND = 0x6,
	UMP_MSG_STATUS_NOTE_OFF = 0x8,
	UMP_MSG_STATUS_NOTE_ON = 0x9,
	UMP_MSG_STATUS_POLY_PRESSURE = 0xa,
	UMP_MSG_STATUS_CC = 0xb,
	UMP_MSG_STATUS_PROGRAM = 0xc,
	UMP_MSG_STATUS_CHANNEL_PRESSURE = 0xd,
	UMP_MSG_STATUS_PITCH_BEND = 0xe,
	UMP_MSG_STATUS_PER_NOTE_MGMT = 0xf,
};

/* MIDI 1.0 Channel Control (7bit) */
enum {
	UMP_CC_BANK_SELECT = 0,
	UMP_CC_MODULATION = 1,
	UMP_CC_BREATH = 2,
	UMP_CC_FOOT = 4,
	UMP_CC_PORTAMENTO_TIME = 5,
	UMP_CC_DATA = 6,
	UMP_CC_VOLUME = 7,
	UMP_CC_BALANCE = 8,
	UMP_CC_PAN = 10,
	UMP_CC_EXPRESSION = 11,
	UMP_CC_EFFECT_CONTROL_1 = 12,
	UMP_CC_EFFECT_CONTROL_2 = 13,
	UMP_CC_GP_1 = 16,
	UMP_CC_GP_2 = 17,
	UMP_CC_GP_3 = 18,
	UMP_CC_GP_4 = 19,
	UMP_CC_BANK_SELECT_LSB = 32,
	UMP_CC_MODULATION_LSB = 33,
	UMP_CC_BREATH_LSB = 34,
	UMP_CC_FOOT_LSB = 36,
	UMP_CC_PORTAMENTO_TIME_LSB = 37,
	UMP_CC_DATA_LSB = 38,
	UMP_CC_VOLUME_LSB = 39,
	UMP_CC_BALANCE_LSB = 40,
	UMP_CC_PAN_LSB = 42,
	UMP_CC_EXPRESSION_LSB = 43,
	UMP_CC_EFFECT1_LSB = 44,
	UMP_CC_EFFECT2_LSB = 45,
	UMP_CC_GP_1_LSB = 48,
	UMP_CC_GP_2_LSB = 49,
	UMP_CC_GP_3_LSB = 50,
	UMP_CC_GP_4_LSB = 51,
	UMP_CC_SUSTAIN = 64,
	UMP_CC_PORTAMENTO_SWITCH = 65,
	UMP_CC_SOSTENUTO = 66,
	UMP_CC_SOFT_PEDAL = 67,
	UMP_CC_LEGATO = 68,
	UMP_CC_HOLD_2 = 69,
	UMP_CC_SOUND_CONTROLLER_1 = 70,
	UMP_CC_SOUND_CONTROLLER_2 = 71,
	UMP_CC_SOUND_CONTROLLER_3 = 72,
	UMP_CC_SOUND_CONTROLLER_4 = 73,
	UMP_CC_SOUND_CONTROLLER_5 = 74,
	UMP_CC_SOUND_CONTROLLER_6 = 75,
	UMP_CC_SOUND_CONTROLLER_7 = 76,
	UMP_CC_SOUND_CONTROLLER_8 = 77,
	UMP_CC_SOUND_CONTROLLER_9 = 78,
	UMP_CC_SOUND_CONTROLLER_10 = 79,
	UMP_CC_GP_5 = 80,
	UMP_CC_GP_6 = 81,
	UMP_CC_GP_7 = 82,
	UMP_CC_GP_8 = 83,
	UMP_CC_PORTAMENTO_CONTROL = 84,
	UMP_CC_EFFECT_1 = 91,
	UMP_CC_EFFECT_2 = 92,
	UMP_CC_EFFECT_3 = 93,
	UMP_CC_EFFECT_4 = 94,
	UMP_CC_EFFECT_5 = 95,
	UMP_CC_DATA_INC = 96,
	UMP_CC_DATA_DEC = 97,
	UMP_CC_NRPN_LSB = 98,
	UMP_CC_NRPN_MSB = 99,
	UMP_CC_RPN_LSB = 100,
	UMP_CC_RPN_MSB = 101,
	UMP_CC_ALL_SOUND_OFF = 120,
	UMP_CC_RESET_ALL = 121,
	UMP_CC_LOCAL_CONTROL = 122,
	UMP_CC_ALL_NOTES_OFF = 123,
	UMP_CC_OMNI_OFF = 124,
	UMP_CC_OMNI_ON = 125,
	UMP_CC_POLY_OFF = 126,
	UMP_CC_POLY_ON = 127,
};

/* MIDI 1.0 / 2.0 System Messages (0xfx) */
enum {
	UMP_SYSTEM_STATUS_MIDI_TIME_CODE = 0xf1,
	UMP_SYSTEM_STATUS_SONG_POSITION = 0xf2,
	UMP_SYSTEM_STATUS_SONG_SELECT = 0xf3,
	UMP_SYSTEM_STATUS_TUNE_REQUEST = 0xf6,
	UMP_SYSTEM_STATUS_TIMING_CLOCK = 0xf8,
	UMP_SYSTEM_STATUS_START = 0xfa,
	UMP_SYSTEM_STATUS_CONTINUE = 0xfb,
	UMP_SYSTEM_STATUS_STOP = 0xfc,
	UMP_SYSTEM_STATUS_ACTIVE_SENSING = 0xfe,
	UMP_SYSTEM_STATUS_RESET = 0xff,
};

/* MIDI 1.0 Realtime and SysEx status messages (0xfx) */
enum {
	UMP_MIDI1_MSG_REALTIME		= 0xf0,	/* mask */
	UMP_MIDI1_MSG_SYSEX_START	= 0xf0,
	UMP_MIDI1_MSG_SYSEX_END		= 0xf7,
};

/*
 * UMP Message Definitions
 */

/* MIDI 1.0 Note Off / Note On (32bit) */
struct snd_ump_midi1_msg_note {
#ifdef __BIG_ENDIAN_BITFIELD
	u32 type:4;
	u32 group:4;
	u32 status:4;
	u32 channel:4;
	u32 note:8;
	u32 velocity:8;
#else
	u32 velocity:8;
	u32 note:8;
	u32 channel:4;
	u32 status:4;
	u32 group:4;
	u32 type:4;
#endif
} __packed;

/* MIDI 1.0 Poly Pressure (32bit) */
struct snd_ump_midi1_msg_paf {
#ifdef __BIG_ENDIAN_BITFIELD
	u32 type:4;
	u32 group:4;
	u32 status:4;
	u32 channel:4;
	u32 note:8;
	u32 data:8;
#else
	u32 data:8;
	u32 note:8;
	u32 channel:4;
	u32 status:4;
	u32 group:4;
	u32 type:4;
#endif
} __packed;

/* MIDI 1.0 Control Change (32bit) */
struct snd_ump_midi1_msg_cc {
#ifdef __BIG_ENDIAN_BITFIELD
	u32 type:4;
	u32 group:4;
	u32 status:4;
	u32 channel:4;
	u32 index:8;
	u32 data:8;
#else
	u32 data:8;
	u32 index:8;
	u32 channel:4;
	u32 status:4;
	u32 group:4;
	u32 type:4;
#endif
} __packed;

/* MIDI 1.0 Program Change (32bit) */
struct snd_ump_midi1_msg_program {
#ifdef __BIG_ENDIAN_BITFIELD
	u32 type:4;
	u32 group:4;
	u32 status:4;
	u32 channel:4;
	u32 program:8;
	u32 reserved:8;
#else
#endif
	u32 reserved:8;
	u32 program:8;
	u32 channel:4;
	u32 status:4;
	u32 group:4;
	u32 type:4;
} __packed;

/* MIDI 1.0 Channel Pressure (32bit) */
struct snd_ump_midi1_msg_caf {
#ifdef __BIG_ENDIAN_BITFIELD
	u32 type:4;
	u32 group:4;
	u32 status:4;
	u32 channel:4;
	u32 data:8;
	u32 reserved:8;
#else
	u32 reserved:8;
	u32 data:8;
	u32 channel:4;
	u32 status:4;
	u32 group:4;
	u32 type:4;
#endif
} __packed;

/* MIDI 1.0 Pitch Bend (32bit) */
struct snd_ump_midi1_msg_pitchbend {
#ifdef __BIG_ENDIAN_BITFIELD
	u32 type:4;
	u32 group:4;
	u32 status:4;
	u32 channel:4;
	u32 data_lsb:8;
	u32 data_msb:8;
#else
	u32 data_msb:8;
	u32 data_lsb:8;
	u32 channel:4;
	u32 status:4;
	u32 group:4;
	u32 type:4;
#endif
} __packed;

/* System Common and Real Time messages (32bit); no channel field */
struct snd_ump_system_msg {
#ifdef __BIG_ENDIAN_BITFIELD
	u32 type:4;
	u32 group:4;
	u32 status:8;
	u32 parm1:8;
	u32 parm2:8;
#else
	u32 parm2:8;
	u32 parm1:8;
	u32 status:8;
	u32 group:4;
	u32 type:4;
#endif
} __packed;

/* MIDI 1.0 UMP CVM (32bit) */
union snd_ump_midi1_msg {
	struct snd_ump_midi1_msg_note note;
	struct snd_ump_midi1_msg_paf paf;
	struct snd_ump_midi1_msg_cc cc;
	struct snd_ump_midi1_msg_program pg;
	struct snd_ump_midi1_msg_caf caf;
	struct snd_ump_midi1_msg_pitchbend pb;
	struct snd_ump_system_msg system;
	u32 raw;
};

/* MIDI 2.0 Note Off / Note On (64bit) */
struct snd_ump_midi2_msg_note {
#ifdef __BIG_ENDIAN_BITFIELD
	/* 0 */
	u32 type:4;
	u32 group:4;
	u32 status:4;
	u32 channel:4;
	u32 note:8;
	u32 attribute_type:8;
	/* 1 */
	u32 velocity:16;
	u32 attribute_data:16;
#else
	/* 0 */
	u32 attribute_type:8;
	u32 note:8;
	u32 channel:4;
	u32 status:4;
	u32 group:4;
	u32 type:4;
	/* 1 */
	u32 attribute_data:16;
	u32 velocity:16;
#endif
} __packed;

/* MIDI 2.0 Poly Pressure (64bit) */
struct snd_ump_midi2_msg_paf {
#ifdef __BIG_ENDIAN_BITFIELD
	/* 0 */
	u32 type:4;
	u32 group:4;
	u32 status:4;
	u32 channel:4;
	u32 note:8;
	u32 reserved:8;
	/* 1 */
	u32 data;
#else
	/* 0 */
	u32 reserved:8;
	u32 note:8;
	u32 channel:4;
	u32 status:4;
	u32 group:4;
	u32 type:4;
	/* 1 */
	u32 data;
#endif
} __packed;

/* MIDI 2.0 Per-Note Controller (64bit) */
struct snd_ump_midi2_msg_pernote_cc {
#ifdef __BIG_ENDIAN_BITFIELD
	/* 0 */
	u32 type:4;
	u32 group:4;
	u32 status:4;
	u32 channel:4;
	u32 note:8;
	u32 index:8;
	/* 1 */
	u32 data;
#else
	/* 0 */
	u32 index:8;
	u32 note:8;
	u32 channel:4;
	u32 status:4;
	u32 group:4;
	u32 type:4;
	/* 1 */
	u32 data;
#endif
} __packed;

/* MIDI 2.0 Per-Note Management (64bit) */
struct snd_ump_midi2_msg_pernote_mgmt {
#ifdef __BIG_ENDIAN_BITFIELD
	/* 0 */
	u32 type:4;
	u32 group:4;
	u32 status:4;
	u32 channel:4;
	u32 note:8;
	u32 flags:8;
	/* 1 */
	u32 reserved;
#else
	/* 0 */
	u32 flags:8;
	u32 note:8;
	u32 channel:4;
	u32 status:4;
	u32 group:4;
	u32 type:4;
	/* 1 */
	u32 reserved;
#endif
} __packed;

/* MIDI 2.0 Control Change (64bit) */
struct snd_ump_midi2_msg_cc {
#ifdef __BIG_ENDIAN_BITFIELD
	/* 0 */
	u32 type:4;
	u32 group:4;
	u32 status:4;
	u32 channel:4;
	u32 index:8;
	u32 reserved:8;
	/* 1 */
	u32 data;
#else
	/* 0 */
	u32 reserved:8;
	u32 index:8;
	u32 channel:4;
	u32 status:4;
	u32 group:4;
	u32 type:4;
	/* 1 */
	u32 data;
#endif
} __packed;

/* MIDI 2.0 Registered Controller (RPN) / Assignable Controller (NRPN) (64bit) */
struct snd_ump_midi2_msg_rpn {
#ifdef __BIG_ENDIAN_BITFIELD
	/* 0 */
	u32 type:4;
	u32 group:4;
	u32 status:4;
	u32 channel:4;
	u32 bank:8;
	u32 index:8;
	/* 1 */
	u32 data;
#else
	/* 0 */
	u32 index:8;
	u32 bank:8;
	u32 channel:4;
	u32 status:4;
	u32 group:4;
	u32 type:4;
	/* 1 */
	u32 data;
#endif
} __packed;

/* MIDI 2.0 Program Change (64bit) */
struct snd_ump_midi2_msg_program {
#ifdef __BIG_ENDIAN_BITFIELD
	/* 0 */
	u32 type:4;
	u32 group:4;
	u32 status:4;
	u32 channel:4;
	u32 reserved:15;
	u32 bank_valid:1;
	/* 1 */
	u32 program:8;
	u32 reserved2:8;
	u32 bank_msb:8;
	u32 bank_lsb:8;
#else
	/* 0 */
	u32 bank_valid:1;
	u32 reserved:15;
	u32 channel:4;
	u32 status:4;
	u32 group:4;
	u32 type:4;
	/* 1 */
	u32 bank_lsb:8;
	u32 bank_msb:8;
	u32 reserved2:8;
	u32 program:8;
#endif
} __packed;

/* MIDI 2.0 Channel Pressure (64bit) */
struct snd_ump_midi2_msg_caf {
#ifdef __BIG_ENDIAN_BITFIELD
	/* 0 */
	u32 type:4;
	u32 group:4;
	u32 status:4;
	u32 channel:4;
	u32 reserved:16;
	/* 1 */
	u32 data;
#else
	/* 0 */
	u32 reserved:16;
	u32 channel:4;
	u32 status:4;
	u32 group:4;
	u32 type:4;
	/* 1 */
	u32 data;
#endif
} __packed;

/* MIDI 2.0 Pitch Bend (64bit) */
struct snd_ump_midi2_msg_pitchbend {
#ifdef __BIG_ENDIAN_BITFIELD
	/* 0 */
	u32 type:4;
	u32 group:4;
	u32 status:4;
	u32 channel:4;
	u32 reserved:16;
	/* 1 */
	u32 data;
#else
	/* 0 */
	u32 reserved:16;
	u32 channel:4;
	u32 status:4;
	u32 group:4;
	u32 type:4;
	/* 1 */
	u32 data;
#endif
} __packed;

/* MIDI 2.0 Per-Note Pitch Bend (64bit) */
struct snd_ump_midi2_msg_pernote_pitchbend {
#ifdef __BIG_ENDIAN_BITFIELD
	/* 0 */
	u32 type:4;
	u32 group:4;
	u32 status:4;
	u32 channel:4;
	u32 note:8;
	u32 reserved:8;
	/* 1 */
	u32 data;
#else
	/* 0 */
	u32 reserved:8;
	u32 note:8;
	u32 channel:4;
	u32 status:4;
	u32 group:4;
	u32 type:4;
	/* 1 */
	u32 data;
#endif
} __packed;

/* MIDI 2.0 UMP CVM (64bit) */
union snd_ump_midi2_msg {
	struct snd_ump_midi2_msg_note note;
	struct snd_ump_midi2_msg_paf paf;
	struct snd_ump_midi2_msg_pernote_cc pernote_cc;
	struct snd_ump_midi2_msg_pernote_mgmt pernote_mgmt;
	struct snd_ump_midi2_msg_cc cc;
	struct snd_ump_midi2_msg_rpn rpn;
	struct snd_ump_midi2_msg_program pg;
	struct snd_ump_midi2_msg_caf caf;
	struct snd_ump_midi2_msg_pitchbend pb;
	struct snd_ump_midi2_msg_pernote_pitchbend pernote_pb;
	u32 raw[2];
};

#endif /* __SOUND_UMP_MSG_H */
+9 −0
Original line number Diff line number Diff line
@@ -30,6 +30,15 @@ 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

+1 −0
Original line number Diff line number Diff line
@@ -29,6 +29,7 @@ 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
+255 −3
Original line number Diff line number Diff line
@@ -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");
Loading