diff mbox series

[02/10] ALSA: ump: Support UMP Endpoint and Function Block parsing

Message ID 20230612081054.17200-3-tiwai@suse.de (mailing list archive)
State Accepted
Commit 37e0e14128e0685267dc5c037bf655421a6ce2ea
Headers show
Series ALSA: Catch up MIDI 2.0 updates for UMP 1.1 | expand

Commit Message

Takashi Iwai June 12, 2023, 8:10 a.m. UTC
This patch adds the basic support for UMP Endpoint and UMP Function
Block parsing, which are extended in the new UMP v1.1 spec.
The patch provides a new helper function to perform the query of the
UMP Endpoint information and builds up the UMP blocks based on UMP
Function Block information.  For the communication over the UMP
Endpoint, it opens the rawmidi device once internally, inquiries the
UMP Endpoint and Function Block info by sending new UMP Stream
messages, and waits for the response for each query.

The new UMP spec allows to update the FB info and change its
associated groups or its activeness on the fly, too.  For catching it,
the UMP core keeps watching the incoming UMP messages, and
snd_ump_receive() handles the incoming UMP Stream messages to refresh
the FB info.

Signed-off-by: Takashi Iwai <tiwai@suse.de>
---
 include/sound/ump.h     |  83 +++++++++
 include/sound/ump_msg.h | 225 ++++++++++++++++++++++++
 sound/core/ump.c        | 376 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 684 insertions(+)
diff mbox series

Patch

diff --git a/include/sound/ump.h b/include/sound/ump.h
index e4fdf7cccf12..aef4748842d0 100644
--- a/include/sound/ump.h
+++ b/include/sound/ump.h
@@ -24,6 +24,13 @@  struct snd_ump_endpoint {
 	void *private_data;
 	void (*private_free)(struct snd_ump_endpoint *ump);
 
+	/* UMP Stream message processing */
+	u32 stream_wait_for;	/* expected stream message status */
+	bool stream_finished;	/* set when message has been processed */
+	bool parsed;		/* UMP / FB parse finished? */
+	wait_queue_head_t stream_wait;
+	struct snd_rawmidi_file stream_rfile;
+
 	struct list_head block_list;	/* list of snd_ump_block objects */
 
 	/* intermediate buffer for UMP input */
@@ -80,6 +87,7 @@  struct snd_ump_block {
 int snd_ump_endpoint_new(struct snd_card *card, char *id, int device,
 			 int output, int input,
 			 struct snd_ump_endpoint **ump_ret);
+int snd_ump_parse_endpoint(struct snd_ump_endpoint *ump);
 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);
@@ -109,6 +117,8 @@  enum {
 	UMP_MSG_TYPE_DATA			= 0x03,
 	UMP_MSG_TYPE_MIDI2_CHANNEL_VOICE	= 0x04,
 	UMP_MSG_TYPE_EXTENDED_DATA		= 0x05,
+	UMP_MSG_TYPE_FLEX_DATA			= 0x0d,
+	UMP_MSG_TYPE_STREAM			= 0x0f,
 };
 
 /* MIDI 2.0 SysEx / Data Status; same values for both 7-bit and 8-bit SysEx */
@@ -119,6 +129,62 @@  enum {
 	UMP_SYSEX_STATUS_END			= 3,
 };
 
+/* UMP Utility Type Status (type 0x0) */
+enum {
+	UMP_UTILITY_MSG_STATUS_NOOP		= 0x00,
+	UMP_UTILITY_MSG_STATUS_JR_CLOCK		= 0x01,
+	UMP_UTILITY_MSG_STATUS_JR_TSTAMP	= 0x02,
+	UMP_UTILITY_MSG_STATUS_DCTPQ		= 0x03,
+	UMP_UTILITY_MSG_STATUS_DC		= 0x04,
+};
+
+/* UMP Stream Message Status (type 0xf) */
+enum {
+	UMP_STREAM_MSG_STATUS_EP_DISCOVERY	= 0x00,
+	UMP_STREAM_MSG_STATUS_EP_INFO		= 0x01,
+	UMP_STREAM_MSG_STATUS_DEVICE_INFO	= 0x02,
+	UMP_STREAM_MSG_STATUS_EP_NAME		= 0x03,
+	UMP_STREAM_MSG_STATUS_PRODUCT_ID	= 0x04,
+	UMP_STREAM_MSG_STATUS_STREAM_CFG_REQUEST = 0x05,
+	UMP_STREAM_MSG_STATUS_STREAM_CFG	= 0x06,
+	UMP_STREAM_MSG_STATUS_FB_DISCOVERY	= 0x10,
+	UMP_STREAM_MSG_STATUS_FB_INFO		= 0x11,
+	UMP_STREAM_MSG_STATUS_FB_NAME		= 0x12,
+	UMP_STREAM_MSG_STATUS_START_CLIP	= 0x20,
+	UMP_STREAM_MSG_STATUS_END_CLIP		= 0x21,
+};
+
+/* UMP Endpoint Discovery filter bitmap */
+enum {
+	UMP_STREAM_MSG_REQUEST_EP_INFO		= (1U << 0),
+	UMP_STREAM_MSG_REQUEST_DEVICE_INFO	= (1U << 1),
+	UMP_STREAM_MSG_REQUEST_EP_NAME		= (1U << 2),
+	UMP_STREAM_MSG_REQUEST_PRODUCT_ID	= (1U << 3),
+	UMP_STREAM_MSG_REQUEST_STREAM_CFG	= (1U << 4),
+};
+
+/* UMP Function Block Discovery filter bitmap */
+enum {
+	UMP_STREAM_MSG_REQUEST_FB_INFO		= (1U << 0),
+	UMP_STREAM_MSG_REQUEST_FB_NAME		= (1U << 1),
+};
+
+/* UMP Endpoint Info capability bits (used for protocol request/notify, too) */
+enum {
+	UMP_STREAM_MSG_EP_INFO_CAP_TXJR		= (1U << 0), /* Sending JRTS */
+	UMP_STREAM_MSG_EP_INFO_CAP_RXJR		= (1U << 1), /* Receiving JRTS */
+	UMP_STREAM_MSG_EP_INFO_CAP_MIDI1	= (1U << 8), /* MIDI 1.0 */
+	UMP_STREAM_MSG_EP_INFO_CAP_MIDI2	= (1U << 9), /* MIDI 2.0 */
+};
+
+/* UMP EP / FB name string format; same as SysEx string handling */
+enum {
+	UMP_STREAM_MSG_FORMAT_SINGLE		= 0,
+	UMP_STREAM_MSG_FORMAT_START		= 1,
+	UMP_STREAM_MSG_FORMAT_CONTINUE		= 2,
+	UMP_STREAM_MSG_FORMAT_END		= 3,
+};
+
 /*
  * Helpers for retrieving / filling bits from UMP
  */
@@ -172,4 +238,21 @@  static inline unsigned char ump_sysex_message_length(u32 data)
 	return (data >> 16) & 0xf;
 }
 
+/* For Stream Messages */
+static inline unsigned char ump_stream_message_format(u32 data)
+{
+	return (data >> 26) & 0x03;
+}
+
+static inline unsigned int ump_stream_message_status(u32 data)
+{
+	return (data >> 16) & 0x3ff;
+}
+
+static inline u32 ump_stream_compose(unsigned char status, unsigned short form)
+{
+	return (UMP_MSG_TYPE_STREAM << 28) | ((u32)form << 26) |
+		((u32)status << 16);
+}
+
 #endif /* __SOUND_UMP_H */
diff --git a/include/sound/ump_msg.h b/include/sound/ump_msg.h
index a594ef951b54..72f60ddfea75 100644
--- a/include/sound/ump_msg.h
+++ b/include/sound/ump_msg.h
@@ -537,4 +537,229 @@  union snd_ump_midi2_msg {
 	u32 raw[2];
 };
 
+/* UMP Stream Message: Endpoint Discovery (128bit) */
+struct snd_ump_stream_msg_ep_discovery {
+#ifdef __BIG_ENDIAN_BITFIELD
+	/* 0 */
+	u32 type:4;
+	u32 format:2;
+	u32 status:10;
+	u32 ump_version_major:8;
+	u32 ump_version_minor:8;
+	/* 1 */
+	u32 reserved:24;
+	u32 filter_bitmap:8;
+	/* 2-3 */
+	u32 reserved2[2];
+#else
+	/* 0 */
+	u32 ump_version_minor:8;
+	u32 ump_version_major:8;
+	u32 status:10;
+	u32 format:2;
+	u32 type:4;
+	/* 1 */
+	u32 filter_bitmap:8;
+	u32 reserved:24;
+	/* 2-3 */
+	u32 reserved2[2];
+#endif
+} __packed;
+
+/* UMP Stream Message: Endpoint Info Notification (128bit) */
+struct snd_ump_stream_msg_ep_info {
+#ifdef __BIG_ENDIAN_BITFIELD
+	/* 0 */
+	u32 type:4;
+	u32 format:2;
+	u32 status:10;
+	u32 ump_version_major:8;
+	u32 ump_version_minor:8;
+	/* 1 */
+	u32 static_function_block:1;
+	u32 num_function_blocks:7;
+	u32 reserved:8;
+	u32 protocol:8;
+	u32 reserved2:6;
+	u32 jrts:2;
+	/* 2-3 */
+	u32 reserved3[2];
+#else
+	/* 0 */
+	u32 ump_version_minor:8;
+	u32 ump_version_major:8;
+	u32 status:10;
+	u32 format:2;
+	u32 type:4;
+	/* 1 */
+	u32 jrts:2;
+	u32 reserved2:6;
+	u32 protocol:8;
+	u32 reserved:8;
+	u32 num_function_blocks:7;
+	u32 static_function_block:1;
+	/* 2-3 */
+	u32 reserved3[2];
+#endif
+} __packed;
+
+/* UMP Stream Message: Device Info Notification (128bit) */
+struct snd_ump_stream_msg_devince_info {
+#ifdef __BIG_ENDIAN_BITFIELD
+	/* 0 */
+	u32 type:4;
+	u32 format:2;
+	u32 status:10;
+	u32 reserved:16;
+	/* 1 */
+	u32 manufacture_id;
+	/* 2 */
+	u8 family_lsb;
+	u8 family_msb;
+	u8 model_lsb;
+	u8 model_msb;
+	/* 3 */
+	u32 sw_revision;
+#else
+	/* 0 */
+	u32 reserved:16;
+	u32 status:10;
+	u32 format:2;
+	u32 type:4;
+	/* 1 */
+	u32 manufacture_id;
+	/* 2 */
+	u8 model_msb;
+	u8 model_lsb;
+	u8 family_msb;
+	u8 family_lsb;
+	/* 3 */
+	u32 sw_revision;
+#endif
+} __packed;
+
+/* UMP Stream Message: Stream Config Request / Notification (128bit) */
+struct snd_ump_stream_msg_stream_cfg {
+#ifdef __BIG_ENDIAN_BITFIELD
+	/* 0 */
+	u32 type:4;
+	u32 format:2;
+	u32 status:10;
+	u32 protocol:8;
+	u32 reserved:6;
+	u32 jrts:2;
+	/* 1-3 */
+	u32 reserved2[3];
+#else
+	/* 0 */
+	u32 jrts:2;
+	u32 reserved:6;
+	u32 protocol:8;
+	u32 status:10;
+	u32 format:2;
+	u32 type:4;
+	/* 1-3 */
+	u32 reserved2[3];
+#endif
+} __packed;
+
+/* UMP Stream Message: Function Block Discovery (128bit) */
+struct snd_ump_stream_msg_fb_discovery {
+#ifdef __BIG_ENDIAN_BITFIELD
+	/* 0 */
+	u32 type:4;
+	u32 format:2;
+	u32 status:10;
+	u32 function_block_id:8;
+	u32 filter:8;
+	/* 1-3 */
+	u32 reserved[3];
+#else
+	/* 0 */
+	u32 filter:8;
+	u32 function_block_id:8;
+	u32 status:10;
+	u32 format:2;
+	u32 type:4;
+	/* 1-3 */
+	u32 reserved[3];
+#endif
+} __packed;
+
+/* UMP Stream Message: Function Block Info Notification (128bit) */
+struct snd_ump_stream_msg_fb_info {
+#ifdef __BIG_ENDIAN_BITFIELD
+	/* 0 */
+	u32 type:4;
+	u32 format:2;
+	u32 status:10;
+	u32 active:1;
+	u32 function_block_id:7;
+	u32 reserved:2;
+	u32 ui_hint:2;
+	u32 midi_10:2;
+	u32 direction:2;
+	/* 1 */
+	u32 first_group:8;
+	u32 num_groups:8;
+	u32 midi_ci_version:8;
+	u32 sysex8_streams:8;
+	/* 2-3 */
+	u32 reserved2[2];
+#else
+	/* 0 */
+	u32 direction:2;
+	u32 midi_10:2;
+	u32 ui_hint:2;
+	u32 reserved:2;
+	u32 function_block_id:7;
+	u32 active:1;
+	u32 status:10;
+	u32 format:2;
+	u32 type:4;
+	/* 1 */
+	u32 sysex8_streams:8;
+	u32 midi_ci_version:8;
+	u32 num_groups:8;
+	u32 first_group:8;
+	/* 2-3 */
+	u32 reserved2[2];
+#endif
+} __packed;
+
+/* UMP Stream Message: Function Block Name Notification (128bit) */
+struct snd_ump_stream_msg_fb_name {
+#ifdef __BIG_ENDIAN_BITFIELD
+	/* 0 */
+	u16 type:4;
+	u16 format:2;
+	u16 status:10;
+	u8 function_block_id;
+	u8 name0;
+	/* 1-3 */
+	u8 name[12];
+#else
+	/* 0 */
+	u8 name0;
+	u8 function_block_id;
+	u16 status:10;
+	u16 format:2;
+	u16 type:4;
+	/* 1-3 */
+	u8 name[12]; // FIXME: byte order
+#endif
+} __packed;
+
+/* MIDI 2.0 Stream Messages (128bit) */
+union snd_ump_stream_msg {
+	struct snd_ump_stream_msg_ep_discovery ep_discovery;
+	struct snd_ump_stream_msg_ep_info ep_info;
+	struct snd_ump_stream_msg_devince_info device_info;
+	struct snd_ump_stream_msg_stream_cfg stream_cfg;
+	struct snd_ump_stream_msg_fb_discovery fb_discovery;
+	struct snd_ump_stream_msg_fb_info fb_info;
+	struct snd_ump_stream_msg_fb_name fb_name;
+	u32 raw[4];
+};
+
 #endif /* __SOUND_UMP_MSG_H */
diff --git a/sound/core/ump.c b/sound/core/ump.c
index 839873fb0f33..7df50f0affe9 100644
--- a/sound/core/ump.c
+++ b/sound/core/ump.c
@@ -30,6 +30,8 @@  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);
@@ -133,6 +135,7 @@  int snd_ump_endpoint_new(struct snd_card *card, char *id, int device,
 		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]);
@@ -302,6 +305,7 @@  int snd_ump_receive(struct snd_ump_endpoint *ump, const u32 *buffer, int count)
 		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);
@@ -513,6 +517,378 @@  static void snd_ump_proc_read(struct snd_info_entry *entry,
 	}
 }
 
+/*
+ * 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;
+	}
+
+	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);
+}
+
+/* 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)
+{
+	ump->info.protocol =
+		(buf->stream_cfg.protocol << 8) | buf->stream_cfg.jrts;
+	ump_dbg(ump, "Current protocol = %x (caps = %x)\n",
+		ump->info.protocol, ump->info.protocol_caps);
+	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);
+}
+
+/* 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);
+	if (fb) {
+		fill_fb_info(ump, &fb->info, buf);
+	} else if (ump->parsed) {
+		/* complain only if updated after parsing */
+		ump_info(ump, "Function Block Info Update for non-existing block %d\n",
+			 blk);
+		return -ENODEV;
+	}
+	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;
+
+	blk = buf->fb_name.function_block_id;
+	fb = snd_ump_get_block(ump, blk);
+	if (!fb)
+		return -ENODEV;
+
+	return ump_append_string(ump, fb->info.name, sizeof(fb->info.name),
+				 buf->raw, 3);
+}
+
+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;
+
+	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