linux-usb.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 0/7] usb: Add USB MIDI 2.0 Gadget Function Driver
@ 2023-07-25  6:21 Takashi Iwai
  2023-07-25  6:22 ` [PATCH 1/7] usb: gadget: Add support for USB MIDI 2.0 function driver Takashi Iwai
                   ` (7 more replies)
  0 siblings, 8 replies; 9+ messages in thread
From: Takashi Iwai @ 2023-07-25  6:21 UTC (permalink / raw)
  To: Greg Kroah-Hartman; +Cc: alsa-devel, linux-usb, linux-kernel

Hi,

This is a patch set for adding a USB gadget function driver for USB
MIDI 2.0 interface.  The MIDI 2.0 support in the host side was already
enabled since 6.5 kernel in snd-usb-audio driver.  Now, this provides
a similar gadget functionality using the existing ALSA UMP helper
functions.

The gadget driver provides both MIDI 2.0 and MIDI 1.0 fallback
interfaces with one or more loopback UMP rawmidi devices.  The
configfs implementation is flexible enough to allow users to set up
multiple UMP Endpoints and Function Blocks.

The first patch adds the bare f_midi2 driver without configfs support,
followed by the configfs support, MIDI 1.0 interface support and a few
other extensions and documentation.


Takashi

===

Takashi Iwai (7):
  usb: gadget: Add support for USB MIDI 2.0 function driver
  usb: gadget: midi2: Add configfs support
  usb: gadget: midi2: Dynamically create MIDI 1.0 altset descriptors
  usb: gadget: midi2: MIDI 1.0 interface (altset 0) support
  usb: gadget: midi2: Add testing documentation
  usb: gadget: midi2: Add "Operation Mode" control
  usb: gadget: midi2: More flexible MIDI 1.0 configuration

 .../ABI/testing/configfs-usb-gadget-midi2     |   54 +
 Documentation/usb/gadget-testing.rst          |  154 +
 drivers/usb/gadget/Kconfig                    |   18 +
 drivers/usb/gadget/function/Makefile          |    2 +
 drivers/usb/gadget/function/f_midi2.c         | 2883 +++++++++++++++++
 drivers/usb/gadget/function/u_midi2.h         |   81 +
 6 files changed, 3192 insertions(+)
 create mode 100644 Documentation/ABI/testing/configfs-usb-gadget-midi2
 create mode 100644 drivers/usb/gadget/function/f_midi2.c
 create mode 100644 drivers/usb/gadget/function/u_midi2.h

-- 
2.35.3


^ permalink raw reply	[flat|nested] 9+ messages in thread

* [PATCH 1/7] usb: gadget: Add support for USB MIDI 2.0 function driver
  2023-07-25  6:21 [PATCH 0/7] usb: Add USB MIDI 2.0 Gadget Function Driver Takashi Iwai
@ 2023-07-25  6:22 ` Takashi Iwai
  2023-07-25  6:22 ` [PATCH 2/7] usb: gadget: midi2: Add configfs support Takashi Iwai
                   ` (6 subsequent siblings)
  7 siblings, 0 replies; 9+ messages in thread
From: Takashi Iwai @ 2023-07-25  6:22 UTC (permalink / raw)
  To: Greg Kroah-Hartman; +Cc: alsa-devel, linux-usb, linux-kernel

This patch adds the support for USB MIDI 2.0 gadget function driver.
The driver emulates a USB MIDI 2.0 interface with one or more UMP
Endpoints, where each of UMP Endpoint is a pair of MIDI Endpoints for
handling MIDI 2.0 UMP packets.  When the function driver is bound, the
driver creates an ALSA card object with UMP rawmidi devices.  This is
a kind of loop-back where the incoming and upcoming UMP packets
from/to the MIDI 2.0 UMP Endpoints are transferred as-is.  In
addition, legacy (MIDI 1.0) rawmidi devices are created, so that
legacy applications can work in the gadget side, too.

When a USB MIDI 2.0 gadget interface appears, the connected host can
use it with the snd-usb-audio driver where MIDI 2.0 support is
enabled.  Both gadget and connected hosts will have the similar UMP
Endpoint and Function Block (or Group Terminal Block) information.
Slight differences are the direction and UI-hint bits; it's due to the
nature of gadget driver, and the input/output direction is swapped in
both sides (the input for gadget is the output for host, and vice
versa).

The driver supports the brand-new UMP v1.1 feature, including the UMP
Stream message handling for providing UMP Endpoint and Function Block
information as well as dealing with the MIDI protocol switch.  The
driver responds to UMP Stream messages by itself.  OTOH, MIDI-CI
message handling isn't implemented in the kernel driver; it should be
processed in the user-space through the loopback UMP device.

As of this patch, the whole configuration is fixed, providing only one
bidirectional UMP Endpoint containing a single FB/GTB with a single
UMP Group.  The configuration will be dynamically changeable in the
following patches.

The traditional MIDI 1.0 is still provided in the altset 0 (which is
mandatory per spec).  But it's only about the configuration, and no
actual I/O will be running for the altset 0 as of this patch.  The
proper support MIDI 1.0 altset will follow in later patches, too.

Signed-off-by: Takashi Iwai <tiwai@suse.de>
---
 drivers/usb/gadget/Kconfig            |   18 +
 drivers/usb/gadget/function/Makefile  |    2 +
 drivers/usb/gadget/function/f_midi2.c | 1691 +++++++++++++++++++++++++
 drivers/usb/gadget/function/u_midi2.h |   79 ++
 4 files changed, 1790 insertions(+)
 create mode 100644 drivers/usb/gadget/function/f_midi2.c
 create mode 100644 drivers/usb/gadget/function/u_midi2.h

diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig
index 336db8f92afa..b3592bcb0f96 100644
--- a/drivers/usb/gadget/Kconfig
+++ b/drivers/usb/gadget/Kconfig
@@ -208,6 +208,9 @@ config USB_F_UVC
 config USB_F_MIDI
 	tristate
 
+config USB_F_MIDI2
+	tristate
+
 config USB_F_HID
 	tristate
 
@@ -436,6 +439,21 @@ config USB_CONFIGFS_F_MIDI
 	  connections can then be made on the gadget system, using
 	  ALSA's aconnect utility etc.
 
+config USB_CONFIGFS_F_MIDI2
+	bool "MIDI 2.0 function"
+	depends on USB_CONFIGFS
+	depends on SND
+	select USB_LIBCOMPOSITE
+	select SND_UMP
+	select SND_UMP_LEGACY_RAWMIDI
+	select USB_F_MIDI2
+	help
+	  The MIDI 2.0 function driver provides the generic emulated
+	  USB MIDI 2.0 interface, looped back to ALSA UMP rawmidi
+	  device on the gadget host. It supports UMP 1.1 spec and
+	  responds UMP Stream messages for UMP Endpoint and Function
+	  Block information / configuration.
+
 config USB_CONFIGFS_F_HID
 	bool "HID function"
 	depends on USB_CONFIGFS
diff --git a/drivers/usb/gadget/function/Makefile b/drivers/usb/gadget/function/Makefile
index 5d3a6cf02218..87917a7d4a9b 100644
--- a/drivers/usb/gadget/function/Makefile
+++ b/drivers/usb/gadget/function/Makefile
@@ -44,6 +44,8 @@ usb_f_uvc-y			:= f_uvc.o uvc_queue.o uvc_v4l2.o uvc_video.o uvc_configfs.o
 obj-$(CONFIG_USB_F_UVC)		+= usb_f_uvc.o
 usb_f_midi-y			:= f_midi.o
 obj-$(CONFIG_USB_F_MIDI)	+= usb_f_midi.o
+usb_f_midi2-y			:= f_midi2.o
+obj-$(CONFIG_USB_F_MIDI2)	+= usb_f_midi2.o
 usb_f_hid-y			:= f_hid.o
 obj-$(CONFIG_USB_F_HID)		+= usb_f_hid.o
 usb_f_printer-y			:= f_printer.o
diff --git a/drivers/usb/gadget/function/f_midi2.c b/drivers/usb/gadget/function/f_midi2.c
new file mode 100644
index 000000000000..848cb3150deb
--- /dev/null
+++ b/drivers/usb/gadget/function/f_midi2.c
@@ -0,0 +1,1691 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * f_midi2.c -- USB MIDI 2.0 class function driver
+ */
+
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+
+#include <sound/core.h>
+#include <sound/ump.h>
+#include <sound/ump_msg.h>
+
+#include <linux/usb/ch9.h>
+#include <linux/usb/gadget.h>
+#include <linux/usb/audio.h>
+#include <linux/usb/midi-v2.h>
+
+#include "u_f.h"
+#include "u_midi2.h"
+
+struct f_midi2;
+struct f_midi2_ep;
+struct f_midi2_usb_ep;
+
+/* Context for each USB request */
+struct f_midi2_req_ctx {
+	struct f_midi2_usb_ep *usb_ep;	/* belonging USB EP */
+	unsigned int index;		/* array index: 0-31 */
+	struct usb_request *req;	/* assigned request */
+};
+
+/* Resources for a USB Endpoint */
+struct f_midi2_usb_ep {
+	struct f_midi2 *card;		/* belonging card */
+	struct f_midi2_ep *ep;		/* belonging UMP EP (optional) */
+	struct usb_ep *usb_ep;		/* assigned USB EP */
+	void (*complete)(struct usb_ep *usb_ep, struct usb_request *req);
+	unsigned long free_reqs;	/* bitmap for unused requests */
+	unsigned int num_reqs;		/* number of allocated requests */
+	struct f_midi2_req_ctx *reqs;	/* request context array */
+};
+
+/* Resources for UMP Function Block (and USB Group Terminal Block) */
+struct f_midi2_block {
+	struct f_midi2_block_info info;	/* FB info, copied from configfs */
+	struct snd_ump_block *fb;	/* assigned FB */
+	unsigned int gtb_id;		/* assigned GTB id */
+	unsigned int string_id;		/* assigned string id */
+};
+
+/* Resources for UMP Endpoint */
+struct f_midi2_ep {
+	struct snd_ump_endpoint *ump;	/* assigned UMP EP */
+	struct f_midi2 *card;		/* belonging MIDI 2.0 device */
+
+	struct f_midi2_ep_info info;	/* UMP EP info, copied from configfs */
+	unsigned int num_blks;		/* number of FBs */
+	struct f_midi2_block blks[SNDRV_UMP_MAX_BLOCKS];	/* UMP FBs */
+
+	struct f_midi2_usb_ep ep_in;	/* USB MIDI EP-in */
+	struct f_midi2_usb_ep ep_out;	/* USB MIDI EP-out */
+};
+
+/* indices for USB strings */
+enum {
+	STR_IFACE = 0,
+	STR_GTB1 = 1,
+};
+
+/* 1-based GTB id to string id */
+#define gtb_to_str_id(id)	(STR_GTB1 + (id) - 1)
+
+/* operation mode */
+enum {
+	MIDI_OP_MODE_UNSET,	/* no altset set yet */
+	MIDI_OP_MODE_MIDI1,	/* MIDI 1.0 (altset 0) is used */
+	MIDI_OP_MODE_MIDI2,	/* MIDI 2.0 (altset 1) is used */
+};
+
+/* Resources for MIDI 2.0 Device */
+struct f_midi2 {
+	struct usb_function func;
+	struct usb_gadget *gadget;
+	struct snd_card *card;
+
+	/* MIDI 1.0 in/out USB EPs */
+	struct f_midi2_usb_ep midi1_ep_in;
+	struct f_midi2_usb_ep midi1_ep_out;
+
+	int midi_if;			/* USB MIDI interface number */
+	int operation_mode;		/* current operation mode */
+
+	spinlock_t queue_lock;
+
+	struct f_midi2_card_info info;	/* card info, copied from configfs */
+
+	unsigned int num_eps;
+	struct f_midi2_ep midi2_eps[MAX_UMP_EPS];
+
+	unsigned int total_blocks;	/* total number of blocks of all EPs */
+	struct usb_string *string_defs;
+	struct usb_string *strings;
+};
+
+#define func_to_midi2(f)	container_of(f, struct f_midi2, func)
+
+/* get EP name string */
+static const char *ump_ep_name(const struct f_midi2_ep *ep)
+{
+	return ep->info.ep_name ? ep->info.ep_name : "MIDI 2.0 Gadget";
+}
+
+/* get EP product ID string */
+static const char *ump_product_id(const struct f_midi2_ep *ep)
+{
+	return ep->info.product_id ? ep->info.product_id : "Unique Product ID";
+}
+
+/* get FB name string */
+static const char *ump_fb_name(const struct f_midi2_block_info *info)
+{
+	return info->name ? info->name : "MIDI 2.0 Gadget I/O";
+}
+
+/*
+ * USB Descriptor Definitions
+ */
+/* GTB header descriptor */
+static struct usb_ms20_gr_trm_block_header_descriptor gtb_header_desc = {
+	.bLength =		sizeof(gtb_header_desc),
+	.bDescriptorType =	USB_DT_CS_GR_TRM_BLOCK,
+	.bDescriptorSubtype =	USB_MS_GR_TRM_BLOCK_HEADER,
+	.wTotalLength =		__cpu_to_le16(0x12), // to be filled
+};
+
+/* GTB descriptor template: most items are replaced dynamically */
+static struct usb_ms20_gr_trm_block_descriptor gtb_desc = {
+	.bLength =		sizeof(gtb_desc),
+	.bDescriptorType =	USB_DT_CS_GR_TRM_BLOCK,
+	.bDescriptorSubtype =	USB_MS_GR_TRM_BLOCK,
+	.bGrpTrmBlkID =		0x01,
+	.bGrpTrmBlkType =	USB_MS_GR_TRM_BLOCK_TYPE_BIDIRECTIONAL,
+	.nGroupTrm =		0x00,
+	.nNumGroupTrm =		1,
+	.iBlockItem =		0,
+	.bMIDIProtocol =	USB_MS_MIDI_PROTO_1_0_64,
+	.wMaxInputBandwidth =	0,
+	.wMaxOutputBandwidth =	0,
+};
+
+DECLARE_USB_MIDI_OUT_JACK_DESCRIPTOR(1);
+DECLARE_USB_MS_ENDPOINT_DESCRIPTOR(1);
+DECLARE_UAC_AC_HEADER_DESCRIPTOR(1);
+DECLARE_USB_MS20_ENDPOINT_DESCRIPTOR(32);
+
+#define EP_MAX_PACKET_INT	8
+
+/* Audio Control Interface */
+static struct usb_interface_descriptor midi2_audio_if_desc = {
+	.bLength =		USB_DT_INTERFACE_SIZE,
+	.bDescriptorType =	USB_DT_INTERFACE,
+	.bInterfaceNumber =	0, // to be filled
+	.bNumEndpoints =	0,
+	.bInterfaceClass =	USB_CLASS_AUDIO,
+	.bInterfaceSubClass =	USB_SUBCLASS_AUDIOCONTROL,
+	.bInterfaceProtocol =	0,
+	.iInterface =		0,
+};
+
+static struct uac1_ac_header_descriptor_1 midi2_audio_class_desc = {
+	.bLength =		0x09,
+	.bDescriptorType =	USB_DT_CS_INTERFACE,
+	.bDescriptorSubtype =	0x01,
+	.bcdADC =		__cpu_to_le16(0x0100),
+	.wTotalLength =		__cpu_to_le16(0x0009),
+	.bInCollection =	0x01,
+	.baInterfaceNr =	{ 0x01 }, // to be filled
+};
+
+/* MIDI 1.0 Streaming Interface (altset 0) */
+static struct usb_interface_descriptor midi2_midi1_if_desc = {
+	.bLength =		USB_DT_INTERFACE_SIZE,
+	.bDescriptorType =	USB_DT_INTERFACE,
+	.bInterfaceNumber =	0, // to be filled
+	.bAlternateSetting =	0,
+	.bNumEndpoints =	2,
+	.bInterfaceClass =	USB_CLASS_AUDIO,
+	.bInterfaceSubClass =	USB_SUBCLASS_MIDISTREAMING,
+	.bInterfaceProtocol =	0,
+	.iInterface =		0, // to be filled
+};
+
+static struct usb_ms_header_descriptor midi2_midi1_class_desc = {
+	.bLength =		0x07,
+	.bDescriptorType =	USB_DT_CS_INTERFACE,
+	.bDescriptorSubtype =	USB_MS_HEADER,
+	.bcdMSC =		__cpu_to_le16(0x0100),
+	.wTotalLength =		__cpu_to_le16(0x41), // to be calculated
+};
+
+/* MIDI 1.0 IN (Embedded) Jack */
+static struct usb_midi_in_jack_descriptor midi2_midi1_in_jack1_desc = {
+	.bLength =		0x06,
+	.bDescriptorType =	USB_DT_CS_INTERFACE,
+	.bDescriptorSubtype =	USB_MS_MIDI_IN_JACK,
+	.bJackType =		USB_MS_EMBEDDED,
+	.bJackID =		0x01,
+	.iJack =		0,
+};
+
+/* MIDI 1.0 IN (External) Jack */
+static struct usb_midi_in_jack_descriptor midi2_midi1_in_jack2_desc = {
+	.bLength =		0x06,
+	.bDescriptorType =	USB_DT_CS_INTERFACE,
+	.bDescriptorSubtype =	USB_MS_MIDI_IN_JACK,
+	.bJackType =		USB_MS_EXTERNAL,
+	.bJackID =		0x02,
+	.iJack =		0,
+};
+
+/* MIDI 1.0 OUT (Embedded) Jack */
+static struct usb_midi_out_jack_descriptor_1 midi2_midi1_out_jack1_desc = {
+	.bLength =		0x09,
+	.bDescriptorType =	USB_DT_CS_INTERFACE,
+	.bDescriptorSubtype =	USB_MS_MIDI_OUT_JACK,
+	.bJackType =		USB_MS_EMBEDDED,
+	.bJackID =		0x03,
+	.bNrInputPins =		1,
+	.pins =			{ { 0x02, 0x01 } },
+	.iJack =		0,
+};
+
+/* MIDI 1.0 OUT (External) Jack */
+static struct usb_midi_out_jack_descriptor_1 midi2_midi1_out_jack2_desc = {
+	.bLength =		0x09,
+	.bDescriptorType =	USB_DT_CS_INTERFACE,
+	.bDescriptorSubtype =	USB_MS_MIDI_OUT_JACK,
+	.bJackType =		USB_MS_EXTERNAL,
+	.bJackID =		0x04,
+	.bNrInputPins =		1,
+	.pins =			{ { 0x01, 0x01 } },
+	.iJack =		0,
+};
+
+/* MIDI 1.0 EP OUT */
+static struct usb_endpoint_descriptor midi2_midi1_ep_out_desc = {
+	.bLength =		USB_DT_ENDPOINT_AUDIO_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+	.bEndpointAddress =	USB_DIR_OUT | 0, // set up dynamically
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+};
+
+static struct usb_ss_ep_comp_descriptor midi2_midi1_ep_out_ss_comp_desc = {
+	.bLength                = sizeof(midi2_midi1_ep_out_ss_comp_desc),
+	.bDescriptorType        = USB_DT_SS_ENDPOINT_COMP,
+};
+
+static struct usb_ms_endpoint_descriptor_1 midi2_midi1_ep_out_class_desc = {
+	.bLength =		0x05,
+	.bDescriptorType =	USB_DT_CS_ENDPOINT,
+	.bDescriptorSubtype =	USB_MS_GENERAL,
+	.bNumEmbMIDIJack =	1,
+	.baAssocJackID =	{ 0x01 },
+};
+
+/* MIDI 1.0 EP IN */
+static struct usb_endpoint_descriptor midi2_midi1_ep_in_desc = {
+	.bLength =		USB_DT_ENDPOINT_AUDIO_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+	.bEndpointAddress =	USB_DIR_IN | 0, // set up dynamically
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+};
+
+static struct usb_ss_ep_comp_descriptor midi2_midi1_ep_in_ss_comp_desc = {
+	.bLength                = sizeof(midi2_midi1_ep_in_ss_comp_desc),
+	.bDescriptorType        = USB_DT_SS_ENDPOINT_COMP,
+};
+
+static struct usb_ms_endpoint_descriptor_1 midi2_midi1_ep_in_class_desc = {
+	.bLength =		0x05,
+	.bDescriptorType =	USB_DT_CS_ENDPOINT,
+	.bDescriptorSubtype =	USB_MS_GENERAL,
+	.bNumEmbMIDIJack =	1,
+	.baAssocJackID =	{ 0x03 },
+};
+
+/* MIDI 2.0 Streaming Interface (altset 1) */
+static struct usb_interface_descriptor midi2_midi2_if_desc = {
+	.bLength =		USB_DT_INTERFACE_SIZE,
+	.bDescriptorType =	USB_DT_INTERFACE,
+	.bInterfaceNumber =	0, // to be filled
+	.bAlternateSetting =	1,
+	.bNumEndpoints =	2,
+	.bInterfaceClass =	USB_CLASS_AUDIO,
+	.bInterfaceSubClass =	USB_SUBCLASS_MIDISTREAMING,
+	.bInterfaceProtocol =	0,
+	.iInterface =		0, // to be filled
+};
+
+static struct usb_ms_header_descriptor midi2_midi2_class_desc = {
+	.bLength =		0x07,
+	.bDescriptorType =	USB_DT_CS_INTERFACE,
+	.bDescriptorSubtype =	USB_MS_HEADER,
+	.bcdMSC =		__cpu_to_le16(0x0200),
+	.wTotalLength =		__cpu_to_le16(0x07),
+};
+
+/* MIDI 2.0 EP OUT */
+static struct usb_endpoint_descriptor midi2_midi2_ep_out_desc[MAX_UMP_EPS];
+
+static struct usb_ss_ep_comp_descriptor midi2_midi2_ep_out_ss_comp_desc = {
+	.bLength                = sizeof(midi2_midi1_ep_out_ss_comp_desc),
+	.bDescriptorType        = USB_DT_SS_ENDPOINT_COMP,
+};
+
+static struct usb_ms20_endpoint_descriptor_32 midi2_midi2_ep_out_class_desc[MAX_UMP_EPS];
+
+/* MIDI 2.0 EP IN */
+static struct usb_endpoint_descriptor midi2_midi2_ep_in_desc[MAX_UMP_EPS];
+
+static struct usb_ss_ep_comp_descriptor midi2_midi2_ep_in_ss_comp_desc = {
+	.bLength                = sizeof(midi2_midi2_ep_in_ss_comp_desc),
+	.bDescriptorType        = USB_DT_SS_ENDPOINT_COMP,
+};
+
+static struct usb_ms20_endpoint_descriptor_32 midi2_midi2_ep_in_class_desc[MAX_UMP_EPS];
+
+/* Arrays of descriptors to be created */
+static void *midi2_audio_descs[] = {
+	&midi2_audio_if_desc,
+	&midi2_audio_class_desc,
+	NULL
+};
+
+static void *midi2_midi1_descs[] = {
+	&midi2_midi1_if_desc,
+	&midi2_midi1_class_desc,
+	&midi2_midi1_in_jack1_desc,
+	&midi2_midi1_in_jack2_desc,
+	&midi2_midi1_out_jack1_desc,
+	&midi2_midi1_out_jack2_desc,
+	NULL
+};
+
+static void *midi2_midi1_ep_descs[] = {
+	&midi2_midi1_ep_out_desc,
+	&midi2_midi1_ep_out_class_desc,
+	&midi2_midi1_ep_in_desc,
+	&midi2_midi1_ep_in_class_desc,
+	NULL
+};
+
+static void *midi2_midi1_ep_ss_descs[] = {
+	&midi2_midi1_ep_out_desc,
+	&midi2_midi1_ep_out_ss_comp_desc,
+	&midi2_midi1_ep_out_class_desc,
+	&midi2_midi1_ep_in_desc,
+	&midi2_midi1_ep_in_ss_comp_desc,
+	&midi2_midi1_ep_in_class_desc,
+	NULL
+};
+
+static void *midi2_midi2_descs[] = {
+	&midi2_midi2_if_desc,
+	&midi2_midi2_class_desc,
+	NULL
+};
+
+/*
+ * USB request handling
+ */
+
+/* get an empty request for the given EP */
+static struct usb_request *get_empty_request(struct f_midi2_usb_ep *usb_ep)
+{
+	struct usb_request *req = NULL;
+	unsigned long flags;
+	int index;
+
+	spin_lock_irqsave(&usb_ep->card->queue_lock, flags);
+	if (!usb_ep->free_reqs)
+		goto unlock;
+	index = find_first_bit(&usb_ep->free_reqs, usb_ep->num_reqs);
+	if (index >= usb_ep->num_reqs)
+		goto unlock;
+	req = usb_ep->reqs[index].req;
+	if (!req)
+		goto unlock;
+	clear_bit(index, &usb_ep->free_reqs);
+	req->length = 0;
+ unlock:
+	spin_unlock_irqrestore(&usb_ep->card->queue_lock, flags);
+	return req;
+}
+
+/* put the empty request back */
+static void put_empty_request(struct usb_request *req)
+{
+	struct f_midi2_req_ctx *ctx = req->context;
+	unsigned long flags;
+
+	spin_lock_irqsave(&ctx->usb_ep->card->queue_lock, flags);
+	set_bit(ctx->index, &ctx->usb_ep->free_reqs);
+	spin_unlock_irqrestore(&ctx->usb_ep->card->queue_lock, flags);
+}
+
+/*
+ * UMP v1.1 Stream message handling
+ */
+
+/* queue a request to UMP EP; request is either queued or freed after this */
+static int queue_request_ep_raw(struct usb_request *req)
+{
+	struct f_midi2_req_ctx *ctx = req->context;
+	int err;
+
+	req->complete = ctx->usb_ep->complete;
+	err = usb_ep_queue(ctx->usb_ep->usb_ep, req, GFP_ATOMIC);
+	if (err) {
+		put_empty_request(req);
+		return err;
+	}
+	return 0;
+}
+
+/* queue a request with endianness conversion */
+static int queue_request_ep_in(struct usb_request *req)
+{
+	/* UMP packets have to be converted to little-endian */
+	cpu_to_le32_array((u32 *)req->buf, req->length >> 2);
+	return queue_request_ep_raw(req);
+}
+
+/* reply a UMP packet via EP-in */
+static int reply_ep_in(struct f_midi2_ep *ep, const void *buf, int len)
+{
+	struct f_midi2_usb_ep *usb_ep = &ep->ep_in;
+	struct usb_request *req;
+
+	req = get_empty_request(usb_ep);
+	if (!req)
+		return -ENOSPC;
+
+	req->length = len;
+	memcpy(req->buf, buf, len);
+	return queue_request_ep_in(req);
+}
+
+/* reply a UMP stream EP info */
+static void reply_ump_stream_ep_info(struct f_midi2_ep *ep)
+{
+	struct snd_ump_stream_msg_ep_info rep = {
+		.type = UMP_MSG_TYPE_STREAM,
+		.status = UMP_STREAM_MSG_STATUS_EP_INFO,
+		.ump_version_major = 0x01,
+		.ump_version_minor = 0x01,
+		.num_function_blocks = ep->num_blks,
+		.static_function_block = !!ep->card->info.static_block,
+		.protocol = (UMP_STREAM_MSG_EP_INFO_CAP_MIDI1 |
+			     UMP_STREAM_MSG_EP_INFO_CAP_MIDI2) >> 8,
+	};
+
+	reply_ep_in(ep, &rep, sizeof(rep));
+}
+
+/* reply a UMP EP device info */
+static void reply_ump_stream_ep_device(struct f_midi2_ep *ep)
+{
+	struct snd_ump_stream_msg_devince_info rep = {
+		.type = UMP_MSG_TYPE_STREAM,
+		.status = UMP_STREAM_MSG_STATUS_DEVICE_INFO,
+		.manufacture_id = ep->info.manufacturer,
+		.family_lsb = ep->info.family & 0xff,
+		.family_msb = (ep->info.family >> 8) & 0xff,
+		.model_lsb = ep->info.model & 0xff,
+		.model_msb = (ep->info.model >> 8) & 0xff,
+		.sw_revision = ep->info.sw_revision,
+	};
+
+	reply_ep_in(ep, &rep, sizeof(rep));
+}
+
+#define UMP_STREAM_PKT_BYTES	16	/* UMP stream packet size = 16 bytes*/
+#define UMP_STREAM_EP_STR_OFF	2	/* offset of name string for EP info */
+#define UMP_STREAM_FB_STR_OFF	3	/* offset of name string for FB info */
+
+/* Helper to replay a string */
+static void reply_ump_stream_string(struct f_midi2_ep *ep, const u8 *name,
+				    unsigned int type, unsigned int extra,
+				    unsigned int start_ofs)
+{
+	struct f_midi2_usb_ep *usb_ep = &ep->ep_in;
+	struct f_midi2 *midi2 = ep->card;
+	struct usb_request *req;
+	unsigned int pos;
+	u32 *buf;
+
+	if (!*name)
+		return;
+	req = get_empty_request(usb_ep);
+	if (!req)
+		return;
+
+	buf = (u32 *)req->buf;
+	pos = start_ofs;
+	for (;;) {
+		if (pos == start_ofs) {
+			memset(buf, 0, UMP_STREAM_PKT_BYTES);
+			buf[0] = ump_stream_compose(type, 0) | extra;
+		}
+		buf[pos / 4] |= *name++ << ((3 - (pos % 4)) * 8);
+		if (!*name) {
+			if (req->length)
+				buf[0] |= UMP_STREAM_MSG_FORMAT_END << 26;
+			req->length += UMP_STREAM_PKT_BYTES;
+			break;
+		}
+		if (++pos == UMP_STREAM_PKT_BYTES) {
+			if (!req->length)
+				buf[0] |= UMP_STREAM_MSG_FORMAT_START << 26;
+			else
+				buf[0] |= UMP_STREAM_MSG_FORMAT_CONTINUE << 26;
+			req->length += UMP_STREAM_PKT_BYTES;
+			if (midi2->info.req_buf_size - req->length < UMP_STREAM_PKT_BYTES)
+				break;
+			buf += 4;
+			pos = start_ofs;
+		}
+	}
+
+	if (req->length)
+		queue_request_ep_in(req);
+	else
+		put_empty_request(req);
+}
+
+/* Reply a UMP EP name string */
+static void reply_ump_stream_ep_name(struct f_midi2_ep *ep)
+{
+	reply_ump_stream_string(ep, ump_ep_name(ep),
+				UMP_STREAM_MSG_STATUS_EP_NAME, 0,
+				UMP_STREAM_EP_STR_OFF);
+}
+
+/* Reply a UMP EP product ID string */
+static void reply_ump_stream_ep_pid(struct f_midi2_ep *ep)
+{
+	reply_ump_stream_string(ep, ump_product_id(ep),
+				UMP_STREAM_MSG_STATUS_PRODUCT_ID, 0,
+				UMP_STREAM_EP_STR_OFF);
+}
+
+/* Reply a UMP EP stream config */
+static void reply_ump_stream_ep_config(struct f_midi2_ep *ep)
+{
+	struct snd_ump_stream_msg_stream_cfg rep = {
+		.type = UMP_MSG_TYPE_STREAM,
+		.status = UMP_STREAM_MSG_STATUS_STREAM_CFG,
+	};
+
+	if ((ep->info.protocol & SNDRV_UMP_EP_INFO_PROTO_MIDI_MASK) ==
+	    SNDRV_UMP_EP_INFO_PROTO_MIDI2)
+		rep.protocol = UMP_STREAM_MSG_EP_INFO_CAP_MIDI2 >> 8;
+	else
+		rep.protocol = UMP_STREAM_MSG_EP_INFO_CAP_MIDI1 >> 8;
+
+	reply_ep_in(ep, &rep, sizeof(rep));
+}
+
+/* Reply a UMP FB info */
+static void reply_ump_stream_fb_info(struct f_midi2_ep *ep, int blk)
+{
+	struct f_midi2_block_info *b = &ep->blks[blk].info;
+	struct snd_ump_stream_msg_fb_info rep = {
+		.type = UMP_MSG_TYPE_STREAM,
+		.status = UMP_STREAM_MSG_STATUS_FB_INFO,
+		.active = !!b->active,
+		.function_block_id = blk,
+		.ui_hint = b->ui_hint,
+		.midi_10 = b->is_midi1,
+		.direction = b->direction,
+		.first_group = b->first_group,
+		.num_groups = b->num_groups,
+		.midi_ci_version = b->midi_ci_version,
+		.sysex8_streams = b->sysex8_streams,
+	};
+
+	reply_ep_in(ep, &rep, sizeof(rep));
+}
+
+/* Reply a FB name string */
+static void reply_ump_stream_fb_name(struct f_midi2_ep *ep, unsigned int blk)
+{
+	reply_ump_stream_string(ep, ump_fb_name(&ep->blks[blk].info),
+				UMP_STREAM_MSG_STATUS_FB_NAME, blk << 8,
+				UMP_STREAM_FB_STR_OFF);
+}
+
+/* Process a UMP Stream message */
+static void process_ump_stream_msg(struct f_midi2_ep *ep, const u32 *data)
+{
+	struct f_midi2 *midi2 = ep->card;
+	unsigned int format, status, blk;
+
+	format = ump_stream_message_format(*data);
+	status = ump_stream_message_status(*data);
+	switch (status) {
+	case UMP_STREAM_MSG_STATUS_EP_DISCOVERY:
+		if (format)
+			return; // invalid
+		if (data[1] & UMP_STREAM_MSG_REQUEST_EP_INFO)
+			reply_ump_stream_ep_info(ep);
+		if (data[1] & UMP_STREAM_MSG_REQUEST_DEVICE_INFO)
+			reply_ump_stream_ep_device(ep);
+		if (data[1] & UMP_STREAM_MSG_REQUEST_EP_NAME)
+			reply_ump_stream_ep_name(ep);
+		if (data[1] & UMP_STREAM_MSG_REQUEST_PRODUCT_ID)
+			reply_ump_stream_ep_pid(ep);
+		if (data[1] & UMP_STREAM_MSG_REQUEST_STREAM_CFG)
+			reply_ump_stream_ep_config(ep);
+		return;
+	case UMP_STREAM_MSG_STATUS_STREAM_CFG_REQUEST:
+		if (*data & UMP_STREAM_MSG_EP_INFO_CAP_MIDI2) {
+			ep->info.protocol = SNDRV_UMP_EP_INFO_PROTO_MIDI2;
+			DBG(midi2, "Switching Protocol to MIDI2\n");
+		} else {
+			ep->info.protocol = SNDRV_UMP_EP_INFO_PROTO_MIDI1;
+			DBG(midi2, "Switching Protocol to MIDI1\n");
+		}
+		snd_ump_switch_protocol(ep->ump, ep->info.protocol);
+		reply_ump_stream_ep_config(ep);
+		return;
+	case UMP_STREAM_MSG_STATUS_FB_DISCOVERY:
+		if (format)
+			return; // invalid
+		blk = (*data >> 8) & 0xff;
+		if (blk >= ep->num_blks)
+			return;
+		if (*data & UMP_STREAM_MSG_REQUEST_FB_INFO)
+			reply_ump_stream_fb_info(ep, blk);
+		if (*data & UMP_STREAM_MSG_REQUEST_FB_NAME)
+			reply_ump_stream_fb_name(ep, blk);
+		return;
+	}
+}
+
+/* Process UMP messages included in a USB request */
+static void process_ump(struct f_midi2_ep *ep, const struct usb_request *req)
+{
+	const u32 *data = (u32 *)req->buf;
+	int len = req->actual >> 2;
+	const u32 *in_buf = ep->ump->input_buf;
+
+	for (; len > 0; len--, data++) {
+		if (snd_ump_receive_ump_val(ep->ump, *data) <= 0)
+			continue;
+		if (ump_message_type(*in_buf) == UMP_MSG_TYPE_STREAM)
+			process_ump_stream_msg(ep, in_buf);
+	}
+}
+
+/*
+ * MIDI 2.0 UMP USB request handling
+ */
+
+/* complete handler for UMP EP-out requests */
+static void f_midi2_ep_out_complete(struct usb_ep *usb_ep,
+				    struct usb_request *req)
+{
+	struct f_midi2_req_ctx *ctx = req->context;
+	struct f_midi2_ep *ep = ctx->usb_ep->ep;
+	struct f_midi2 *midi2 = ep->card;
+	int status = req->status;
+
+	if (status) {
+		DBG(midi2, "%s complete error %d: %d/%d\n",
+		    usb_ep->name, status, req->actual, req->length);
+		goto error;
+	}
+
+	/* convert to UMP packet in native endianness */
+	le32_to_cpu_array((u32 *)req->buf, req->actual >> 2);
+
+	if (midi2->info.process_ump)
+		process_ump(ep, req);
+
+	snd_ump_receive(ep->ump, req->buf, req->actual & ~3);
+
+	if (midi2->operation_mode != MIDI_OP_MODE_MIDI2)
+		goto error;
+
+	if (queue_request_ep_raw(req))
+		goto error;
+	return;
+
+ error:
+	put_empty_request(req);
+}
+
+/* Transmit UMP packets received from user-space to the gadget */
+static void process_ump_transmit(struct f_midi2_ep *ep)
+{
+	struct f_midi2_usb_ep *usb_ep = &ep->ep_in;
+	struct f_midi2 *midi2 = ep->card;
+	struct usb_request *req;
+	int len;
+
+	if (!usb_ep->usb_ep->enabled)
+		return;
+
+	for (;;) {
+		req = get_empty_request(usb_ep);
+		if (!req)
+			break;
+		len = snd_ump_transmit(ep->ump, (u32 *)req->buf,
+				       midi2->info.req_buf_size);
+		if (len <= 0) {
+			put_empty_request(req);
+			break;
+		}
+
+		req->length = len;
+		if (queue_request_ep_in(req) < 0)
+			break;
+	}
+}
+
+/* Complete handler for UMP EP-in requests */
+static void f_midi2_ep_in_complete(struct usb_ep *usb_ep,
+				   struct usb_request *req)
+{
+	struct f_midi2_req_ctx *ctx = req->context;
+	struct f_midi2_ep *ep = ctx->usb_ep->ep;
+	struct f_midi2 *midi2 = ep->card;
+	int status = req->status;
+
+	put_empty_request(req);
+
+	if (status) {
+		DBG(midi2, "%s complete error %d: %d/%d\n",
+		    usb_ep->name, status, req->actual, req->length);
+		return;
+	}
+
+	process_ump_transmit(ep);
+}
+
+/* Start MIDI EP */
+static int f_midi2_start_ep(struct f_midi2_usb_ep *usb_ep,
+			    struct usb_function *fn)
+{
+	int err;
+
+	usb_ep_disable(usb_ep->usb_ep);
+	err = config_ep_by_speed(usb_ep->card->gadget, fn, usb_ep->usb_ep);
+	if (err)
+		return err;
+	return usb_ep_enable(usb_ep->usb_ep);
+}
+
+/* Drop pending requests */
+static void f_midi2_drop_reqs(struct f_midi2_usb_ep *usb_ep)
+{
+	int i;
+
+	if (!usb_ep->num_reqs)
+		return;
+
+	for (i = 0; i < usb_ep->num_reqs; i++) {
+		if (!test_bit(i, &usb_ep->free_reqs) && usb_ep->reqs[i].req) {
+			usb_ep_dequeue(usb_ep->usb_ep, usb_ep->reqs[i].req);
+			set_bit(i, &usb_ep->free_reqs);
+		}
+	}
+}
+
+/* Allocate requests for the given EP */
+static int f_midi2_alloc_ep_reqs(struct f_midi2_usb_ep *usb_ep)
+{
+	struct f_midi2 *midi2 = usb_ep->card;
+	int i;
+
+	if (!usb_ep->reqs)
+		return -EINVAL;
+
+	for (i = 0; i < midi2->info.num_reqs; i++) {
+		if (usb_ep->reqs[i].req)
+			continue;
+		usb_ep->reqs[i].req = alloc_ep_req(usb_ep->usb_ep,
+						   midi2->info.req_buf_size);
+		if (!usb_ep->reqs[i].req)
+			return -ENOMEM;
+		usb_ep->reqs[i].req->context = &usb_ep->reqs[i];
+	}
+	return 0;
+}
+
+/* Free allocated requests */
+static void f_midi2_free_ep_reqs(struct f_midi2_usb_ep *usb_ep)
+{
+	struct f_midi2 *midi2 = usb_ep->card;
+	int i;
+
+	for (i = 0; i < midi2->info.num_reqs; i++) {
+		if (!usb_ep->reqs[i].req)
+			continue;
+		free_ep_req(usb_ep->usb_ep, usb_ep->reqs[i].req);
+		usb_ep->reqs[i].req = NULL;
+	}
+}
+
+/* Initialize EP */
+static int f_midi2_init_ep(struct f_midi2 *midi2, struct f_midi2_ep *ep,
+			   struct f_midi2_usb_ep *usb_ep,
+			   void *desc, int num_reqs,
+			   void (*complete)(struct usb_ep *usb_ep,
+					    struct usb_request *req))
+{
+	int i;
+
+	usb_ep->card = midi2;
+	usb_ep->ep = ep;
+	usb_ep->usb_ep = usb_ep_autoconfig(midi2->gadget, desc);
+	if (!usb_ep->usb_ep)
+		return -ENODEV;
+	usb_ep->complete = complete;
+
+	if (num_reqs) {
+		usb_ep->reqs = kcalloc(num_reqs, sizeof(*usb_ep->reqs),
+				       GFP_KERNEL);
+		if (!usb_ep->reqs)
+			return -ENOMEM;
+		for (i = 0; i < num_reqs; i++) {
+			usb_ep->reqs[i].index = i;
+			usb_ep->reqs[i].usb_ep = usb_ep;
+			set_bit(i, &usb_ep->free_reqs);
+			usb_ep->num_reqs++;
+		}
+	}
+
+	return 0;
+}
+
+/* Free EP */
+static void f_midi2_free_ep(struct f_midi2_usb_ep *usb_ep)
+{
+	f_midi2_drop_reqs(usb_ep);
+
+	f_midi2_free_ep_reqs(usb_ep);
+
+	kfree(usb_ep->reqs);
+	usb_ep->num_reqs = 0;
+	usb_ep->free_reqs = 0;
+	usb_ep->reqs = NULL;
+}
+
+/* Queue requests for EP-out at start */
+static void f_midi2_queue_out_reqs(struct f_midi2_usb_ep *usb_ep)
+{
+	int i, err;
+
+	for (i = 0; i < usb_ep->num_reqs; i++) {
+		if (!test_bit(i, &usb_ep->free_reqs) || !usb_ep->reqs[i].req)
+			continue;
+		usb_ep->reqs[i].req->complete = usb_ep->complete;
+		err = usb_ep_queue(usb_ep->usb_ep, usb_ep->reqs[i].req,
+				   GFP_ATOMIC);
+		if (!err)
+			clear_bit(i, &usb_ep->free_reqs);
+	}
+}
+
+/*
+ * Gadget Function callbacks
+ */
+
+/* gadget function set_alt callback */
+static int f_midi2_set_alt(struct usb_function *fn, unsigned int intf,
+			   unsigned int alt)
+{
+	struct f_midi2 *midi2 = func_to_midi2(fn);
+	struct f_midi2_ep *ep;
+	int i, op_mode, err;
+
+	if (intf != midi2->midi_if || alt > 1)
+		return 0;
+
+	if (alt == 0)
+		op_mode = MIDI_OP_MODE_MIDI1;
+	else if (alt == 1)
+		op_mode = MIDI_OP_MODE_MIDI2;
+	else
+		op_mode = MIDI_OP_MODE_UNSET;
+
+	if (midi2->operation_mode == op_mode)
+		return 0;
+
+	midi2->operation_mode = op_mode;
+
+	if (op_mode != MIDI_OP_MODE_MIDI2) {
+		for (i = 0; i < midi2->num_eps; i++) {
+			ep = &midi2->midi2_eps[i];
+			f_midi2_drop_reqs(&ep->ep_in);
+			f_midi2_drop_reqs(&ep->ep_out);
+			f_midi2_free_ep_reqs(&ep->ep_in);
+			f_midi2_free_ep_reqs(&ep->ep_out);
+		}
+		return 0;
+	}
+
+	for (i = 0; i < midi2->num_eps; i++) {
+		ep = &midi2->midi2_eps[i];
+
+		err = f_midi2_start_ep(&ep->ep_in, fn);
+		if (err)
+			return err;
+		err = f_midi2_start_ep(&ep->ep_out, fn);
+		if (err)
+			return err;
+
+		err = f_midi2_alloc_ep_reqs(&ep->ep_in);
+		if (err)
+			return err;
+		err = f_midi2_alloc_ep_reqs(&ep->ep_out);
+		if (err)
+			return err;
+
+		f_midi2_queue_out_reqs(&ep->ep_out);
+	}
+
+	return 0;
+}
+
+/* gadget function get_alt callback */
+static int f_midi2_get_alt(struct usb_function *fn, unsigned int intf)
+{
+	struct f_midi2 *midi2 = func_to_midi2(fn);
+
+	if (intf == midi2->midi_if &&
+	    midi2->operation_mode == MIDI_OP_MODE_MIDI2)
+		return 1;
+	return 0;
+}
+
+/* convert UMP direction to USB MIDI 2.0 direction */
+static unsigned int ump_to_usb_dir(unsigned int ump_dir)
+{
+	switch (ump_dir) {
+	case SNDRV_UMP_DIR_INPUT:
+		return USB_MS_GR_TRM_BLOCK_TYPE_INPUT_ONLY;
+	case SNDRV_UMP_DIR_OUTPUT:
+		return USB_MS_GR_TRM_BLOCK_TYPE_OUTPUT_ONLY;
+	default:
+		return USB_MS_GR_TRM_BLOCK_TYPE_BIDIRECTIONAL;
+	}
+}
+
+/* assign GTB descriptors (for the given request) */
+static void assign_block_descriptors(struct f_midi2 *midi2,
+				     struct usb_request *req,
+				     int max_len)
+{
+	struct usb_ms20_gr_trm_block_header_descriptor header;
+	struct usb_ms20_gr_trm_block_descriptor *desc;
+	struct f_midi2_block_info *b;
+	struct f_midi2_ep *ep;
+	int i, blk, len;
+	char *data;
+
+	len = sizeof(gtb_header_desc) + sizeof(gtb_desc) * midi2->total_blocks;
+	if (WARN_ON(len > midi2->info.req_buf_size))
+		return;
+
+	header = gtb_header_desc;
+	header.wTotalLength = cpu_to_le16(len);
+	if (max_len < len) {
+		len = min_t(int, len, sizeof(header));
+		memcpy(req->buf, &header, len);
+		req->length = len;
+		req->zero = len < max_len;
+		return;
+	}
+
+	memcpy(req->buf, &header, sizeof(header));
+	data = req->buf + sizeof(header);
+	for (i = 0; i < midi2->num_eps; i++) {
+		ep = &midi2->midi2_eps[i];
+		for (blk = 0; blk < ep->num_blks; blk++) {
+			b = &ep->blks[blk].info;
+			desc = (struct usb_ms20_gr_trm_block_descriptor *)data;
+
+			*desc = gtb_desc;
+			desc->bGrpTrmBlkID = ep->blks[blk].gtb_id;
+			desc->bGrpTrmBlkType = ump_to_usb_dir(b->direction);
+			desc->nGroupTrm = b->first_group;
+			desc->nNumGroupTrm = b->num_groups;
+			desc->iBlockItem = ep->blks[blk].string_id;
+
+			if (ep->info.protocol & SNDRV_UMP_EP_INFO_PROTO_MIDI2)
+				desc->bMIDIProtocol = USB_MS_MIDI_PROTO_2_0;
+			else
+				desc->bMIDIProtocol = USB_MS_MIDI_PROTO_1_0_128;
+
+			if (b->is_midi1 == 2) {
+				desc->wMaxInputBandwidth = cpu_to_le16(1);
+				desc->wMaxOutputBandwidth = cpu_to_le16(1);
+			}
+
+			data += sizeof(*desc);
+		}
+	}
+
+	req->length = len;
+	req->zero = len < max_len;
+}
+
+/* gadget function setup callback: handle GTB requests */
+static int f_midi2_setup(struct usb_function *fn,
+			 const struct usb_ctrlrequest *ctrl)
+{
+	struct f_midi2 *midi2 = func_to_midi2(fn);
+	struct usb_composite_dev *cdev = fn->config->cdev;
+	struct usb_request *req = cdev->req;
+	u16 value, length;
+
+	if ((ctrl->bRequestType & USB_TYPE_MASK) != USB_TYPE_STANDARD ||
+	    ctrl->bRequest != USB_REQ_GET_DESCRIPTOR)
+		return -EOPNOTSUPP;
+
+	value = le16_to_cpu(ctrl->wValue);
+	length = le16_to_cpu(ctrl->wLength);
+
+	if ((value >> 8) != USB_DT_CS_GR_TRM_BLOCK)
+		return -EOPNOTSUPP;
+
+	/* handle only altset 1 */
+	if ((value & 0xff) != 1)
+		return -EOPNOTSUPP;
+
+	assign_block_descriptors(midi2, req, length);
+	return usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC);
+}
+
+/* gadget function disable callback */
+static void f_midi2_disable(struct usb_function *fn)
+{
+	struct f_midi2 *midi2 = func_to_midi2(fn);
+
+	midi2->operation_mode = MIDI_OP_MODE_UNSET;
+}
+
+/*
+ * ALSA UMP ops: most of them are NOPs, only trigger for write is needed
+ */
+static int f_midi2_ump_open(struct snd_ump_endpoint *ump, int dir)
+{
+	return 0;
+}
+
+static void f_midi2_ump_close(struct snd_ump_endpoint *ump, int dir)
+{
+}
+
+static void f_midi2_ump_trigger(struct snd_ump_endpoint *ump, int dir, int up)
+{
+	struct f_midi2_ep *ep = ump->private_data;
+
+	if (up && dir == SNDRV_RAWMIDI_STREAM_OUTPUT)
+		process_ump_transmit(ep);
+}
+
+static void f_midi2_ump_drain(struct snd_ump_endpoint *ump, int dir)
+{
+}
+
+static const struct snd_ump_ops f_midi2_ump_ops = {
+	.open = f_midi2_ump_open,
+	.close = f_midi2_ump_close,
+	.trigger = f_midi2_ump_trigger,
+	.drain = f_midi2_ump_drain,
+};
+
+/*
+ * ALSA UMP instance creation / deletion
+ */
+static void f_midi2_free_card(struct f_midi2 *midi2)
+{
+	if (midi2->card) {
+		snd_card_free_when_closed(midi2->card);
+		midi2->card = NULL;
+	}
+}
+
+/* use a reverse direction for the gadget host */
+static int reverse_dir(int dir)
+{
+	if (!dir || dir == SNDRV_UMP_DIR_BIDIRECTION)
+		return dir;
+	return (dir == SNDRV_UMP_DIR_OUTPUT) ?
+		SNDRV_UMP_DIR_INPUT : SNDRV_UMP_DIR_OUTPUT;
+}
+
+static int f_midi2_create_card(struct f_midi2 *midi2)
+{
+	struct snd_card *card;
+	struct snd_ump_endpoint *ump;
+	struct f_midi2_ep *ep;
+	int i, id, blk, err;
+	__be32 sw;
+
+	err = snd_card_new(&midi2->gadget->dev, -1, NULL, THIS_MODULE, 0,
+			   &card);
+	if (err < 0)
+		return err;
+	midi2->card = card;
+
+	strcpy(card->driver, "f_midi2");
+	strcpy(card->shortname, "MIDI 2.0 Gadget");
+	strcpy(card->longname, "MIDI 2.0 Gadget");
+
+	id = 0;
+	for (i = 0; i < midi2->num_eps; i++) {
+		ep = &midi2->midi2_eps[i];
+		err = snd_ump_endpoint_new(card, "MIDI 2.0 Gadget", id,
+					   1, 1, &ump);
+		if (err < 0)
+			goto error;
+		id++;
+
+		ep->ump = ump;
+		ump->no_process_stream = true;
+		ump->private_data = ep;
+		ump->ops = &f_midi2_ump_ops;
+		if (midi2->info.static_block)
+			ump->info.flags |= SNDRV_UMP_EP_INFO_STATIC_BLOCKS;
+		ump->info.protocol_caps = (ep->info.protocol_caps & 3) << 8;
+		ump->info.protocol = (ep->info.protocol & 3) << 8;
+		ump->info.version = 0x0101;
+		ump->info.family_id = ep->info.family;
+		ump->info.model_id = ep->info.model;
+		ump->info.manufacturer_id = ep->info.manufacturer & 0xffffff;
+		sw = cpu_to_be32(ep->info.sw_revision);
+		memcpy(ump->info.sw_revision, &sw, 4);
+
+		strscpy(ump->info.name, ump_ep_name(ep),
+			sizeof(ump->info.name));
+		strscpy(ump->info.product_id, ump_product_id(ep),
+			sizeof(ump->info.product_id));
+		strscpy(ump->core.name, ump->info.name, sizeof(ump->core.name));
+
+		for (blk = 0; blk < ep->num_blks; blk++) {
+			const struct f_midi2_block_info *b = &ep->blks[blk].info;
+			struct snd_ump_block *fb;
+
+			err = snd_ump_block_new(ump, blk,
+						reverse_dir(b->direction),
+						b->first_group, b->num_groups,
+						&ep->blks[blk].fb);
+			if (err < 0)
+				goto error;
+			fb = ep->blks[blk].fb;
+			fb->info.active = !!b->active;
+			fb->info.midi_ci_version = b->midi_ci_version;
+			fb->info.ui_hint = reverse_dir(b->ui_hint);
+			fb->info.sysex8_streams = b->sysex8_streams;
+			fb->info.flags |= b->is_midi1;
+			strscpy(fb->info.name, ump_fb_name(b),
+				sizeof(fb->info.name));
+		}
+	}
+
+	for (i = 0; i < midi2->num_eps; i++) {
+		err = snd_ump_attach_legacy_rawmidi(midi2->midi2_eps[i].ump,
+						    "Legacy MIDI", id);
+		if (err < 0)
+			goto error;
+		id++;
+	}
+
+	err = snd_card_register(card);
+	if (err < 0)
+		goto error;
+
+	return 0;
+
+ error:
+	f_midi2_free_card(midi2);
+	return err;
+}
+
+/*
+ * Creation of USB descriptors
+ */
+struct f_midi2_usb_config {
+	struct usb_descriptor_header **list;
+	unsigned int size;
+	unsigned int alloc;
+};
+
+static int append_config(struct f_midi2_usb_config *config, void *d)
+{
+	unsigned int size;
+	void *buf;
+
+	if (config->size + 2 >= config->alloc) {
+		size = config->size + 16;
+		buf = krealloc(config->list, size * sizeof(void *), GFP_KERNEL);
+		if (!buf)
+			return -ENOMEM;
+		config->list = buf;
+		config->alloc = size;
+	}
+
+	config->list[config->size] = d;
+	config->size++;
+	config->list[config->size] = NULL;
+	return 0;
+}
+
+static int append_configs(struct f_midi2_usb_config *config, void **d)
+{
+	int err;
+
+	for (; *d; d++) {
+		err = append_config(config, *d);
+		if (err)
+			return err;
+	}
+	return 0;
+}
+
+static int f_midi2_create_usb_configs(struct f_midi2 *midi2,
+				      struct f_midi2_usb_config *config,
+				      int speed)
+{
+	void **midi1_eps;
+	int i, err;
+
+	switch (speed) {
+	default:
+	case USB_SPEED_HIGH:
+		midi2_midi1_ep_out_desc.wMaxPacketSize = cpu_to_le16(512);
+		midi2_midi1_ep_in_desc.wMaxPacketSize = cpu_to_le16(512);
+		for (i = 0; i < midi2->num_eps; i++)
+			midi2_midi2_ep_out_desc[i].wMaxPacketSize =
+				cpu_to_le16(512);
+		fallthrough;
+	case USB_SPEED_FULL:
+		midi1_eps = midi2_midi1_ep_descs;
+		break;
+	case USB_SPEED_SUPER:
+	case USB_SPEED_SUPER_PLUS:
+		midi2_midi1_ep_out_desc.wMaxPacketSize = cpu_to_le16(1024);
+		midi2_midi1_ep_in_desc.wMaxPacketSize = cpu_to_le16(1024);
+		for (i = 0; i < midi2->num_eps; i++)
+			midi2_midi2_ep_out_desc[i].wMaxPacketSize =
+				cpu_to_le16(1024);
+		midi1_eps = midi2_midi1_ep_ss_descs;
+		break;
+	}
+
+	err = append_configs(config, midi2_audio_descs);
+	if (err < 0)
+		return err;
+	err = append_configs(config, midi2_midi1_descs);
+	if (err < 0)
+		return err;
+	err = append_configs(config, midi1_eps);
+	if (err < 0)
+		return err;
+	err = append_configs(config, midi2_midi2_descs);
+	if (err < 0)
+		return err;
+
+	for (i = 0; i < midi2->num_eps; i++) {
+		err = append_config(config, &midi2_midi2_ep_out_desc[i]);
+		if (err < 0)
+			return err;
+		if (speed == USB_SPEED_SUPER || speed == USB_SPEED_SUPER_PLUS) {
+			err = append_config(config, &midi2_midi2_ep_out_ss_comp_desc);
+			if (err < 0)
+				return err;
+		}
+		err = append_config(config, &midi2_midi2_ep_out_class_desc[i]);
+		if (err < 0)
+			return err;
+		err = append_config(config, &midi2_midi2_ep_in_desc[i]);
+		if (err < 0)
+			return err;
+		if (speed == USB_SPEED_SUPER || speed == USB_SPEED_SUPER_PLUS) {
+			err = append_config(config, &midi2_midi2_ep_in_ss_comp_desc);
+			if (err < 0)
+				return err;
+		}
+		err = append_config(config, &midi2_midi2_ep_in_class_desc[i]);
+		if (err < 0)
+			return err;
+	}
+
+	return 0;
+}
+
+static void f_midi2_free_usb_configs(struct f_midi2_usb_config *config)
+{
+	kfree(config->list);
+	memset(config, 0, sizeof(*config));
+}
+
+/* as we use the static descriptors for simplicity, serialize bind call */
+static DEFINE_MUTEX(f_midi2_desc_mutex);
+
+/* fill MIDI2 EP class-specific descriptor */
+static void fill_midi2_class_desc(struct f_midi2_ep *ep,
+				  struct usb_ms20_endpoint_descriptor_32 *cdesc)
+{
+	int blk;
+
+	cdesc->bLength = USB_DT_MS20_ENDPOINT_SIZE(ep->num_blks);
+	cdesc->bDescriptorType = USB_DT_CS_ENDPOINT;
+	cdesc->bDescriptorSubtype = USB_MS_GENERAL_2_0;
+	cdesc->bNumGrpTrmBlock = ep->num_blks;
+	for (blk = 0; blk < ep->num_blks; blk++)
+		cdesc->baAssoGrpTrmBlkID[blk] = ep->blks[blk].gtb_id;
+}
+
+/* initialize MIDI2 EP-in */
+static int f_midi2_init_midi2_ep_in(struct f_midi2 *midi2, int index)
+{
+	struct f_midi2_ep *ep = &midi2->midi2_eps[index];
+	struct usb_endpoint_descriptor *desc = &midi2_midi2_ep_in_desc[index];
+
+	desc->bLength = USB_DT_ENDPOINT_SIZE;
+	desc->bDescriptorType = USB_DT_ENDPOINT;
+	desc->bEndpointAddress = USB_DIR_IN;
+	desc->bmAttributes = USB_ENDPOINT_XFER_INT;
+	desc->wMaxPacketSize = cpu_to_le16(EP_MAX_PACKET_INT);
+	desc->bInterval = 1;
+
+	fill_midi2_class_desc(ep, &midi2_midi2_ep_in_class_desc[index]);
+
+	return f_midi2_init_ep(midi2, ep, &ep->ep_in, desc,
+			       midi2->info.num_reqs, f_midi2_ep_in_complete);
+}
+
+/* initialize MIDI2 EP-out */
+static int f_midi2_init_midi2_ep_out(struct f_midi2 *midi2, int index)
+{
+	struct f_midi2_ep *ep = &midi2->midi2_eps[index];
+	struct usb_endpoint_descriptor *desc = &midi2_midi2_ep_out_desc[index];
+
+	desc->bLength = USB_DT_ENDPOINT_SIZE;
+	desc->bDescriptorType = USB_DT_ENDPOINT;
+	desc->bEndpointAddress = USB_DIR_OUT;
+	desc->bmAttributes = USB_ENDPOINT_XFER_BULK;
+
+	fill_midi2_class_desc(ep, &midi2_midi2_ep_out_class_desc[index]);
+
+	return f_midi2_init_ep(midi2, ep, &ep->ep_out, desc,
+			       midi2->info.num_reqs, f_midi2_ep_out_complete);
+}
+
+/* gadget function bind callback */
+static int f_midi2_bind(struct usb_configuration *c, struct usb_function *f)
+{
+	struct usb_composite_dev *cdev = c->cdev;
+	struct f_midi2 *midi2 = func_to_midi2(f);
+	struct f_midi2_ep *ep;
+	struct f_midi2_usb_config config = {};
+	struct usb_gadget_strings string_fn = {
+		.language = 0x0409,	/* en-us */
+		.strings = midi2->string_defs,
+	};
+	struct usb_gadget_strings *strings[] = {
+		&string_fn,
+		NULL,
+	};
+	int i, blk, status;
+
+	midi2->gadget = cdev->gadget;
+	midi2->operation_mode = MIDI_OP_MODE_UNSET;
+
+	status = f_midi2_create_card(midi2);
+	if (status < 0)
+		goto fail_register;
+
+	/* maybe allocate device-global string ID */
+	midi2->strings = usb_gstrings_attach(c->cdev, strings,
+					     midi2->total_blocks + 1);
+	if (IS_ERR(midi2->strings)) {
+		status = PTR_ERR(midi2->strings);
+		goto fail_string;
+	}
+
+	mutex_lock(&f_midi2_desc_mutex);
+	midi2_midi1_if_desc.iInterface = midi2->strings[STR_IFACE].id;
+	midi2_midi2_if_desc.iInterface = midi2->strings[STR_IFACE].id;
+	for (i = 0; i < midi2->num_eps; i++) {
+		ep = &midi2->midi2_eps[i];
+		for (blk = 0; blk < ep->num_blks; blk++)
+			ep->blks[blk].string_id =
+				midi2->strings[gtb_to_str_id(ep->blks[blk].gtb_id)].id;
+	}
+
+	/* audio interface */
+	status = usb_interface_id(c, f);
+	if (status < 0)
+		goto fail;
+	midi2_audio_if_desc.bInterfaceNumber = status;
+
+	/* MIDI streaming */
+	status = usb_interface_id(c, f);
+	if (status < 0)
+		goto fail;
+	midi2->midi_if = status;
+	midi2_midi1_if_desc.bInterfaceNumber = status;
+	midi2_midi2_if_desc.bInterfaceNumber = status;
+	midi2_audio_class_desc.baInterfaceNr[0] = status;
+
+	/* allocate instance-specific endpoints */
+	status = f_midi2_init_ep(midi2, NULL, &midi2->midi1_ep_in,
+				 &midi2_midi1_ep_in_desc, 0, NULL);
+	if (status)
+		goto fail;
+	status = f_midi2_init_ep(midi2, NULL, &midi2->midi1_ep_out,
+				 &midi2_midi1_ep_out_desc, 0, NULL);
+	if (status)
+		goto fail;
+
+	for (i = 0; i < midi2->num_eps; i++) {
+		status = f_midi2_init_midi2_ep_in(midi2, i);
+		if (status)
+			goto fail;
+		status = f_midi2_init_midi2_ep_out(midi2, i);
+		if (status)
+			goto fail;
+	}
+
+	status = f_midi2_create_usb_configs(midi2, &config, USB_SPEED_FULL);
+	if (status < 0)
+		goto fail;
+	f->fs_descriptors = usb_copy_descriptors(config.list);
+	if (!f->fs_descriptors) {
+		status = -ENOMEM;
+		goto fail;
+	}
+	f_midi2_free_usb_configs(&config);
+
+	if (gadget_is_dualspeed(midi2->gadget)) {
+		status = f_midi2_create_usb_configs(midi2, &config, USB_SPEED_HIGH);
+		if (status < 0)
+			goto fail;
+		f->hs_descriptors = usb_copy_descriptors(config.list);
+		if (!f->hs_descriptors) {
+			status = -ENOMEM;
+			goto fail;
+		}
+		f_midi2_free_usb_configs(&config);
+	}
+
+	if (gadget_is_superspeed(midi2->gadget)) {
+		status = f_midi2_create_usb_configs(midi2, &config, USB_SPEED_SUPER);
+		if (status < 0)
+			goto fail;
+		f->ss_descriptors = usb_copy_descriptors(config.list);
+		if (!f->ss_descriptors) {
+			status = -ENOMEM;
+			goto fail;
+		}
+		if (gadget_is_superspeed_plus(midi2->gadget)) {
+			f->ssp_descriptors = usb_copy_descriptors(config.list);
+			if (!f->ssp_descriptors) {
+				status = -ENOMEM;
+				goto fail;
+			}
+		}
+		f_midi2_free_usb_configs(&config);
+	}
+
+	mutex_unlock(&f_midi2_desc_mutex);
+	return 0;
+
+fail:
+	f_midi2_free_usb_configs(&config);
+	mutex_unlock(&f_midi2_desc_mutex);
+	usb_free_all_descriptors(f);
+fail_string:
+	f_midi2_free_card(midi2);
+fail_register:
+	ERROR(midi2, "%s: can't bind, err %d\n", f->name, status);
+	return status;
+}
+
+/* gadget function unbind callback */
+static void f_midi2_unbind(struct usb_configuration *c, struct usb_function *f)
+{
+	struct f_midi2 *midi2 = func_to_midi2(f);
+	int i;
+
+	f_midi2_free_card(midi2);
+
+	f_midi2_free_ep(&midi2->midi1_ep_in);
+	f_midi2_free_ep(&midi2->midi1_ep_out);
+	for (i = 0; i < midi2->num_eps; i++) {
+		f_midi2_free_ep(&midi2->midi2_eps[i].ep_in);
+		f_midi2_free_ep(&midi2->midi2_eps[i].ep_out);
+	}
+
+	usb_free_all_descriptors(f);
+}
+
+/* create a f_midi2_block_opts instance for the given block number */
+static int f_midi2_block_opts_create(struct f_midi2_ep_opts *ep_opts,
+				     unsigned int blk,
+				     struct f_midi2_block_opts **block_p)
+{
+	struct f_midi2_block_opts *block_opts;
+
+	block_opts = kzalloc(sizeof(*block_opts), GFP_KERNEL);
+	if (!block_opts)
+		return -ENOMEM;
+
+	block_opts->ep = ep_opts;
+	block_opts->id = blk;
+
+	/* set up the default values */
+	block_opts->info.direction = SNDRV_UMP_DIR_BIDIRECTION;
+	block_opts->info.first_group = 0;
+	block_opts->info.num_groups = 1;
+	block_opts->info.ui_hint = SNDRV_UMP_BLOCK_UI_HINT_BOTH;
+	block_opts->info.active = 1;
+
+	ep_opts->blks[blk] = block_opts;
+	*block_p = block_opts;
+	return 0;
+}
+
+/* create a f_midi2_ep_opts instance */
+static int f_midi2_ep_opts_create(struct f_midi2_opts *opts,
+				  unsigned int index,
+				  struct f_midi2_ep_opts **ep_p)
+{
+	struct f_midi2_ep_opts *ep_opts;
+
+	ep_opts = kzalloc(sizeof(*ep_opts), GFP_KERNEL);
+	if (!ep_opts)
+		return -ENOMEM;
+
+	ep_opts->opts = opts;
+	ep_opts->index = index;
+
+	/* set up the default values */
+	ep_opts->info.protocol = 2;
+	ep_opts->info.protocol_caps = 3;
+
+	opts->eps[index] = ep_opts;
+	*ep_p = ep_opts;
+	return 0;
+}
+
+static const struct config_item_type f_midi2_func_type = {
+	.ct_owner	= THIS_MODULE,
+};
+
+static void f_midi2_free_inst(struct usb_function_instance *f)
+{
+	struct f_midi2_opts *opts;
+
+	opts = container_of(f, struct f_midi2_opts, func_inst);
+
+	/* we have only one EP and one FB */
+	if (opts->eps[0]) {
+		kfree(opts->eps[0]->blks[0]);
+		kfree(opts->eps[0]);
+	}
+	kfree(opts);
+}
+
+/* gadget alloc_inst */
+static struct usb_function_instance *f_midi2_alloc_inst(void)
+{
+	struct f_midi2_opts *opts;
+	struct f_midi2_ep_opts *ep_opts;
+	struct f_midi2_block_opts *block_opts;
+	int ret;
+
+	opts = kzalloc(sizeof(*opts), GFP_KERNEL);
+	if (!opts)
+		return ERR_PTR(-ENOMEM);
+
+	mutex_init(&opts->lock);
+	opts->func_inst.free_func_inst = f_midi2_free_inst;
+	opts->info.process_ump = true;
+	opts->info.static_block = true;
+	opts->info.num_reqs = 32;
+	opts->info.req_buf_size = 512;
+
+	ret = f_midi2_ep_opts_create(opts, 0, &ep_opts);
+	if (ret) {
+		kfree(opts);
+		return ERR_PTR(ret);
+	}
+
+	/* create the default block */
+	ret = f_midi2_block_opts_create(ep_opts, 0, &block_opts);
+	if (ret) {
+		kfree(ep_opts);
+		kfree(opts);
+		return ERR_PTR(ret);
+	}
+
+	config_group_init_type_name(&opts->func_inst.group, "",
+				    &f_midi2_func_type);
+	return &opts->func_inst;
+}
+
+static void do_f_midi2_free(struct f_midi2 *midi2, struct f_midi2_opts *opts)
+{
+	mutex_lock(&opts->lock);
+	--opts->refcnt;
+	mutex_unlock(&opts->lock);
+	kfree(midi2->string_defs);
+	kfree(midi2);
+}
+
+static void f_midi2_free(struct usb_function *f)
+{
+	do_f_midi2_free(func_to_midi2(f),
+			container_of(f->fi, struct f_midi2_opts, func_inst));
+}
+
+/* gadget alloc callback */
+static struct usb_function *f_midi2_alloc(struct usb_function_instance *fi)
+{
+	struct f_midi2 *midi2;
+	struct f_midi2_opts *opts;
+	int i;
+
+	midi2 = kzalloc(sizeof(*midi2), GFP_KERNEL);
+	if (!midi2)
+		return ERR_PTR(-ENOMEM);
+
+	opts = container_of(fi, struct f_midi2_opts, func_inst);
+	mutex_lock(&opts->lock);
+	++opts->refcnt;
+	mutex_unlock(&opts->lock);
+
+	spin_lock_init(&midi2->queue_lock);
+
+	midi2->func.name = "midi2_func";
+	midi2->func.bind = f_midi2_bind;
+	midi2->func.unbind = f_midi2_unbind;
+	midi2->func.get_alt = f_midi2_get_alt;
+	midi2->func.set_alt = f_midi2_set_alt;
+	midi2->func.setup = f_midi2_setup;
+	midi2->func.disable = f_midi2_disable;
+	midi2->func.free_func = f_midi2_free;
+
+	midi2->info = opts->info;
+
+	/* fixed 1 UMP EP and 1 UMP FB as of now */
+	midi2->num_eps = 1;
+	midi2->midi2_eps[0].info = opts->eps[0]->info;
+	midi2->midi2_eps[0].card = midi2;
+	midi2->midi2_eps[0].num_blks = 1;
+	midi2->midi2_eps[0].blks[0].info = opts->eps[0]->blks[0]->info;
+	midi2->midi2_eps[0].blks[0].gtb_id = 1;
+
+	for (i = 0; i < midi2->num_eps; i++)
+		midi2->total_blocks += midi2->midi2_eps[i].num_blks;
+
+	midi2->string_defs = kcalloc(midi2->total_blocks + 1,
+				     sizeof(*midi2->string_defs), GFP_KERNEL);
+	if (!midi2->string_defs) {
+		do_f_midi2_free(midi2, opts);
+		return ERR_PTR(-ENOMEM);
+	}
+
+	if (opts->info.iface_name && *opts->info.iface_name)
+		midi2->string_defs[0].s = opts->info.iface_name;
+	else
+		midi2->string_defs[0].s = ump_ep_name(&midi2->midi2_eps[0]);
+	midi2->string_defs[1].s = ump_fb_name(&midi2->midi2_eps[0].blks[0].info);
+
+	return &midi2->func;
+}
+
+DECLARE_USB_FUNCTION_INIT(midi2, f_midi2_alloc_inst, f_midi2_alloc);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/usb/gadget/function/u_midi2.h b/drivers/usb/gadget/function/u_midi2.h
new file mode 100644
index 000000000000..a68dc2ea035e
--- /dev/null
+++ b/drivers/usb/gadget/function/u_midi2.h
@@ -0,0 +1,79 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Utility definitions for MIDI 2.0 function
+ */
+
+#ifndef U_MIDI2_H
+#define U_MIDI2_H
+
+#include <linux/usb/composite.h>
+#include <sound/asound.h>
+
+struct f_midi2_opts;
+struct f_midi2_ep_opts;
+struct f_midi2_block_opts;
+
+/* UMP Function Block info */
+struct f_midi2_block_info {
+	unsigned int direction;		/* FB direction: 1-3 */
+	unsigned int first_group;	/* first UMP group: 0-15 */
+	unsigned int num_groups;	/* number of UMP groups: 1-16 */
+	unsigned int ui_hint;		/* UI-hint: 0-3 */
+	unsigned int midi_ci_version;	/* MIDI-CI version: 0-255 */
+	unsigned int sysex8_streams;	/* number of sysex8 streams: 0-255 */
+	unsigned int is_midi1;		/* MIDI 1.0 port: 0-2 */
+	bool active;			/* FB active flag: bool */
+	const char *name;		/* FB name */
+};
+
+/* UMP Endpoint info */
+struct f_midi2_ep_info {
+	unsigned int protocol_caps;	/* protocol capabilities: 1-3 */
+	unsigned int protocol;		/* default protocol: 1-2 */
+	unsigned int manufacturer;	/* manufacturer id: 0-0xffffff */
+	unsigned int family;		/* device family id: 0-0xffff */
+	unsigned int model;		/* device model id: 0x-0xffff */
+	unsigned int sw_revision;	/* software revision: 32bit */
+
+	const char *ep_name;		/* Endpoint name */
+	const char *product_id;		/* Product ID */
+};
+
+struct f_midi2_card_info {
+	bool process_ump;		/* process UMP stream: bool */
+	bool static_block;		/* static FBs: bool */
+	unsigned int req_buf_size;	/* request buffer size */
+	unsigned int num_reqs;		/* number of requests */
+	const char *iface_name;		/* interface name */
+};
+
+struct f_midi2_block_opts {
+	struct config_group group;
+	unsigned int id;
+	struct f_midi2_block_info info;
+	struct f_midi2_ep_opts *ep;
+};
+
+struct f_midi2_ep_opts {
+	struct config_group group;
+	unsigned int index;
+	struct f_midi2_ep_info info;
+	struct f_midi2_block_opts *blks[SNDRV_UMP_MAX_BLOCKS];
+	struct f_midi2_opts *opts;
+};
+
+#define MAX_UMP_EPS		4
+#define MAX_CABLES		16
+
+struct f_midi2_opts {
+	struct usb_function_instance func_inst;
+	struct mutex lock;
+	int refcnt;
+
+	struct f_midi2_card_info info;
+
+	unsigned int num_eps;
+	struct f_midi2_ep_opts *eps[MAX_UMP_EPS];
+};
+
+#endif /* U_MIDI2_H */
-- 
2.35.3


^ permalink raw reply related	[flat|nested] 9+ messages in thread

* [PATCH 2/7] usb: gadget: midi2: Add configfs support
  2023-07-25  6:21 [PATCH 0/7] usb: Add USB MIDI 2.0 Gadget Function Driver Takashi Iwai
  2023-07-25  6:22 ` [PATCH 1/7] usb: gadget: Add support for USB MIDI 2.0 function driver Takashi Iwai
@ 2023-07-25  6:22 ` Takashi Iwai
  2023-07-25  6:22 ` [PATCH 3/7] usb: gadget: midi2: Dynamically create MIDI 1.0 altset descriptors Takashi Iwai
                   ` (5 subsequent siblings)
  7 siblings, 0 replies; 9+ messages in thread
From: Takashi Iwai @ 2023-07-25  6:22 UTC (permalink / raw)
  To: Greg Kroah-Hartman; +Cc: alsa-devel, linux-usb, linux-kernel

This patch adds the support of configfs to MIDI 2.0 function driver
for users to allow configuring the UMP Endpoint and Function Blocks
more flexibly.  The configuration is in a tree form.  The top-most
contains some card-level configurations.  UMP Endpoints are defined in
subdirectories (ep.0, ep.1, etc) that contain Endpoint-specific
configurations such as ep_name, etc.  And, UMP Function Blocks are
defined in the subdirectories (block.0, block.1, etc) under EP
subdirectory.  As default, the driver creates a single UMP Endpoint
(ep.0) and a single Function Block (block.0) to work in a minimalistic
manner.  User can modify those attributes freely to fit with the
demands.

When multiple Function Blocks are required, user can create another
directory as block.1, block.2, and so on (up to block.31).  A block.*
directory can be deleted dynamically, too.  A caveat is that the block
number has to be continuous.

Similarly, when multiple UMP Endpoints are required, user can create
another directory as ep.1, ep.2, up to ep.3.

Also, some driver behavior can be controlled in the card top-level
configs.  e.g. you can pass process_ump=0 to disable the processing of
UMP Stream messages.  This would be equivalent with the older MIDI 2.0
spec that doesn't support UMP v1.1 features.

The configfs interface checks upper- / lower-bound of input values,
and more sanity checks are performed at binding.

Attributes can't be changed any longer once when the instance is
linked to UDC.

Signed-off-by: Takashi Iwai <tiwai@suse.de>
---
 .../ABI/testing/configfs-usb-gadget-midi2     |  52 ++
 drivers/usb/gadget/function/f_midi2.c         | 621 +++++++++++++++++-
 2 files changed, 650 insertions(+), 23 deletions(-)
 create mode 100644 Documentation/ABI/testing/configfs-usb-gadget-midi2

diff --git a/Documentation/ABI/testing/configfs-usb-gadget-midi2 b/Documentation/ABI/testing/configfs-usb-gadget-midi2
new file mode 100644
index 000000000000..a3a036d784c7
--- /dev/null
+++ b/Documentation/ABI/testing/configfs-usb-gadget-midi2
@@ -0,0 +1,52 @@
+What:		/config/usb-gadget/gadget/functions/midi2.name
+Date:		Jul 2023
+KernelVersion:	6.6
+Description:
+		The attributes:
+
+		============	===============================================
+		process_ump	Flag to process UMP Stream messages (0 or 1)
+		static_block	Flag for static blocks (0 or 1)
+		iface_name	MIDI interface name string
+		============	===============================================
+
+What:		/config/usb-gadget/gadget/functions/midi2.name/ep.number
+Date:		Jul 2023
+KernelVersion:	6.6
+Description:
+		This group contains a UMP Endpoint configuration.
+		A new Endpoint starts from 0, and can be up to 3.
+
+		The attributes:
+
+		=============	===============================================
+		protocol_caps	MIDI protocol capabilities (1, 2 or 3 for both)
+		protocol	Default MIDI protocol (1 or 2)
+		ep_name		UMP Endpoint name string
+		product_id	Product ID string
+		manufacturer	Manufacture ID (24 bit)
+		family		Device family ID (16 bit)
+		model		Device model ID (16 bit)
+		sw_revision	Software Revision (32 bit)
+		=============	===============================================
+
+What:		/config/usb-gadget/gadget/functions/midi2.name/ep.number/block.number
+Date:		Jul 2023
+KernelVersion:	6.6
+Description:
+		This group contains a UMP Function Block configuration.
+		A new block starts from 0, and can be up to 31.
+
+		The attributes:
+
+		===============	===============================================
+		name		Function Block name string
+		direction	1: input, 2: output, 3: bidirectional
+		first_group	The first UMP Group number (0-15)
+		num_groups	The number of groups in this FB (1-16)
+		ui_hint		0: unknown, 1: receiver, 2: sender, 3: both
+		midi_ci_verison	Supported MIDI-CI version number (8 bit)
+		is_midi1	Legacy MIDI 1.0 device (0, 1 or 2)
+		sysex8_streams	Max number of SysEx8 streams (8 bit)
+		active		Active FB flag (0 or 1)
+		===============	===============================================
diff --git a/drivers/usb/gadget/function/f_midi2.c b/drivers/usb/gadget/function/f_midi2.c
index 848cb3150deb..c68a6fa0d237 100644
--- a/drivers/usb/gadget/function/f_midi2.c
+++ b/drivers/usb/gadget/function/f_midi2.c
@@ -292,7 +292,7 @@ static struct usb_interface_descriptor midi2_midi2_if_desc = {
 	.bDescriptorType =	USB_DT_INTERFACE,
 	.bInterfaceNumber =	0, // to be filled
 	.bAlternateSetting =	1,
-	.bNumEndpoints =	2,
+	.bNumEndpoints =	2, // to be filled
 	.bInterfaceClass =	USB_CLASS_AUDIO,
 	.bInterfaceSubClass =	USB_SUBCLASS_MIDISTREAMING,
 	.bInterfaceProtocol =	0,
@@ -1403,6 +1403,8 @@ static int f_midi2_bind(struct usb_configuration *c, struct usb_function *f)
 				midi2->strings[gtb_to_str_id(ep->blks[blk].gtb_id)].id;
 	}
 
+	midi2_midi2_if_desc.bNumEndpoints = midi2->num_eps * 2;
+
 	/* audio interface */
 	status = usb_interface_id(c, f);
 	if (status < 0)
@@ -1510,16 +1512,274 @@ static void f_midi2_unbind(struct usb_configuration *c, struct usb_function *f)
 	usb_free_all_descriptors(f);
 }
 
+/*
+ * ConfigFS interface
+ */
+
+/* type conversion helpers */
+static inline struct f_midi2_opts *to_f_midi2_opts(struct config_item *item)
+{
+	return container_of(to_config_group(item), struct f_midi2_opts,
+			    func_inst.group);
+}
+
+static inline struct f_midi2_ep_opts *
+to_f_midi2_ep_opts(struct config_item *item)
+{
+	return container_of(to_config_group(item), struct f_midi2_ep_opts,
+			    group);
+}
+
+static inline struct f_midi2_block_opts *
+to_f_midi2_block_opts(struct config_item *item)
+{
+	return container_of(to_config_group(item), struct f_midi2_block_opts,
+			    group);
+}
+
+/* trim the string to be usable for EP and FB name strings */
+static void make_name_string(char *s)
+{
+	char *p;
+
+	p = strchr(s, '\n');
+	if (p)
+		*p = 0;
+
+	p = s + strlen(s);
+	for (; p > s && isspace(*p); p--)
+		*p = 0;
+}
+
+/* configfs helpers: generic show/store for unisnged int */
+static ssize_t f_midi2_opts_uint_show(struct f_midi2_opts *opts,
+				      u32 val, const char *format, char *page)
+{
+	int result;
+
+	mutex_lock(&opts->lock);
+	result = sprintf(page, format, val);
+	mutex_unlock(&opts->lock);
+	return result;
+}
+
+static ssize_t f_midi2_opts_uint_store(struct f_midi2_opts *opts,
+				       u32 *valp, u32 minval, u32 maxval,
+				       const char *page, size_t len)
+{
+	int ret;
+	u32 val;
+
+	mutex_lock(&opts->lock);
+	if (opts->refcnt) {
+		ret = -EBUSY;
+		goto end;
+	}
+
+	ret = kstrtou32(page, 0, &val);
+	if (ret)
+		goto end;
+	if (val < minval || val > maxval) {
+		ret = -EINVAL;
+		goto end;
+	}
+
+	*valp = val;
+	ret = len;
+
+end:
+	mutex_unlock(&opts->lock);
+	return ret;
+}
+
+/* generic store for bool */
+static ssize_t f_midi2_opts_bool_store(struct f_midi2_opts *opts,
+				       bool *valp, const char *page, size_t len)
+{
+	int ret;
+	bool val;
+
+	mutex_lock(&opts->lock);
+	if (opts->refcnt) {
+		ret = -EBUSY;
+		goto end;
+	}
+
+	ret = kstrtobool(page, &val);
+	if (ret)
+		goto end;
+	*valp = val;
+	ret = len;
+
+end:
+	mutex_unlock(&opts->lock);
+	return ret;
+}
+
+/* generic show/store for string */
+static ssize_t f_midi2_opts_str_show(struct f_midi2_opts *opts,
+				     const char *str, char *page)
+{
+	int result = 0;
+
+	mutex_lock(&opts->lock);
+	if (str)
+		result = scnprintf(page, PAGE_SIZE, "%s\n", str);
+	mutex_unlock(&opts->lock);
+	return result;
+}
+
+static ssize_t f_midi2_opts_str_store(struct f_midi2_opts *opts,
+				      const char **strp, size_t maxlen,
+				      const char *page, size_t len)
+{
+	char *c;
+	int ret;
+
+	mutex_lock(&opts->lock);
+	if (opts->refcnt) {
+		ret = -EBUSY;
+		goto end;
+	}
+
+	c = kstrndup(page, min(len, maxlen), GFP_KERNEL);
+	if (!c) {
+		ret = -ENOMEM;
+		goto end;
+	}
+
+	kfree(*strp);
+	make_name_string(c);
+	*strp = c;
+	ret = len;
+
+end:
+	mutex_unlock(&opts->lock);
+	return ret;
+}
+
+/*
+ * Definitions for UMP Block config
+ */
+
+/* define an uint option for block */
+#define F_MIDI2_BLOCK_OPT(name, format, minval, maxval)			\
+static ssize_t f_midi2_block_opts_##name##_show(struct config_item *item,\
+					  char *page)			\
+{									\
+	struct f_midi2_block_opts *opts = to_f_midi2_block_opts(item);	\
+	return f_midi2_opts_uint_show(opts->ep->opts, opts->info.name,	\
+				      format "\n", page);		\
+}									\
+									\
+static ssize_t f_midi2_block_opts_##name##_store(struct config_item *item,\
+					 const char *page, size_t len)	\
+{									\
+	struct f_midi2_block_opts *opts = to_f_midi2_block_opts(item);	\
+	return f_midi2_opts_uint_store(opts->ep->opts, &opts->info.name,\
+				       minval, maxval, page, len);	\
+}									\
+									\
+CONFIGFS_ATTR(f_midi2_block_opts_, name)
+
+/* define a boolean option for block */
+#define F_MIDI2_BLOCK_BOOL_OPT(name)					\
+static ssize_t f_midi2_block_opts_##name##_show(struct config_item *item,\
+					  char *page)			\
+{									\
+	struct f_midi2_block_opts *opts = to_f_midi2_block_opts(item);	\
+	return f_midi2_opts_uint_show(opts->ep->opts, opts->info.name,	\
+				      "%u\n", page);			\
+}									\
+									\
+static ssize_t f_midi2_block_opts_##name##_store(struct config_item *item,\
+					 const char *page, size_t len)	\
+{									\
+	struct f_midi2_block_opts *opts = to_f_midi2_block_opts(item);	\
+	return f_midi2_opts_bool_store(opts->ep->opts, &opts->info.name,\
+				       page, len);			\
+}									\
+									\
+CONFIGFS_ATTR(f_midi2_block_opts_, name)
+
+F_MIDI2_BLOCK_OPT(direction, "0x%x", 1, 3);
+F_MIDI2_BLOCK_OPT(first_group, "0x%x", 0, 15);
+F_MIDI2_BLOCK_OPT(num_groups, "0x%x", 1, 16);
+F_MIDI2_BLOCK_OPT(ui_hint, "0x%x", 0, 3);
+F_MIDI2_BLOCK_OPT(midi_ci_version, "%u", 0, 1);
+F_MIDI2_BLOCK_OPT(sysex8_streams, "%u", 0, 255);
+F_MIDI2_BLOCK_OPT(is_midi1, "%u", 0, 2);
+F_MIDI2_BLOCK_BOOL_OPT(active);
+
+static ssize_t f_midi2_block_opts_name_show(struct config_item *item,
+					    char *page)
+{
+	struct f_midi2_block_opts *opts = to_f_midi2_block_opts(item);
+
+	return f_midi2_opts_str_show(opts->ep->opts, opts->info.name, page);
+}
+
+static ssize_t f_midi2_block_opts_name_store(struct config_item *item,
+					     const char *page, size_t len)
+{
+	struct f_midi2_block_opts *opts = to_f_midi2_block_opts(item);
+
+	return f_midi2_opts_str_store(opts->ep->opts, &opts->info.name, 128,
+				      page, len);
+}
+
+CONFIGFS_ATTR(f_midi2_block_opts_, name);
+
+static struct configfs_attribute *f_midi2_block_attrs[] = {
+	&f_midi2_block_opts_attr_direction,
+	&f_midi2_block_opts_attr_first_group,
+	&f_midi2_block_opts_attr_num_groups,
+	&f_midi2_block_opts_attr_ui_hint,
+	&f_midi2_block_opts_attr_midi_ci_version,
+	&f_midi2_block_opts_attr_sysex8_streams,
+	&f_midi2_block_opts_attr_is_midi1,
+	&f_midi2_block_opts_attr_active,
+	&f_midi2_block_opts_attr_name,
+	NULL,
+};
+
+static void f_midi2_block_opts_release(struct config_item *item)
+{
+	struct f_midi2_block_opts *opts = to_f_midi2_block_opts(item);
+
+	kfree(opts->info.name);
+	kfree(opts);
+}
+
+static struct configfs_item_operations f_midi2_block_item_ops = {
+	.release	= f_midi2_block_opts_release,
+};
+
+static const struct config_item_type f_midi2_block_type = {
+	.ct_item_ops	= &f_midi2_block_item_ops,
+	.ct_attrs	= f_midi2_block_attrs,
+	.ct_owner	= THIS_MODULE,
+};
+
 /* create a f_midi2_block_opts instance for the given block number */
 static int f_midi2_block_opts_create(struct f_midi2_ep_opts *ep_opts,
 				     unsigned int blk,
 				     struct f_midi2_block_opts **block_p)
 {
 	struct f_midi2_block_opts *block_opts;
+	int ret = 0;
+
+	mutex_lock(&ep_opts->opts->lock);
+	if (ep_opts->opts->refcnt || ep_opts->blks[blk]) {
+		ret = -EBUSY;
+		goto out;
+	}
 
 	block_opts = kzalloc(sizeof(*block_opts), GFP_KERNEL);
-	if (!block_opts)
-		return -ENOMEM;
+	if (!block_opts) {
+		ret = -ENOMEM;
+		goto out;
+	}
 
 	block_opts->ep = ep_opts;
 	block_opts->id = blk;
@@ -1533,9 +1793,143 @@ static int f_midi2_block_opts_create(struct f_midi2_ep_opts *ep_opts,
 
 	ep_opts->blks[blk] = block_opts;
 	*block_p = block_opts;
-	return 0;
+
+	mutex_unlock(&ep_opts->opts->lock);
+ out:
+	return ret;
 }
 
+/* make_group callback for a block */
+static struct config_group *
+f_midi2_opts_block_make(struct config_group *group, const char *name)
+{
+	struct f_midi2_ep_opts *ep_opts;
+	struct f_midi2_block_opts *block_opts;
+	unsigned int blk;
+	int ret;
+
+	if (strncmp(name, "block.", 6))
+		return ERR_PTR(-EINVAL);
+	ret = kstrtouint(name + 6, 10, &blk);
+	if (ret)
+		return ERR_PTR(ret);
+
+	ep_opts = to_f_midi2_ep_opts(&group->cg_item);
+
+	if (blk >= SNDRV_UMP_MAX_BLOCKS)
+		return ERR_PTR(-EINVAL);
+	if (ep_opts->blks[blk])
+		return ERR_PTR(-EBUSY);
+	ret = f_midi2_block_opts_create(ep_opts, blk, &block_opts);
+	if (ret)
+		return ERR_PTR(ret);
+
+	config_group_init_type_name(&block_opts->group, name,
+				    &f_midi2_block_type);
+	return &block_opts->group;
+}
+
+/* drop_item callback for a block */
+static void
+f_midi2_opts_block_drop(struct config_group *group, struct config_item *item)
+{
+	struct f_midi2_block_opts *block_opts = to_f_midi2_block_opts(item);
+
+	mutex_lock(&block_opts->ep->opts->lock);
+	block_opts->ep->blks[block_opts->id] = NULL;
+	mutex_unlock(&block_opts->ep->opts->lock);
+	config_item_put(item);
+}
+
+/*
+ * Definitions for UMP Endpoint config
+ */
+
+/* define an uint option for EP */
+#define F_MIDI2_EP_OPT(name, format, minval, maxval)			\
+static ssize_t f_midi2_ep_opts_##name##_show(struct config_item *item,	\
+					     char *page)		\
+{									\
+	struct f_midi2_ep_opts *opts = to_f_midi2_ep_opts(item);	\
+	return f_midi2_opts_uint_show(opts->opts, opts->info.name,	\
+				      format "\n", page);		\
+}									\
+									\
+static ssize_t f_midi2_ep_opts_##name##_store(struct config_item *item,	\
+					   const char *page, size_t len)\
+{									\
+	struct f_midi2_ep_opts *opts = to_f_midi2_ep_opts(item);	\
+	return f_midi2_opts_uint_store(opts->opts, &opts->info.name,	\
+				       minval, maxval, page, len);	\
+}									\
+									\
+CONFIGFS_ATTR(f_midi2_ep_opts_, name)
+
+/* define a string option for EP */
+#define F_MIDI2_EP_STR_OPT(name, maxlen)				\
+static ssize_t f_midi2_ep_opts_##name##_show(struct config_item *item,	\
+					     char *page)		\
+{									\
+	struct f_midi2_ep_opts *opts = to_f_midi2_ep_opts(item);	\
+	return f_midi2_opts_str_show(opts->opts, opts->info.name, page);\
+}									\
+									\
+static ssize_t f_midi2_ep_opts_##name##_store(struct config_item *item,	\
+					 const char *page, size_t len)	\
+{									\
+	struct f_midi2_ep_opts *opts = to_f_midi2_ep_opts(item);	\
+	return f_midi2_opts_str_store(opts->opts, &opts->info.name, maxlen,\
+				      page, len);			\
+}									\
+									\
+CONFIGFS_ATTR(f_midi2_ep_opts_, name)
+
+F_MIDI2_EP_OPT(protocol, "0x%x", 1, 2);
+F_MIDI2_EP_OPT(protocol_caps, "0x%x", 1, 3);
+F_MIDI2_EP_OPT(manufacturer, "0x%x", 0, 0xffffff);
+F_MIDI2_EP_OPT(family, "0x%x", 0, 0xffff);
+F_MIDI2_EP_OPT(model, "0x%x", 0, 0xffff);
+F_MIDI2_EP_OPT(sw_revision, "0x%x", 0, 0xffffffff);
+F_MIDI2_EP_STR_OPT(ep_name, 128);
+F_MIDI2_EP_STR_OPT(product_id, 128);
+
+static struct configfs_attribute *f_midi2_ep_attrs[] = {
+	&f_midi2_ep_opts_attr_protocol,
+	&f_midi2_ep_opts_attr_protocol_caps,
+	&f_midi2_ep_opts_attr_ep_name,
+	&f_midi2_ep_opts_attr_product_id,
+	&f_midi2_ep_opts_attr_manufacturer,
+	&f_midi2_ep_opts_attr_family,
+	&f_midi2_ep_opts_attr_model,
+	&f_midi2_ep_opts_attr_sw_revision,
+	NULL,
+};
+
+static void f_midi2_ep_opts_release(struct config_item *item)
+{
+	struct f_midi2_ep_opts *opts = to_f_midi2_ep_opts(item);
+
+	kfree(opts->info.ep_name);
+	kfree(opts->info.product_id);
+	kfree(opts);
+}
+
+static struct configfs_item_operations f_midi2_ep_item_ops = {
+	.release	= f_midi2_ep_opts_release,
+};
+
+static struct configfs_group_operations f_midi2_ep_group_ops = {
+	.make_group	= f_midi2_opts_block_make,
+	.drop_item	= f_midi2_opts_block_drop,
+};
+
+static const struct config_item_type f_midi2_ep_type = {
+	.ct_item_ops	= &f_midi2_ep_item_ops,
+	.ct_group_ops	= &f_midi2_ep_group_ops,
+	.ct_attrs	= f_midi2_ep_attrs,
+	.ct_owner	= THIS_MODULE,
+};
+
 /* create a f_midi2_ep_opts instance */
 static int f_midi2_ep_opts_create(struct f_midi2_opts *opts,
 				  unsigned int index,
@@ -1559,7 +1953,119 @@ static int f_midi2_ep_opts_create(struct f_midi2_opts *opts,
 	return 0;
 }
 
+/* make_group callback for an EP */
+static struct config_group *
+f_midi2_opts_ep_make(struct config_group *group, const char *name)
+{
+	struct f_midi2_opts *opts;
+	struct f_midi2_ep_opts *ep_opts;
+	unsigned int index;
+	int ret;
+
+	if (strncmp(name, "ep.", 3))
+		return ERR_PTR(-EINVAL);
+	ret = kstrtouint(name + 3, 10, &index);
+	if (ret)
+		return ERR_PTR(ret);
+
+	opts = to_f_midi2_opts(&group->cg_item);
+	if (index >= MAX_UMP_EPS)
+		return ERR_PTR(-EINVAL);
+	if (opts->eps[index])
+		return ERR_PTR(-EBUSY);
+	ret = f_midi2_ep_opts_create(opts, index, &ep_opts);
+	if (ret)
+		return ERR_PTR(ret);
+
+	config_group_init_type_name(&ep_opts->group, name, &f_midi2_ep_type);
+	return &ep_opts->group;
+}
+
+/* drop_item callback for an EP */
+static void
+f_midi2_opts_ep_drop(struct config_group *group, struct config_item *item)
+{
+	struct f_midi2_ep_opts *ep_opts = to_f_midi2_ep_opts(item);
+
+	mutex_lock(&ep_opts->opts->lock);
+	ep_opts->opts->eps[ep_opts->index] = NULL;
+	mutex_unlock(&ep_opts->opts->lock);
+	config_item_put(item);
+}
+
+/*
+ * Definitions for card config
+ */
+
+/* define a bool option for card */
+#define F_MIDI2_BOOL_OPT(name)						\
+static ssize_t f_midi2_opts_##name##_show(struct config_item *item,	\
+					  char *page)			\
+{									\
+	struct f_midi2_opts *opts = to_f_midi2_opts(item);		\
+	return f_midi2_opts_uint_show(opts, opts->info.name,		\
+				      "%u\n", page);			\
+}									\
+									\
+static ssize_t f_midi2_opts_##name##_store(struct config_item *item,	\
+					 const char *page, size_t len)	\
+{									\
+	struct f_midi2_opts *opts = to_f_midi2_opts(item);		\
+	return f_midi2_opts_bool_store(opts, &opts->info.name,		\
+				       page, len);			\
+}									\
+									\
+CONFIGFS_ATTR(f_midi2_opts_, name)
+
+F_MIDI2_BOOL_OPT(process_ump);
+F_MIDI2_BOOL_OPT(static_block);
+
+static ssize_t f_midi2_opts_iface_name_show(struct config_item *item,
+					    char *page)
+{
+	struct f_midi2_opts *opts = to_f_midi2_opts(item);
+
+	return f_midi2_opts_str_show(opts, opts->info.iface_name, page);
+}
+
+static ssize_t f_midi2_opts_iface_name_store(struct config_item *item,
+					     const char *page, size_t len)
+{
+	struct f_midi2_opts *opts = to_f_midi2_opts(item);
+
+	return f_midi2_opts_str_store(opts, &opts->info.iface_name, 128,
+				      page, len);
+}
+
+CONFIGFS_ATTR(f_midi2_opts_, iface_name);
+
+static struct configfs_attribute *f_midi2_attrs[] = {
+	&f_midi2_opts_attr_process_ump,
+	&f_midi2_opts_attr_static_block,
+	&f_midi2_opts_attr_iface_name,
+	NULL
+};
+
+static void f_midi2_opts_release(struct config_item *item)
+{
+	struct f_midi2_opts *opts = to_f_midi2_opts(item);
+
+	usb_put_function_instance(&opts->func_inst);
+}
+
+static struct configfs_item_operations f_midi2_item_ops = {
+	.release	= f_midi2_opts_release,
+};
+
+static struct configfs_group_operations f_midi2_group_ops = {
+	.make_group	= f_midi2_opts_ep_make,
+	.drop_item	= f_midi2_opts_ep_drop,
+};
+
 static const struct config_item_type f_midi2_func_type = {
+	.ct_item_ops	= &f_midi2_item_ops,
+	.ct_group_ops	= &f_midi2_group_ops,
+	.ct_attrs	= f_midi2_attrs,
 	.ct_owner	= THIS_MODULE,
 };
 
@@ -1569,11 +2075,7 @@ static void f_midi2_free_inst(struct usb_function_instance *f)
 
 	opts = container_of(f, struct f_midi2_opts, func_inst);
 
-	/* we have only one EP and one FB */
-	if (opts->eps[0]) {
-		kfree(opts->eps[0]->blks[0]);
-		kfree(opts->eps[0]);
-	}
+	kfree(opts->info.iface_name);
 	kfree(opts);
 }
 
@@ -1596,6 +2098,7 @@ static struct usb_function_instance *f_midi2_alloc_inst(void)
 	opts->info.num_reqs = 32;
 	opts->info.req_buf_size = 512;
 
+	/* create the default ep */
 	ret = f_midi2_ep_opts_create(opts, 0, &ep_opts);
 	if (ret) {
 		kfree(opts);
@@ -1612,6 +2115,15 @@ static struct usb_function_instance *f_midi2_alloc_inst(void)
 
 	config_group_init_type_name(&opts->func_inst.group, "",
 				    &f_midi2_func_type);
+
+	config_group_init_type_name(&ep_opts->group, "ep.0",
+				    &f_midi2_ep_type);
+	configfs_add_default_group(&ep_opts->group, &opts->func_inst.group);
+
+	config_group_init_type_name(&block_opts->group, "block.0",
+				    &f_midi2_block_type);
+	configfs_add_default_group(&block_opts->group, &ep_opts->group);
+
 	return &opts->func_inst;
 }
 
@@ -1630,12 +2142,58 @@ static void f_midi2_free(struct usb_function *f)
 			container_of(f->fi, struct f_midi2_opts, func_inst));
 }
 
+/* verify the parameters set up via configfs;
+ * return the number of EPs or a negative error
+ */
+static int verify_parameters(struct f_midi2_opts *opts)
+{
+	int i, j, num_eps, num_blks;
+	struct f_midi2_ep_info *ep;
+	struct f_midi2_block_info *bp;
+
+	for (num_eps = 0; num_eps < MAX_UMP_EPS && opts->eps[num_eps];
+	     num_eps++)
+		;
+	if (!num_eps) {
+		pr_err("f_midi2: No EP is defined\n");
+		return -EINVAL;
+	}
+
+	num_blks = 0;
+	for (i = 0; i < num_eps; i++) {
+		ep = &opts->eps[i]->info;
+		if (!(ep->protocol_caps & ep->protocol)) {
+			pr_err("f_midi2: Invalid protocol 0x%x (caps 0x%x) for EP %d\n",
+			       ep->protocol, ep->protocol_caps, i);
+			return -EINVAL;
+		}
+
+		for (j = 0; j < SNDRV_UMP_MAX_BLOCKS && opts->eps[i]->blks[j];
+		     j++, num_blks++) {
+			bp = &opts->eps[i]->blks[j]->info;
+			if (bp->first_group + bp->num_groups > SNDRV_UMP_MAX_GROUPS) {
+				pr_err("f_midi2: Invalid group definitions for block %d:%d\n",
+				       i, j);
+				return -EINVAL;
+			}
+		}
+	}
+	if (!num_blks) {
+		pr_err("f_midi2: No block is defined\n");
+		return -EINVAL;
+	}
+
+	return num_eps;
+}
+
 /* gadget alloc callback */
 static struct usb_function *f_midi2_alloc(struct usb_function_instance *fi)
 {
 	struct f_midi2 *midi2;
 	struct f_midi2_opts *opts;
-	int i;
+	struct f_midi2_ep *ep;
+	struct f_midi2_block *bp;
+	int i, num_eps, blk;
 
 	midi2 = kzalloc(sizeof(*midi2), GFP_KERNEL);
 	if (!midi2)
@@ -1643,6 +2201,12 @@ static struct usb_function *f_midi2_alloc(struct usb_function_instance *fi)
 
 	opts = container_of(fi, struct f_midi2_opts, func_inst);
 	mutex_lock(&opts->lock);
+	num_eps = verify_parameters(opts);
+	if (num_eps < 0) {
+		mutex_unlock(&opts->lock);
+		kfree(midi2);
+		return ERR_PTR(num_eps);
+	}
 	++opts->refcnt;
 	mutex_unlock(&opts->lock);
 
@@ -1658,17 +2222,20 @@ static struct usb_function *f_midi2_alloc(struct usb_function_instance *fi)
 	midi2->func.free_func = f_midi2_free;
 
 	midi2->info = opts->info;
+	midi2->num_eps = num_eps;
 
-	/* fixed 1 UMP EP and 1 UMP FB as of now */
-	midi2->num_eps = 1;
-	midi2->midi2_eps[0].info = opts->eps[0]->info;
-	midi2->midi2_eps[0].card = midi2;
-	midi2->midi2_eps[0].num_blks = 1;
-	midi2->midi2_eps[0].blks[0].info = opts->eps[0]->blks[0]->info;
-	midi2->midi2_eps[0].blks[0].gtb_id = 1;
-
-	for (i = 0; i < midi2->num_eps; i++)
-		midi2->total_blocks += midi2->midi2_eps[i].num_blks;
+	for (i = 0; i < num_eps; i++) {
+		ep = &midi2->midi2_eps[i];
+		ep->info = opts->eps[i]->info;
+		ep->card = midi2;
+		for (blk = 0; blk < SNDRV_UMP_MAX_BLOCKS &&
+			     opts->eps[i]->blks[blk]; blk++) {
+			bp = &ep->blks[blk];
+			ep->num_blks++;
+			bp->info = opts->eps[i]->blks[blk]->info;
+			bp->gtb_id = ++midi2->total_blocks;
+		}
+	}
 
 	midi2->string_defs = kcalloc(midi2->total_blocks + 1,
 				     sizeof(*midi2->string_defs), GFP_KERNEL);
@@ -1678,10 +2245,18 @@ static struct usb_function *f_midi2_alloc(struct usb_function_instance *fi)
 	}
 
 	if (opts->info.iface_name && *opts->info.iface_name)
-		midi2->string_defs[0].s = opts->info.iface_name;
+		midi2->string_defs[STR_IFACE].s = opts->info.iface_name;
 	else
-		midi2->string_defs[0].s = ump_ep_name(&midi2->midi2_eps[0]);
-	midi2->string_defs[1].s = ump_fb_name(&midi2->midi2_eps[0].blks[0].info);
+		midi2->string_defs[STR_IFACE].s = ump_ep_name(&midi2->midi2_eps[0]);
+
+	for (i = 0; i < midi2->num_eps; i++) {
+		ep = &midi2->midi2_eps[i];
+		for (blk = 0; blk < ep->num_blks; blk++) {
+			bp = &ep->blks[blk];
+			midi2->string_defs[gtb_to_str_id(bp->gtb_id)].s =
+				ump_fb_name(&bp->info);
+		}
+	}
 
 	return &midi2->func;
 }
-- 
2.35.3


^ permalink raw reply related	[flat|nested] 9+ messages in thread

* [PATCH 3/7] usb: gadget: midi2: Dynamically create MIDI 1.0 altset descriptors
  2023-07-25  6:21 [PATCH 0/7] usb: Add USB MIDI 2.0 Gadget Function Driver Takashi Iwai
  2023-07-25  6:22 ` [PATCH 1/7] usb: gadget: Add support for USB MIDI 2.0 function driver Takashi Iwai
  2023-07-25  6:22 ` [PATCH 2/7] usb: gadget: midi2: Add configfs support Takashi Iwai
@ 2023-07-25  6:22 ` Takashi Iwai
  2023-07-25  6:22 ` [PATCH 4/7] usb: gadget: midi2: MIDI 1.0 interface (altset 0) support Takashi Iwai
                   ` (4 subsequent siblings)
  7 siblings, 0 replies; 9+ messages in thread
From: Takashi Iwai @ 2023-07-25  6:22 UTC (permalink / raw)
  To: Greg Kroah-Hartman; +Cc: alsa-devel, linux-usb, linux-kernel

This patch extends MIDI 2.0 function driver to deal with more MIDI1
Jacks depending on the given Block configuration.

For MIDI 1.0, we take the configuration given in Function Block 0, and
create MIDI Jacks and Endpoints depending on the definition there.
That is, when more UMP Groups are defined in the Block 0, the
corresponding MIDI1 Jacks will be created.

Signed-off-by: Takashi Iwai <tiwai@suse.de>
---
 drivers/usb/gadget/function/f_midi2.c | 228 ++++++++++++++++++--------
 1 file changed, 157 insertions(+), 71 deletions(-)

diff --git a/drivers/usb/gadget/function/f_midi2.c b/drivers/usb/gadget/function/f_midi2.c
index c68a6fa0d237..b15d832ff441 100644
--- a/drivers/usb/gadget/function/f_midi2.c
+++ b/drivers/usb/gadget/function/f_midi2.c
@@ -151,7 +151,7 @@ static struct usb_ms20_gr_trm_block_descriptor gtb_desc = {
 };
 
 DECLARE_USB_MIDI_OUT_JACK_DESCRIPTOR(1);
-DECLARE_USB_MS_ENDPOINT_DESCRIPTOR(1);
+DECLARE_USB_MS_ENDPOINT_DESCRIPTOR(16);
 DECLARE_UAC_AC_HEADER_DESCRIPTOR(1);
 DECLARE_USB_MS20_ENDPOINT_DESCRIPTOR(32);
 
@@ -185,7 +185,7 @@ static struct usb_interface_descriptor midi2_midi1_if_desc = {
 	.bDescriptorType =	USB_DT_INTERFACE,
 	.bInterfaceNumber =	0, // to be filled
 	.bAlternateSetting =	0,
-	.bNumEndpoints =	2,
+	.bNumEndpoints =	2, // to be filled
 	.bInterfaceClass =	USB_CLASS_AUDIO,
 	.bInterfaceSubClass =	USB_SUBCLASS_MIDISTREAMING,
 	.bInterfaceProtocol =	0,
@@ -200,50 +200,6 @@ static struct usb_ms_header_descriptor midi2_midi1_class_desc = {
 	.wTotalLength =		__cpu_to_le16(0x41), // to be calculated
 };
 
-/* MIDI 1.0 IN (Embedded) Jack */
-static struct usb_midi_in_jack_descriptor midi2_midi1_in_jack1_desc = {
-	.bLength =		0x06,
-	.bDescriptorType =	USB_DT_CS_INTERFACE,
-	.bDescriptorSubtype =	USB_MS_MIDI_IN_JACK,
-	.bJackType =		USB_MS_EMBEDDED,
-	.bJackID =		0x01,
-	.iJack =		0,
-};
-
-/* MIDI 1.0 IN (External) Jack */
-static struct usb_midi_in_jack_descriptor midi2_midi1_in_jack2_desc = {
-	.bLength =		0x06,
-	.bDescriptorType =	USB_DT_CS_INTERFACE,
-	.bDescriptorSubtype =	USB_MS_MIDI_IN_JACK,
-	.bJackType =		USB_MS_EXTERNAL,
-	.bJackID =		0x02,
-	.iJack =		0,
-};
-
-/* MIDI 1.0 OUT (Embedded) Jack */
-static struct usb_midi_out_jack_descriptor_1 midi2_midi1_out_jack1_desc = {
-	.bLength =		0x09,
-	.bDescriptorType =	USB_DT_CS_INTERFACE,
-	.bDescriptorSubtype =	USB_MS_MIDI_OUT_JACK,
-	.bJackType =		USB_MS_EMBEDDED,
-	.bJackID =		0x03,
-	.bNrInputPins =		1,
-	.pins =			{ { 0x02, 0x01 } },
-	.iJack =		0,
-};
-
-/* MIDI 1.0 OUT (External) Jack */
-static struct usb_midi_out_jack_descriptor_1 midi2_midi1_out_jack2_desc = {
-	.bLength =		0x09,
-	.bDescriptorType =	USB_DT_CS_INTERFACE,
-	.bDescriptorSubtype =	USB_MS_MIDI_OUT_JACK,
-	.bJackType =		USB_MS_EXTERNAL,
-	.bJackID =		0x04,
-	.bNrInputPins =		1,
-	.pins =			{ { 0x01, 0x01 } },
-	.iJack =		0,
-};
-
 /* MIDI 1.0 EP OUT */
 static struct usb_endpoint_descriptor midi2_midi1_ep_out_desc = {
 	.bLength =		USB_DT_ENDPOINT_AUDIO_SIZE,
@@ -257,8 +213,8 @@ static struct usb_ss_ep_comp_descriptor midi2_midi1_ep_out_ss_comp_desc = {
 	.bDescriptorType        = USB_DT_SS_ENDPOINT_COMP,
 };
 
-static struct usb_ms_endpoint_descriptor_1 midi2_midi1_ep_out_class_desc = {
-	.bLength =		0x05,
+static struct usb_ms_endpoint_descriptor_16 midi2_midi1_ep_out_class_desc = {
+	.bLength =		0x05, // to be filled
 	.bDescriptorType =	USB_DT_CS_ENDPOINT,
 	.bDescriptorSubtype =	USB_MS_GENERAL,
 	.bNumEmbMIDIJack =	1,
@@ -278,8 +234,8 @@ static struct usb_ss_ep_comp_descriptor midi2_midi1_ep_in_ss_comp_desc = {
 	.bDescriptorType        = USB_DT_SS_ENDPOINT_COMP,
 };
 
-static struct usb_ms_endpoint_descriptor_1 midi2_midi1_ep_in_class_desc = {
-	.bLength =		0x05,
+static struct usb_ms_endpoint_descriptor_16 midi2_midi1_ep_in_class_desc = {
+	.bLength =		0x05, // to be filled
 	.bDescriptorType =	USB_DT_CS_ENDPOINT,
 	.bDescriptorSubtype =	USB_MS_GENERAL,
 	.bNumEmbMIDIJack =	1,
@@ -337,25 +293,29 @@ static void *midi2_audio_descs[] = {
 static void *midi2_midi1_descs[] = {
 	&midi2_midi1_if_desc,
 	&midi2_midi1_class_desc,
-	&midi2_midi1_in_jack1_desc,
-	&midi2_midi1_in_jack2_desc,
-	&midi2_midi1_out_jack1_desc,
-	&midi2_midi1_out_jack2_desc,
 	NULL
 };
 
-static void *midi2_midi1_ep_descs[] = {
+static void *midi2_midi1_ep_out_descs[] = {
 	&midi2_midi1_ep_out_desc,
 	&midi2_midi1_ep_out_class_desc,
+	NULL
+};
+
+static void *midi2_midi1_ep_in_descs[] = {
 	&midi2_midi1_ep_in_desc,
 	&midi2_midi1_ep_in_class_desc,
 	NULL
 };
 
-static void *midi2_midi1_ep_ss_descs[] = {
+static void *midi2_midi1_ep_out_ss_descs[] = {
 	&midi2_midi1_ep_out_desc,
 	&midi2_midi1_ep_out_ss_comp_desc,
 	&midi2_midi1_ep_out_class_desc,
+	NULL
+};
+
+static void *midi2_midi1_ep_in_ss_descs[] = {
 	&midi2_midi1_ep_in_desc,
 	&midi2_midi1_ep_in_ss_comp_desc,
 	&midi2_midi1_ep_in_class_desc,
@@ -1197,6 +1157,11 @@ struct f_midi2_usb_config {
 	struct usb_descriptor_header **list;
 	unsigned int size;
 	unsigned int alloc;
+
+	/* MIDI 1.0 jacks */
+	unsigned char jack_in, jack_out, jack_id;
+	struct usb_midi_in_jack_descriptor jack_ins[16];
+	struct usb_midi_out_jack_descriptor_1 jack_outs[16];
 };
 
 static int append_config(struct f_midi2_usb_config *config, void *d)
@@ -1231,12 +1196,61 @@ static int append_configs(struct f_midi2_usb_config *config, void **d)
 	return 0;
 }
 
+static int append_midi1_in_jack(struct f_midi2 *midi2,
+				struct f_midi2_usb_config *config,
+				unsigned int type)
+{
+	struct usb_midi_in_jack_descriptor *jack =
+		&config->jack_ins[config->jack_in++];
+	int id = ++config->jack_id;
+	int err;
+
+	jack->bLength = 0x06;
+	jack->bDescriptorType = USB_DT_CS_INTERFACE;
+	jack->bDescriptorSubtype = USB_MS_MIDI_IN_JACK;
+	jack->bJackType = type;
+	jack->bJackID = id;
+	jack->iJack = midi2->strings[STR_GTB1].id; // TODO: better names?
+
+	err = append_config(config, jack);
+	if (err < 0)
+		return err;
+	return id;
+}
+
+static int append_midi1_out_jack(struct f_midi2 *midi2,
+				 struct f_midi2_usb_config *config,
+				 unsigned int type, unsigned int source)
+{
+	struct usb_midi_out_jack_descriptor_1 *jack =
+		&config->jack_outs[config->jack_out++];
+	int id = ++config->jack_id;
+	int err;
+
+	jack->bLength = 0x09;
+	jack->bDescriptorType = USB_DT_CS_INTERFACE;
+	jack->bDescriptorSubtype = USB_MS_MIDI_OUT_JACK;
+	jack->bJackType = type;
+	jack->bJackID = id;
+	jack->bNrInputPins = 1;
+	jack->pins[0].baSourceID = source;
+	jack->pins[0].baSourcePin = 0x01;
+	jack->iJack = midi2->strings[STR_GTB1].id; // TODO: better names?
+
+	err = append_config(config, jack);
+	if (err < 0)
+		return err;
+	return id;
+}
+
 static int f_midi2_create_usb_configs(struct f_midi2 *midi2,
 				      struct f_midi2_usb_config *config,
 				      int speed)
 {
-	void **midi1_eps;
-	int i, err;
+	struct f_midi2_block *blk = &midi2->midi2_eps[0].blks[0];
+	void **midi1_in_eps, **midi1_out_eps;
+	int i, jack, total;
+	int err;
 
 	switch (speed) {
 	default:
@@ -1248,7 +1262,8 @@ static int f_midi2_create_usb_configs(struct f_midi2 *midi2,
 				cpu_to_le16(512);
 		fallthrough;
 	case USB_SPEED_FULL:
-		midi1_eps = midi2_midi1_ep_descs;
+		midi1_in_eps = midi2_midi1_ep_in_descs;
+		midi1_out_eps = midi2_midi1_ep_out_descs;
 		break;
 	case USB_SPEED_SUPER:
 	case USB_SPEED_SUPER_PLUS:
@@ -1257,19 +1272,85 @@ static int f_midi2_create_usb_configs(struct f_midi2 *midi2,
 		for (i = 0; i < midi2->num_eps; i++)
 			midi2_midi2_ep_out_desc[i].wMaxPacketSize =
 				cpu_to_le16(1024);
-		midi1_eps = midi2_midi1_ep_ss_descs;
+		midi1_in_eps = midi2_midi1_ep_in_ss_descs;
+		midi1_out_eps = midi2_midi1_ep_out_ss_descs;
 		break;
 	}
 
 	err = append_configs(config, midi2_audio_descs);
 	if (err < 0)
 		return err;
+
+	switch (blk->info.direction) {
+	case SNDRV_UMP_DIR_INPUT:
+	case SNDRV_UMP_DIR_OUTPUT:
+		midi2_midi1_if_desc.bNumEndpoints = 1;
+		break;
+	default:
+		midi2_midi1_if_desc.bNumEndpoints = 2;
+		break;
+	}
+
 	err = append_configs(config, midi2_midi1_descs);
 	if (err < 0)
 		return err;
-	err = append_configs(config, midi1_eps);
-	if (err < 0)
-		return err;
+
+	total = USB_DT_MS_HEADER_SIZE;
+	if (blk->info.direction != SNDRV_UMP_DIR_INPUT) {
+		midi2_midi1_ep_out_class_desc.bLength =
+			USB_DT_MS_ENDPOINT_SIZE(blk->info.num_groups);
+		total += midi2_midi1_ep_out_class_desc.bLength;
+		midi2_midi1_ep_out_class_desc.bNumEmbMIDIJack =
+			blk->info.num_groups;
+		total += blk->info.num_groups *
+			(USB_DT_MIDI_IN_SIZE + USB_DT_MIDI_OUT_SIZE(1));
+		for (i = 0; i < blk->info.num_groups; i++) {
+			jack = append_midi1_in_jack(midi2, config,
+						    USB_MS_EMBEDDED);
+			if (jack < 0)
+				return jack;
+			midi2_midi1_ep_out_class_desc.baAssocJackID[i] = jack;
+			jack = append_midi1_out_jack(midi2, config,
+						     USB_MS_EXTERNAL, jack);
+			if (jack < 0)
+				return jack;
+		}
+	}
+
+	if (blk->info.direction != SNDRV_UMP_DIR_OUTPUT) {
+		midi2_midi1_ep_in_class_desc.bLength =
+			USB_DT_MS_ENDPOINT_SIZE(blk->info.num_groups);
+		total += midi2_midi1_ep_in_class_desc.bLength;
+		midi2_midi1_ep_in_class_desc.bNumEmbMIDIJack =
+			blk->info.num_groups;
+		total += blk->info.num_groups *
+			(USB_DT_MIDI_IN_SIZE + USB_DT_MIDI_OUT_SIZE(1));
+		for (i = 0; i < blk->info.num_groups; i++) {
+			jack = append_midi1_in_jack(midi2, config,
+						    USB_MS_EXTERNAL);
+			if (jack < 0)
+				return jack;
+			jack = append_midi1_out_jack(midi2, config,
+						     USB_MS_EMBEDDED, jack);
+			if (jack < 0)
+				return jack;
+			midi2_midi1_ep_in_class_desc.baAssocJackID[i] = jack;
+		}
+	}
+
+	midi2_midi1_class_desc.wTotalLength = cpu_to_le16(total);
+
+	if (blk->info.direction != SNDRV_UMP_DIR_INPUT) {
+		err = append_configs(config, midi1_out_eps);
+		if (err < 0)
+			return err;
+	}
+	if (blk->info.direction != SNDRV_UMP_DIR_OUTPUT) {
+		err = append_configs(config, midi1_in_eps);
+		if (err < 0)
+			return err;
+	}
+
 	err = append_configs(config, midi2_midi2_descs);
 	if (err < 0)
 		return err;
@@ -1421,14 +1502,19 @@ static int f_midi2_bind(struct usb_configuration *c, struct usb_function *f)
 	midi2_audio_class_desc.baInterfaceNr[0] = status;
 
 	/* allocate instance-specific endpoints */
-	status = f_midi2_init_ep(midi2, NULL, &midi2->midi1_ep_in,
-				 &midi2_midi1_ep_in_desc, 0, NULL);
-	if (status)
-		goto fail;
-	status = f_midi2_init_ep(midi2, NULL, &midi2->midi1_ep_out,
-				 &midi2_midi1_ep_out_desc, 0, NULL);
-	if (status)
-		goto fail;
+	if (midi2->midi2_eps[0].blks[0].info.direction != SNDRV_UMP_DIR_OUTPUT) {
+		status = f_midi2_init_ep(midi2, NULL, &midi2->midi1_ep_in,
+					 &midi2_midi1_ep_in_desc, 0, NULL);
+		if (status)
+			goto fail;
+	}
+
+	if (midi2->midi2_eps[0].blks[0].info.direction != SNDRV_UMP_DIR_INPUT) {
+		status = f_midi2_init_ep(midi2, NULL, &midi2->midi1_ep_out,
+					 &midi2_midi1_ep_out_desc, 0, NULL);
+		if (status)
+			goto fail;
+	}
 
 	for (i = 0; i < midi2->num_eps; i++) {
 		status = f_midi2_init_midi2_ep_in(midi2, i);
-- 
2.35.3


^ permalink raw reply related	[flat|nested] 9+ messages in thread

* [PATCH 4/7] usb: gadget: midi2: MIDI 1.0 interface (altset 0) support
  2023-07-25  6:21 [PATCH 0/7] usb: Add USB MIDI 2.0 Gadget Function Driver Takashi Iwai
                   ` (2 preceding siblings ...)
  2023-07-25  6:22 ` [PATCH 3/7] usb: gadget: midi2: Dynamically create MIDI 1.0 altset descriptors Takashi Iwai
@ 2023-07-25  6:22 ` Takashi Iwai
  2023-07-25  6:22 ` [PATCH 5/7] usb: gadget: midi2: Add testing documentation Takashi Iwai
                   ` (3 subsequent siblings)
  7 siblings, 0 replies; 9+ messages in thread
From: Takashi Iwai @ 2023-07-25  6:22 UTC (permalink / raw)
  To: Greg Kroah-Hartman; +Cc: alsa-devel, linux-usb, linux-kernel

This patch extends MIDI 2.0 function driver to add more proper support
for MIDI 1.0 interface.  Before this patch, the driver only exposes
the USB descriptor of a MIDI 1.0 interface in altset 0 while no actual
I/O is running for it.  This patch enables the actual I/O for the
altset 0; the backend UMP rawmidi is translated from/to the MIDI 1.0
USB commands.

For converting to USB MIDI 1.0 data protocol, a helper function is
copied from the existing f_midi driver, in addition to a few other UMP
Core helper functions.  For the MIDI 1.0 OUT (that is, input for
gadget), the incoming USB MIDI 1.0 packet is translated to UMP packets
via UMP Core helper, and tossed to the attached UMP rawmidi.  It's a
relatively straightforward.  OTOH, for MIDI 1.0 IN (i.e. output for
gadget), it's a bit more complex: we need to convert a source UMP
packet once to the standard MIDI 1.0 byte stream, and convert it again
to USB MIDI 1.0 packets, then send them out.

Signed-off-by: Takashi Iwai <tiwai@suse.de>
---
 drivers/usb/gadget/function/f_midi2.c | 494 +++++++++++++++++++++++---
 1 file changed, 452 insertions(+), 42 deletions(-)

diff --git a/drivers/usb/gadget/function/f_midi2.c b/drivers/usb/gadget/function/f_midi2.c
index b15d832ff441..a368ac51d349 100644
--- a/drivers/usb/gadget/function/f_midi2.c
+++ b/drivers/usb/gadget/function/f_midi2.c
@@ -11,6 +11,7 @@
 #include <sound/core.h>
 #include <sound/ump.h>
 #include <sound/ump_msg.h>
+#include <sound/ump_convert.h>
 
 #include <linux/usb/ch9.h>
 #include <linux/usb/gadget.h>
@@ -50,6 +51,27 @@ struct f_midi2_block {
 	unsigned int string_id;		/* assigned string id */
 };
 
+/* Temporary buffer for altset 0 MIDI 1.0 handling */
+struct f_midi2_midi1_port {
+	unsigned int pending; /* pending bytes on the input buffer */
+	u8 buf[32];	/* raw MIDI 1.0 byte input */
+	u8 state;	/* running status */
+	u8 data[2];	/* rendered USB MIDI 1.0 packet data */
+};
+
+/* MIDI 1.0 message states */
+enum {
+	STATE_INITIAL = 0,	/* pseudo state */
+	STATE_1PARAM,
+	STATE_2PARAM_1,
+	STATE_2PARAM_2,
+	STATE_SYSEX_0,
+	STATE_SYSEX_1,
+	STATE_SYSEX_2,
+	STATE_REAL_TIME,
+	STATE_FINISHED,		/* pseudo state */
+};
+
 /* Resources for UMP Endpoint */
 struct f_midi2_ep {
 	struct snd_ump_endpoint *ump;	/* assigned UMP EP */
@@ -89,6 +111,11 @@ struct f_midi2 {
 	struct f_midi2_usb_ep midi1_ep_in;
 	struct f_midi2_usb_ep midi1_ep_out;
 
+	/* conversion for MIDI 1.0 EP-in */
+	struct f_midi2_midi1_port midi1_port[MAX_CABLES];
+	/* conversion for MIDI 1.0 EP-out */
+	struct ump_cvt_to_ump midi1_ump_cvt;
+
 	int midi_if;			/* USB MIDI interface number */
 	int operation_mode;		/* current operation mode */
 
@@ -707,12 +734,353 @@ static void f_midi2_ep_in_complete(struct usb_ep *usb_ep,
 	process_ump_transmit(ep);
 }
 
+/*
+ * MIDI1 (altset 0) USB request handling
+ */
+
+/* process one MIDI byte -- copied from f_midi.c
+ *
+ * fill the packet or request if needed
+ * returns true if the request became empty (queued)
+ */
+static bool process_midi1_byte(struct f_midi2 *midi2, u8 cable, u8 b,
+			       struct usb_request **req_p)
+{
+	struct f_midi2_midi1_port *port = &midi2->midi1_port[cable];
+	u8 p[4] = { cable << 4, 0, 0, 0 };
+	int next_state = STATE_INITIAL;
+	struct usb_request *req = *req_p;
+
+	switch (b) {
+	case 0xf8 ... 0xff:
+		/* System Real-Time Messages */
+		p[0] |= 0x0f;
+		p[1] = b;
+		next_state = port->state;
+		port->state = STATE_REAL_TIME;
+		break;
+
+	case 0xf7:
+		/* End of SysEx */
+		switch (port->state) {
+		case STATE_SYSEX_0:
+			p[0] |= 0x05;
+			p[1] = 0xf7;
+			next_state = STATE_FINISHED;
+			break;
+		case STATE_SYSEX_1:
+			p[0] |= 0x06;
+			p[1] = port->data[0];
+			p[2] = 0xf7;
+			next_state = STATE_FINISHED;
+			break;
+		case STATE_SYSEX_2:
+			p[0] |= 0x07;
+			p[1] = port->data[0];
+			p[2] = port->data[1];
+			p[3] = 0xf7;
+			next_state = STATE_FINISHED;
+			break;
+		default:
+			/* Ignore byte */
+			next_state = port->state;
+			port->state = STATE_INITIAL;
+		}
+		break;
+
+	case 0xf0 ... 0xf6:
+		/* System Common Messages */
+		port->data[0] = port->data[1] = 0;
+		port->state = STATE_INITIAL;
+		switch (b) {
+		case 0xf0:
+			port->data[0] = b;
+			port->data[1] = 0;
+			next_state = STATE_SYSEX_1;
+			break;
+		case 0xf1:
+		case 0xf3:
+			port->data[0] = b;
+			next_state = STATE_1PARAM;
+			break;
+		case 0xf2:
+			port->data[0] = b;
+			next_state = STATE_2PARAM_1;
+			break;
+		case 0xf4:
+		case 0xf5:
+			next_state = STATE_INITIAL;
+			break;
+		case 0xf6:
+			p[0] |= 0x05;
+			p[1] = 0xf6;
+			next_state = STATE_FINISHED;
+			break;
+		}
+		break;
+
+	case 0x80 ... 0xef:
+		/*
+		 * Channel Voice Messages, Channel Mode Messages
+		 * and Control Change Messages.
+		 */
+		port->data[0] = b;
+		port->data[1] = 0;
+		port->state = STATE_INITIAL;
+		if (b >= 0xc0 && b <= 0xdf)
+			next_state = STATE_1PARAM;
+		else
+			next_state = STATE_2PARAM_1;
+		break;
+
+	case 0x00 ... 0x7f:
+		/* Message parameters */
+		switch (port->state) {
+		case STATE_1PARAM:
+			if (port->data[0] < 0xf0)
+				p[0] |= port->data[0] >> 4;
+			else
+				p[0] |= 0x02;
+
+			p[1] = port->data[0];
+			p[2] = b;
+			/* This is to allow Running State Messages */
+			next_state = STATE_1PARAM;
+			break;
+		case STATE_2PARAM_1:
+			port->data[1] = b;
+			next_state = STATE_2PARAM_2;
+			break;
+		case STATE_2PARAM_2:
+			if (port->data[0] < 0xf0)
+				p[0] |= port->data[0] >> 4;
+			else
+				p[0] |= 0x03;
+
+			p[1] = port->data[0];
+			p[2] = port->data[1];
+			p[3] = b;
+			/* This is to allow Running State Messages */
+			next_state = STATE_2PARAM_1;
+			break;
+		case STATE_SYSEX_0:
+			port->data[0] = b;
+			next_state = STATE_SYSEX_1;
+			break;
+		case STATE_SYSEX_1:
+			port->data[1] = b;
+			next_state = STATE_SYSEX_2;
+			break;
+		case STATE_SYSEX_2:
+			p[0] |= 0x04;
+			p[1] = port->data[0];
+			p[2] = port->data[1];
+			p[3] = b;
+			next_state = STATE_SYSEX_0;
+			break;
+		}
+		break;
+	}
+
+	/* States where we have to write into the USB request */
+	if (next_state == STATE_FINISHED ||
+	    port->state == STATE_SYSEX_2 ||
+	    port->state == STATE_1PARAM ||
+	    port->state == STATE_2PARAM_2 ||
+	    port->state == STATE_REAL_TIME) {
+		memcpy(req->buf + req->length, p, sizeof(p));
+		req->length += sizeof(p);
+
+		if (next_state == STATE_FINISHED) {
+			next_state = STATE_INITIAL;
+			port->data[0] = port->data[1] = 0;
+		}
+
+		if (midi2->info.req_buf_size - req->length <= 4) {
+			queue_request_ep_raw(req);
+			*req_p = NULL;
+			return true;
+		}
+	}
+
+	port->state = next_state;
+	return false;
+}
+
+/* process all pending MIDI bytes in the internal buffer;
+ * returns true if the request gets empty
+ * returns false if all have been processed
+ */
+static bool process_midi1_pending_buf(struct f_midi2 *midi2,
+				      struct usb_request **req_p)
+{
+	unsigned int cable, c;
+
+	for (cable = 0; cable < midi2->midi2_eps[0].blks[0].info.num_groups;
+	     cable++) {
+		struct f_midi2_midi1_port *port = &midi2->midi1_port[cable];
+
+		if (!port->pending)
+			continue;
+		for (c = 0; c < port->pending; c++) {
+			if (process_midi1_byte(midi2, cable, port->buf[c],
+					       req_p)) {
+				port->pending -= c;
+				if (port->pending)
+					memmove(port->buf, port->buf + c,
+						port->pending);
+				return true;
+			}
+		}
+		port->pending = 0;
+	}
+
+	return false;
+}
+
+/* fill the MIDI bytes onto the temporary buffer
+ */
+static void fill_midi1_pending_buf(struct f_midi2 *midi2, u8 cable, u8 *buf,
+				   unsigned int size)
+{
+	struct f_midi2_midi1_port *port = &midi2->midi1_port[cable];
+
+	if (port->pending + size > sizeof(port->buf))
+		return;
+	memcpy(port->buf + port->pending, buf, size);
+	port->pending += size;
+}
+
+/* try to process data given from the associated UMP stream */
+static void process_midi1_transmit(struct f_midi2 *midi2)
+{
+	struct f_midi2_usb_ep *usb_ep = &midi2->midi1_ep_in;
+	struct f_midi2_ep *ep = &midi2->midi2_eps[0];
+	struct usb_request *req = NULL;
+	/* 12 is the largest outcome (4 MIDI1 cmds) for a single UMP packet */
+	unsigned char outbuf[12];
+	unsigned char group;
+	int len, size, cable;
+	u32 ump;
+
+	if (!usb_ep->usb_ep || !usb_ep->usb_ep->enabled)
+		return;
+
+	for (;;) {
+		if (!req) {
+			req = get_empty_request(usb_ep);
+			if (!req)
+				break;
+		}
+
+		if (process_midi1_pending_buf(midi2, &req))
+			continue;
+
+		len = snd_ump_transmit(ep->ump, &ump, 4);
+		if (len <= 0)
+			break;
+		if (snd_ump_receive_ump_val(ep->ump, ump) <= 0)
+			continue;
+		size = snd_ump_convert_from_ump(ep->ump->input_buf, outbuf,
+						&group);
+		if (size <= 0)
+			continue;
+		cable = group - ep->blks[0].info.first_group;
+		if (cable < 0 || cable >= ep->blks[0].info.num_groups)
+			continue;
+		fill_midi1_pending_buf(midi2, cable, outbuf, size);
+	}
+
+	if (req) {
+		if (req->length)
+			queue_request_ep_raw(req);
+		else
+			put_empty_request(req);
+	}
+}
+
+/* complete handler for MIDI1 EP-in requests */
+static void f_midi2_midi1_ep_in_complete(struct usb_ep *usb_ep,
+					 struct usb_request *req)
+{
+	struct f_midi2_req_ctx *ctx = req->context;
+	struct f_midi2 *midi2 = ctx->usb_ep->card;
+	int status = req->status;
+
+	put_empty_request(req);
+
+	if (status) {
+		DBG(midi2, "%s complete error %d: %d/%d\n",
+		    usb_ep->name, status, req->actual, req->length);
+		return;
+	}
+
+	process_midi1_transmit(midi2);
+}
+
+/* complete handler for MIDI1 EP-out requests */
+static void f_midi2_midi1_ep_out_complete(struct usb_ep *usb_ep,
+					  struct usb_request *req)
+{
+	struct f_midi2_req_ctx *ctx = req->context;
+	struct f_midi2 *midi2 = ctx->usb_ep->card;
+	struct f_midi2_ep *ep = &midi2->midi2_eps[0];
+	struct ump_cvt_to_ump *cvt = &midi2->midi1_ump_cvt;
+	static const u8 midi1_packet_bytes[16] = {
+		0, 0, 2, 3, 3, 1, 2, 3, 3, 3, 3, 3, 2, 2, 3, 1
+	};
+	unsigned int group, bytes, c, len;
+	int status = req->status;
+	const u8 *buf = req->buf;
+
+	if (status) {
+		DBG(midi2, "%s complete error %d: %d/%d\n",
+		    usb_ep->name, status, req->actual, req->length);
+		goto error;
+	}
+
+	len = req->actual >> 2;
+	for (; len; len--, buf += 4) {
+		group = *buf >> 4;
+		if (group >= ep->blks[0].info.num_groups)
+			continue;
+		group += ep->blks[0].info.first_group;
+		bytes = midi1_packet_bytes[*buf & 0x0f];
+		for (c = 0; c < bytes; c++) {
+			snd_ump_convert_to_ump(cvt, group, ep->info.protocol,
+					       buf[c + 1]);
+			if (cvt->ump_bytes) {
+				snd_ump_receive(ep->ump, cvt->ump,
+						cvt->ump_bytes);
+				cvt->ump_bytes = 0;
+			}
+		}
+	}
+
+	if (midi2->operation_mode != MIDI_OP_MODE_MIDI1)
+		goto error;
+
+	if (queue_request_ep_raw(req))
+		goto error;
+	return;
+
+ error:
+	put_empty_request(req);
+}
+
+/*
+ * Common EP handling helpers
+ */
+
 /* Start MIDI EP */
 static int f_midi2_start_ep(struct f_midi2_usb_ep *usb_ep,
 			    struct usb_function *fn)
 {
 	int err;
 
+	if (!usb_ep->usb_ep)
+		return 0;
+
 	usb_ep_disable(usb_ep->usb_ep);
 	err = config_ep_by_speed(usb_ep->card->gadget, fn, usb_ep->usb_ep);
 	if (err)
@@ -725,7 +1093,7 @@ static void f_midi2_drop_reqs(struct f_midi2_usb_ep *usb_ep)
 {
 	int i;
 
-	if (!usb_ep->num_reqs)
+	if (!usb_ep->usb_ep || !usb_ep->num_reqs)
 		return;
 
 	for (i = 0; i < usb_ep->num_reqs; i++) {
@@ -742,6 +1110,8 @@ static int f_midi2_alloc_ep_reqs(struct f_midi2_usb_ep *usb_ep)
 	struct f_midi2 *midi2 = usb_ep->card;
 	int i;
 
+	if (!usb_ep->usb_ep)
+		return 0;
 	if (!usb_ep->reqs)
 		return -EINVAL;
 
@@ -774,7 +1144,7 @@ static void f_midi2_free_ep_reqs(struct f_midi2_usb_ep *usb_ep)
 /* Initialize EP */
 static int f_midi2_init_ep(struct f_midi2 *midi2, struct f_midi2_ep *ep,
 			   struct f_midi2_usb_ep *usb_ep,
-			   void *desc, int num_reqs,
+			   void *desc,
 			   void (*complete)(struct usb_ep *usb_ep,
 					    struct usb_request *req))
 {
@@ -787,17 +1157,15 @@ static int f_midi2_init_ep(struct f_midi2 *midi2, struct f_midi2_ep *ep,
 		return -ENODEV;
 	usb_ep->complete = complete;
 
-	if (num_reqs) {
-		usb_ep->reqs = kcalloc(num_reqs, sizeof(*usb_ep->reqs),
-				       GFP_KERNEL);
-		if (!usb_ep->reqs)
-			return -ENOMEM;
-		for (i = 0; i < num_reqs; i++) {
-			usb_ep->reqs[i].index = i;
-			usb_ep->reqs[i].usb_ep = usb_ep;
-			set_bit(i, &usb_ep->free_reqs);
-			usb_ep->num_reqs++;
-		}
+	usb_ep->reqs = kcalloc(midi2->info.num_reqs, sizeof(*usb_ep->reqs),
+			       GFP_KERNEL);
+	if (!usb_ep->reqs)
+		return -ENOMEM;
+	for (i = 0; i < midi2->info.num_reqs; i++) {
+		usb_ep->reqs[i].index = i;
+		usb_ep->reqs[i].usb_ep = usb_ep;
+		set_bit(i, &usb_ep->free_reqs);
+		usb_ep->num_reqs++;
 	}
 
 	return 0;
@@ -821,6 +1189,9 @@ static void f_midi2_queue_out_reqs(struct f_midi2_usb_ep *usb_ep)
 {
 	int i, err;
 
+	if (!usb_ep->usb_ep)
+		return;
+
 	for (i = 0; i < usb_ep->num_reqs; i++) {
 		if (!test_bit(i, &usb_ep->free_reqs) || !usb_ep->reqs[i].req)
 			continue;
@@ -836,6 +1207,41 @@ static void f_midi2_queue_out_reqs(struct f_midi2_usb_ep *usb_ep)
  * Gadget Function callbacks
  */
 
+/* stop both IN and OUT EPs */
+static void f_midi2_stop_eps(struct f_midi2_usb_ep *ep_in,
+			     struct f_midi2_usb_ep *ep_out)
+{
+	f_midi2_drop_reqs(ep_in);
+	f_midi2_drop_reqs(ep_out);
+	f_midi2_free_ep_reqs(ep_in);
+	f_midi2_free_ep_reqs(ep_out);
+}
+
+/* start/queue both IN and OUT EPs */
+static int f_midi2_start_eps(struct f_midi2_usb_ep *ep_in,
+			     struct f_midi2_usb_ep *ep_out,
+			     struct usb_function *fn)
+{
+	int err;
+
+	err = f_midi2_start_ep(ep_in, fn);
+	if (err)
+		return err;
+	err = f_midi2_start_ep(ep_out, fn);
+	if (err)
+		return err;
+
+	err = f_midi2_alloc_ep_reqs(ep_in);
+	if (err)
+		return err;
+	err = f_midi2_alloc_ep_reqs(ep_out);
+	if (err)
+		return err;
+
+	f_midi2_queue_out_reqs(ep_out);
+	return 0;
+}
+
 /* gadget function set_alt callback */
 static int f_midi2_set_alt(struct usb_function *fn, unsigned int intf,
 			   unsigned int alt)
@@ -859,35 +1265,28 @@ static int f_midi2_set_alt(struct usb_function *fn, unsigned int intf,
 
 	midi2->operation_mode = op_mode;
 
+	if (op_mode != MIDI_OP_MODE_MIDI1)
+		f_midi2_stop_eps(&midi2->midi1_ep_in, &midi2->midi1_ep_out);
+
 	if (op_mode != MIDI_OP_MODE_MIDI2) {
 		for (i = 0; i < midi2->num_eps; i++) {
 			ep = &midi2->midi2_eps[i];
-			f_midi2_drop_reqs(&ep->ep_in);
-			f_midi2_drop_reqs(&ep->ep_out);
-			f_midi2_free_ep_reqs(&ep->ep_in);
-			f_midi2_free_ep_reqs(&ep->ep_out);
+			f_midi2_stop_eps(&ep->ep_in, &ep->ep_out);
 		}
-		return 0;
 	}
 
-	for (i = 0; i < midi2->num_eps; i++) {
-		ep = &midi2->midi2_eps[i];
+	if (op_mode == MIDI_OP_MODE_MIDI1)
+		return f_midi2_start_eps(&midi2->midi1_ep_in,
+					 &midi2->midi1_ep_out, fn);
 
-		err = f_midi2_start_ep(&ep->ep_in, fn);
-		if (err)
-			return err;
-		err = f_midi2_start_ep(&ep->ep_out, fn);
-		if (err)
-			return err;
+	if (op_mode == MIDI_OP_MODE_MIDI2) {
+		for (i = 0; i < midi2->num_eps; i++) {
+			ep = &midi2->midi2_eps[i];
 
-		err = f_midi2_alloc_ep_reqs(&ep->ep_in);
-		if (err)
-			return err;
-		err = f_midi2_alloc_ep_reqs(&ep->ep_out);
-		if (err)
-			return err;
-
-		f_midi2_queue_out_reqs(&ep->ep_out);
+			err = f_midi2_start_eps(&ep->ep_in, &ep->ep_out, fn);
+			if (err)
+				return err;
+		}
 	}
 
 	return 0;
@@ -1026,9 +1425,18 @@ static void f_midi2_ump_close(struct snd_ump_endpoint *ump, int dir)
 static void f_midi2_ump_trigger(struct snd_ump_endpoint *ump, int dir, int up)
 {
 	struct f_midi2_ep *ep = ump->private_data;
+	struct f_midi2 *midi2 = ep->card;
 
-	if (up && dir == SNDRV_RAWMIDI_STREAM_OUTPUT)
-		process_ump_transmit(ep);
+	if (up && dir == SNDRV_RAWMIDI_STREAM_OUTPUT) {
+		switch (midi2->operation_mode) {
+		case MIDI_OP_MODE_MIDI1:
+			process_midi1_transmit(midi2);
+			break;
+		case MIDI_OP_MODE_MIDI2:
+			process_ump_transmit(ep);
+			break;
+		}
+	}
 }
 
 static void f_midi2_ump_drain(struct snd_ump_endpoint *ump, int dir)
@@ -1160,8 +1568,8 @@ struct f_midi2_usb_config {
 
 	/* MIDI 1.0 jacks */
 	unsigned char jack_in, jack_out, jack_id;
-	struct usb_midi_in_jack_descriptor jack_ins[16];
-	struct usb_midi_out_jack_descriptor_1 jack_outs[16];
+	struct usb_midi_in_jack_descriptor jack_ins[MAX_CABLES];
+	struct usb_midi_out_jack_descriptor_1 jack_outs[MAX_CABLES];
 };
 
 static int append_config(struct f_midi2_usb_config *config, void *d)
@@ -1422,7 +1830,7 @@ static int f_midi2_init_midi2_ep_in(struct f_midi2 *midi2, int index)
 	fill_midi2_class_desc(ep, &midi2_midi2_ep_in_class_desc[index]);
 
 	return f_midi2_init_ep(midi2, ep, &ep->ep_in, desc,
-			       midi2->info.num_reqs, f_midi2_ep_in_complete);
+			       f_midi2_ep_in_complete);
 }
 
 /* initialize MIDI2 EP-out */
@@ -1439,7 +1847,7 @@ static int f_midi2_init_midi2_ep_out(struct f_midi2 *midi2, int index)
 	fill_midi2_class_desc(ep, &midi2_midi2_ep_out_class_desc[index]);
 
 	return f_midi2_init_ep(midi2, ep, &ep->ep_out, desc,
-			       midi2->info.num_reqs, f_midi2_ep_out_complete);
+			       f_midi2_ep_out_complete);
 }
 
 /* gadget function bind callback */
@@ -1504,14 +1912,16 @@ static int f_midi2_bind(struct usb_configuration *c, struct usb_function *f)
 	/* allocate instance-specific endpoints */
 	if (midi2->midi2_eps[0].blks[0].info.direction != SNDRV_UMP_DIR_OUTPUT) {
 		status = f_midi2_init_ep(midi2, NULL, &midi2->midi1_ep_in,
-					 &midi2_midi1_ep_in_desc, 0, NULL);
+					 &midi2_midi1_ep_in_desc,
+					 f_midi2_midi1_ep_in_complete);
 		if (status)
 			goto fail;
 	}
 
 	if (midi2->midi2_eps[0].blks[0].info.direction != SNDRV_UMP_DIR_INPUT) {
 		status = f_midi2_init_ep(midi2, NULL, &midi2->midi1_ep_out,
-					 &midi2_midi1_ep_out_desc, 0, NULL);
+					 &midi2_midi1_ep_out_desc,
+					 f_midi2_midi1_ep_out_complete);
 		if (status)
 			goto fail;
 	}
-- 
2.35.3


^ permalink raw reply related	[flat|nested] 9+ messages in thread

* [PATCH 5/7] usb: gadget: midi2: Add testing documentation
  2023-07-25  6:21 [PATCH 0/7] usb: Add USB MIDI 2.0 Gadget Function Driver Takashi Iwai
                   ` (3 preceding siblings ...)
  2023-07-25  6:22 ` [PATCH 4/7] usb: gadget: midi2: MIDI 1.0 interface (altset 0) support Takashi Iwai
@ 2023-07-25  6:22 ` Takashi Iwai
  2023-07-25  6:22 ` [PATCH 6/7] usb: gadget: midi2: Add "Operation Mode" control Takashi Iwai
                   ` (2 subsequent siblings)
  7 siblings, 0 replies; 9+ messages in thread
From: Takashi Iwai @ 2023-07-25  6:22 UTC (permalink / raw)
  To: Greg Kroah-Hartman; +Cc: alsa-devel, linux-usb, linux-kernel

Describing brief testing of the new MIDI 2.0 function driver.

Signed-off-by: Takashi Iwai <tiwai@suse.de>
---
 Documentation/usb/gadget-testing.rst | 141 +++++++++++++++++++++++++++
 1 file changed, 141 insertions(+)

diff --git a/Documentation/usb/gadget-testing.rst b/Documentation/usb/gadget-testing.rst
index 2fca40443dc9..0f3708ae5bc8 100644
--- a/Documentation/usb/gadget-testing.rst
+++ b/Documentation/usb/gadget-testing.rst
@@ -27,6 +27,7 @@ provided by gadgets.
    18. UVC function
    19. PRINTER function
    20. UAC1 function (new API)
+   21. MIDI2 function
 
 
 1. ACM function
@@ -965,3 +966,143 @@ e.g.::
 
 	$ arecord -f dat -t wav -D hw:CARD=UAC1Gadget,DEV=0 | \
 	  aplay -D default:CARD=OdroidU3
+
+
+21. MIDI2 function
+==================
+
+The function is provided by usb_f_midi2.ko module.
+It will create a virtual ALSA card containing a UMP rawmidi device
+where the UMP packet is looped back. In addition, a legacy rawmidi
+device is created. The UMP rawmidi is bound with ALSA sequencer
+clients, too.
+
+Function-specific configfs interface
+------------------------------------
+
+The function name to use when creating the function directory is "midi2".
+The midi2 function provides these attributes in its function directory
+as the card top-level information:
+
+	=============	=================================================
+	process_ump	Bool flag to process UMP Stream messages (0 or 1)
+	static_block	Bool flag for static blocks (0 or 1)
+	iface_name	Optional interface name string
+	=============	=================================================
+
+The directory contains a subdirectory "ep.0", and this provides the
+attributes for a UMP Endpoint (which is a pair of USB MIDI Endpoints):
+
+	=============	=================================================
+	protocol_caps	MIDI protocol capabilities;
+			1: MIDI 1.0, 2: MIDI 2.0, or 3: both protocols
+	protocol	Default MIDI protocol (either 1 or 2)
+	ep_name		UMP Endpoint name string
+	product_id	Product ID string
+	manufacturer	Manufacture ID number (24 bit)
+	family		Device family ID number (16 bit)
+	model		Device model ID number (16 bit)
+	sw_revision	Software revision (32 bit)
+	=============	=================================================
+
+Each Endpoint subdirectory contains a subdirectory "block.0", which
+represents the Function Block for Block 0 information.
+Its attributes are:
+
+	===============	===============================================
+	name		Function Block name string
+	direction	Direction of this FB
+			1: input, 2: output, or 3: bidirectional
+	first_group	The first UMP Group number (0-15)
+	num_groups	The number of groups in this FB (1-16)
+	ui_hint		UI-hint of this FB
+			0: unknown, 1: receiver, 2: sender, 3: both
+	midi_ci_verison	Supported MIDI-CI version number (8 bit)
+	is_midi1	Legacy MIDI 1.0 device (0-2)
+			0: MIDI 2.0 device,
+			1: MIDI 1.0 without restriction, or
+			2: MIDI 1.0 with low speed
+	sysex8_streams	Max number of SysEx8 streams (8 bit)
+	active		Bool flag for FB activity (0 or 1)
+	===============	===============================================
+
+If multiple Function Blocks are required, you can add more Function
+Blocks by creating subdirectories "block.<num>" with the corresponding
+Function Block number (1, 2, ....). The FB subdirectories can be
+dynamically removed, too. Note that the Function Block numbers must be
+continuous.
+
+Similarly, if you multiple UMP Endpoints are required, you can add
+more Endpoints by creating subdirectories "ep.<num>". The number must
+be continuous.
+
+For emulating the old MIDI 2.0 device without UMP v1.1 support, pass 0
+to `process_ump` flag. Then the whole UMP v1.1 requests are ignored.
+
+Testing the MIDI2 function
+--------------------------
+
+On the device: run the gadget, and running::
+
+  $ cat /proc/asound/cards
+
+will show a new sound card containing a MIDI2 device.
+
+OTOH, on the host::
+
+  $ cat /proc/asound/cards
+
+will show a new sound card containing either MIDI1 or MIDI2 device,
+depending on the USB audio driver configuration.
+
+On both, when ALSA sequencer is enabled on the host, you can find the
+UMP MIDI client such as "MIDI 2.0 Gadget".
+
+As the driver simply loops back the data, there is no need for a real
+device just for testing.
+
+For testing a MIDI input from the gadget to the host (e.g. emulating a
+MIDI keyboard), you can send a MIDI stream like the following.
+
+On the gadget::
+
+  $ aconnect -o
+  ....
+  client 20: 'MIDI 2.0 Gadget' [type=kernel,card=1]
+      0 'MIDI 2.0        '
+      1 'Group 1 (MIDI 2.0 Gadget I/O)'
+  $ aplaymidi -p 20:1 to_host.mid
+
+On the host::
+
+  $ aconnect -i
+  ....
+  client 24: 'MIDI 2.0 Gadget' [type=kernel,card=2]
+      0 'MIDI 2.0        '
+      1 'Group 1 (MIDI 2.0 Gadget I/O)'
+  $ arecordmidi -p 24:1 from_gadget.mid
+
+If you have a UMP-capable application, you can use the UMP port to
+send/receive the raw UMP packets, too. For example, aseqdump program
+with UMP support can receive from UMP port. On the host::
+
+  $ aseqdump -u 2 -p 24:1
+  Waiting for data. Press Ctrl+C to end.
+  Source  Group    Event                  Ch  Data
+   24:1   Group  0, Program change          0, program 0, Bank select 0:0
+   24:1   Group  0, Channel pressure        0, value 0x80000000
+
+For testing a MIDI output to the gadget to the host (e.g. emulating a
+MIDI synth), it'll be just other way round.
+
+On the gadget::
+
+  $ arecordmidi -p 20:1 from_host.mid
+
+On the host::
+
+  $ aplaymidi -p 24:1 to_gadget.mid
+
+The access to MIDI 1.0 on altset 0 on the host is supported, and it's
+translated from/to UMP packets on the gadget. It's bound to only
+Function Block 0.
-- 
2.35.3


^ permalink raw reply related	[flat|nested] 9+ messages in thread

* [PATCH 6/7] usb: gadget: midi2: Add "Operation Mode" control
  2023-07-25  6:21 [PATCH 0/7] usb: Add USB MIDI 2.0 Gadget Function Driver Takashi Iwai
                   ` (4 preceding siblings ...)
  2023-07-25  6:22 ` [PATCH 5/7] usb: gadget: midi2: Add testing documentation Takashi Iwai
@ 2023-07-25  6:22 ` Takashi Iwai
  2023-07-25  6:22 ` [PATCH 7/7] usb: gadget: midi2: More flexible MIDI 1.0 configuration Takashi Iwai
  2023-07-25 16:29 ` [PATCH 0/7] usb: Add USB MIDI 2.0 Gadget Function Driver Greg Kroah-Hartman
  7 siblings, 0 replies; 9+ messages in thread
From: Takashi Iwai @ 2023-07-25  6:22 UTC (permalink / raw)
  To: Greg Kroah-Hartman; +Cc: alsa-devel, linux-usb, linux-kernel

Add a new ALSA control element to watch the current operation mode
(MIDI 1.0 or MIDI 2.0).  It's a read-only control that reflects the
current value of altsetting, and 0 means unused, 1 for MIDI 1.0
(altset 0) and 2 for MIDI 2.0 (altset 1).

Signed-off-by: Takashi Iwai <tiwai@suse.de>
---
 Documentation/usb/gadget-testing.rst  | 11 +++++++++
 drivers/usb/gadget/function/f_midi2.c | 35 +++++++++++++++++++++++++++
 2 files changed, 46 insertions(+)

diff --git a/Documentation/usb/gadget-testing.rst b/Documentation/usb/gadget-testing.rst
index 0f3708ae5bc8..1fb181d61322 100644
--- a/Documentation/usb/gadget-testing.rst
+++ b/Documentation/usb/gadget-testing.rst
@@ -1106,3 +1106,14 @@ On the host::
 The access to MIDI 1.0 on altset 0 on the host is supported, and it's
 translated from/to UMP packets on the gadget. It's bound to only
 Function Block 0.
+
+The current operation mode can be observed in ALSA control element
+"Operation Mode" for SND_CTL_IFACE_RAWMIDI.  For example::
+
+  $ amixer -c1 contents
+  numid=1,iface=RAWMIDI,name='Operation Mode'
+    ; type=INTEGER,access=r--v----,values=1,min=0,max=2,step=0
+    : values=2
+
+where 0 = unused, 1 = MIDI 1.0 (altset 0), 2 = MIDI 2.0 (altset 1).
+The example above shows it's running in 2, i.e. MIDI 2.0.
diff --git a/drivers/usb/gadget/function/f_midi2.c b/drivers/usb/gadget/function/f_midi2.c
index a368ac51d349..ec9ef15abfea 100644
--- a/drivers/usb/gadget/function/f_midi2.c
+++ b/drivers/usb/gadget/function/f_midi2.c
@@ -9,6 +9,7 @@
 #include <linux/slab.h>
 
 #include <sound/core.h>
+#include <sound/control.h>
 #include <sound/ump.h>
 #include <sound/ump_msg.h>
 #include <sound/ump_convert.h>
@@ -1450,6 +1451,36 @@ static const struct snd_ump_ops f_midi2_ump_ops = {
 	.drain = f_midi2_ump_drain,
 };
 
+/*
+ * "Operation Mode" control element
+ */
+static int f_midi2_operation_mode_info(struct snd_kcontrol *kcontrol,
+				       struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = MIDI_OP_MODE_UNSET;
+	uinfo->value.integer.max = MIDI_OP_MODE_MIDI2;
+	return 0;
+}
+
+static int f_midi2_operation_mode_get(struct snd_kcontrol *kcontrol,
+				      struct snd_ctl_elem_value *ucontrol)
+{
+	struct f_midi2 *midi2 = snd_kcontrol_chip(kcontrol);
+
+	ucontrol->value.integer.value[0] = midi2->operation_mode;
+	return 0;
+}
+
+static const struct snd_kcontrol_new operation_mode_ctl = {
+	.iface = SNDRV_CTL_ELEM_IFACE_RAWMIDI,
+	.name = "Operation Mode",
+	.access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+	.info = f_midi2_operation_mode_info,
+	.get = f_midi2_operation_mode_get,
+};
+
 /*
  * ALSA UMP instance creation / deletion
  */
@@ -1547,6 +1578,10 @@ static int f_midi2_create_card(struct f_midi2 *midi2)
 		id++;
 	}
 
+	err = snd_ctl_add(card, snd_ctl_new1(&operation_mode_ctl, midi2));
+	if (err < 0)
+		goto error;
+
 	err = snd_card_register(card);
 	if (err < 0)
 		goto error;
-- 
2.35.3


^ permalink raw reply related	[flat|nested] 9+ messages in thread

* [PATCH 7/7] usb: gadget: midi2: More flexible MIDI 1.0 configuration
  2023-07-25  6:21 [PATCH 0/7] usb: Add USB MIDI 2.0 Gadget Function Driver Takashi Iwai
                   ` (5 preceding siblings ...)
  2023-07-25  6:22 ` [PATCH 6/7] usb: gadget: midi2: Add "Operation Mode" control Takashi Iwai
@ 2023-07-25  6:22 ` Takashi Iwai
  2023-07-25 16:29 ` [PATCH 0/7] usb: Add USB MIDI 2.0 Gadget Function Driver Greg Kroah-Hartman
  7 siblings, 0 replies; 9+ messages in thread
From: Takashi Iwai @ 2023-07-25  6:22 UTC (permalink / raw)
  To: Greg Kroah-Hartman; +Cc: alsa-devel, linux-usb, linux-kernel

This patch allows users to set up MIDI 1.0 ports more flexibly.
Namely, instead of the fixed mapping only from FB 0, now multiple
block definitions are applied to build up the MIDI 1.0 mapping.

The each block config has midi1_first_group and midi1_num_groups
attributes, and those specify which Groups are used for MIDI 1.0.
Those fields must be within the UMP Groups defined in the block
itself.

Signed-off-by: Takashi Iwai <tiwai@suse.de>
---
 .../ABI/testing/configfs-usb-gadget-midi2     |  24 +--
 Documentation/usb/gadget-testing.rst          |  34 ++--
 drivers/usb/gadget/function/f_midi2.c         | 154 ++++++++++++++----
 drivers/usb/gadget/function/u_midi2.h         |   2 +
 4 files changed, 153 insertions(+), 61 deletions(-)

diff --git a/Documentation/ABI/testing/configfs-usb-gadget-midi2 b/Documentation/ABI/testing/configfs-usb-gadget-midi2
index a3a036d784c7..0eac3aaba137 100644
--- a/Documentation/ABI/testing/configfs-usb-gadget-midi2
+++ b/Documentation/ABI/testing/configfs-usb-gadget-midi2
@@ -39,14 +39,16 @@ Description:
 
 		The attributes:
 
-		===============	===============================================
-		name		Function Block name string
-		direction	1: input, 2: output, 3: bidirectional
-		first_group	The first UMP Group number (0-15)
-		num_groups	The number of groups in this FB (1-16)
-		ui_hint		0: unknown, 1: receiver, 2: sender, 3: both
-		midi_ci_verison	Supported MIDI-CI version number (8 bit)
-		is_midi1	Legacy MIDI 1.0 device (0, 1 or 2)
-		sysex8_streams	Max number of SysEx8 streams (8 bit)
-		active		Active FB flag (0 or 1)
-		===============	===============================================
+		=================	==============================================
+		name			Function Block name string
+		direction		1: input, 2: output, 3: bidirectional
+		first_group		The first UMP Group number (0-15)
+		num_groups		The number of groups in this FB (1-16)
+		midi1_first_group	The first UMP Group number for MIDI 1.0 (0-15)
+		midi1_num_groups	The number of groups for MIDI 1.0 (0-16)
+		ui_hint			0: unknown, 1: receiver, 2: sender, 3: both
+		midi_ci_verison		Supported MIDI-CI version number (8 bit)
+		is_midi1		Legacy MIDI 1.0 device (0, 1 or 2)
+		sysex8_streams		Max number of SysEx8 streams (8 bit)
+		active			Active FB flag (0 or 1)
+		=================	==============================================
diff --git a/Documentation/usb/gadget-testing.rst b/Documentation/usb/gadget-testing.rst
index 1fb181d61322..394cd226bfae 100644
--- a/Documentation/usb/gadget-testing.rst
+++ b/Documentation/usb/gadget-testing.rst
@@ -1009,22 +1009,24 @@ Each Endpoint subdirectory contains a subdirectory "block.0", which
 represents the Function Block for Block 0 information.
 Its attributes are:
 
-	===============	===============================================
-	name		Function Block name string
-	direction	Direction of this FB
-			1: input, 2: output, or 3: bidirectional
-	first_group	The first UMP Group number (0-15)
-	num_groups	The number of groups in this FB (1-16)
-	ui_hint		UI-hint of this FB
-			0: unknown, 1: receiver, 2: sender, 3: both
-	midi_ci_verison	Supported MIDI-CI version number (8 bit)
-	is_midi1	Legacy MIDI 1.0 device (0-2)
-			0: MIDI 2.0 device,
-			1: MIDI 1.0 without restriction, or
-			2: MIDI 1.0 with low speed
-	sysex8_streams	Max number of SysEx8 streams (8 bit)
-	active		Bool flag for FB activity (0 or 1)
-	===============	===============================================
+	=================	===============================================
+	name			Function Block name string
+	direction		Direction of this FB
+				1: input, 2: output, or 3: bidirectional
+	first_group		The first UMP Group number (0-15)
+	num_groups		The number of groups in this FB (1-16)
+	midi1_first_group	The first UMP Group number for MIDI 1.0 (0-15)
+	midi1_num_groups	The number of groups for MIDI 1.0 (0-16)
+	ui_hint			UI-hint of this FB
+				0: unknown, 1: receiver, 2: sender, 3: both
+	midi_ci_verison		Supported MIDI-CI version number (8 bit)
+	is_midi1		Legacy MIDI 1.0 device (0-2)
+				0: MIDI 2.0 device,
+				1: MIDI 1.0 without restriction, or
+				2: MIDI 1.0 with low speed
+	sysex8_streams		Max number of SysEx8 streams (8 bit)
+	active			Bool flag for FB activity (0 or 1)
+	=================	===============================================
 
 If multiple Function Blocks are required, you can add more Function
 Blocks by creating subdirectories "block.<num>" with the corresponding
diff --git a/drivers/usb/gadget/function/f_midi2.c b/drivers/usb/gadget/function/f_midi2.c
index ec9ef15abfea..f1c47753e0c1 100644
--- a/drivers/usb/gadget/function/f_midi2.c
+++ b/drivers/usb/gadget/function/f_midi2.c
@@ -84,6 +84,8 @@ struct f_midi2_ep {
 
 	struct f_midi2_usb_ep ep_in;	/* USB MIDI EP-in */
 	struct f_midi2_usb_ep ep_out;	/* USB MIDI EP-out */
+
+	u8 in_group_to_cable[SNDRV_UMP_MAX_GROUPS]; /* map to cable; 1-based! */
 };
 
 /* indices for USB strings */
@@ -95,6 +97,13 @@ enum {
 /* 1-based GTB id to string id */
 #define gtb_to_str_id(id)	(STR_GTB1 + (id) - 1)
 
+/* mapping from MIDI 1.0 cable to UMP group */
+struct midi1_cable_mapping {
+	struct f_midi2_ep *ep;
+	unsigned char block;
+	unsigned char group;
+};
+
 /* operation mode */
 enum {
 	MIDI_OP_MODE_UNSET,	/* no altset set yet */
@@ -112,10 +121,17 @@ struct f_midi2 {
 	struct f_midi2_usb_ep midi1_ep_in;
 	struct f_midi2_usb_ep midi1_ep_out;
 
+	/* number of MIDI 1.0 I/O cables */
+	unsigned int num_midi1_in;
+	unsigned int num_midi1_out;
+
 	/* conversion for MIDI 1.0 EP-in */
 	struct f_midi2_midi1_port midi1_port[MAX_CABLES];
 	/* conversion for MIDI 1.0 EP-out */
 	struct ump_cvt_to_ump midi1_ump_cvt;
+	/* mapping between cables and UMP groups */
+	struct midi1_cable_mapping in_cable_mapping[MAX_CABLES];
+	struct midi1_cable_mapping out_cable_mapping[MAX_CABLES];
 
 	int midi_if;			/* USB MIDI interface number */
 	int operation_mode;		/* current operation mode */
@@ -917,8 +933,7 @@ static bool process_midi1_pending_buf(struct f_midi2 *midi2,
 {
 	unsigned int cable, c;
 
-	for (cable = 0; cable < midi2->midi2_eps[0].blks[0].info.num_groups;
-	     cable++) {
+	for (cable = 0; cable < midi2->num_midi1_in; cable++) {
 		struct f_midi2_midi1_port *port = &midi2->midi1_port[cable];
 
 		if (!port->pending)
@@ -960,8 +975,8 @@ static void process_midi1_transmit(struct f_midi2 *midi2)
 	struct usb_request *req = NULL;
 	/* 12 is the largest outcome (4 MIDI1 cmds) for a single UMP packet */
 	unsigned char outbuf[12];
-	unsigned char group;
-	int len, size, cable;
+	unsigned char group, cable;
+	int len, size;
 	u32 ump;
 
 	if (!usb_ep->usb_ep || !usb_ep->usb_ep->enabled)
@@ -986,9 +1001,10 @@ static void process_midi1_transmit(struct f_midi2 *midi2)
 						&group);
 		if (size <= 0)
 			continue;
-		cable = group - ep->blks[0].info.first_group;
-		if (cable < 0 || cable >= ep->blks[0].info.num_groups)
+		cable = ep->in_group_to_cable[group];
+		if (!cable)
 			continue;
+		cable--; /* to 0-base */
 		fill_midi1_pending_buf(midi2, cable, outbuf, size);
 	}
 
@@ -1025,12 +1041,12 @@ static void f_midi2_midi1_ep_out_complete(struct usb_ep *usb_ep,
 {
 	struct f_midi2_req_ctx *ctx = req->context;
 	struct f_midi2 *midi2 = ctx->usb_ep->card;
-	struct f_midi2_ep *ep = &midi2->midi2_eps[0];
+	struct f_midi2_ep *ep;
 	struct ump_cvt_to_ump *cvt = &midi2->midi1_ump_cvt;
 	static const u8 midi1_packet_bytes[16] = {
 		0, 0, 2, 3, 3, 1, 2, 3, 3, 3, 3, 3, 2, 2, 3, 1
 	};
-	unsigned int group, bytes, c, len;
+	unsigned int group, cable, bytes, c, len;
 	int status = req->status;
 	const u8 *buf = req->buf;
 
@@ -1042,10 +1058,11 @@ static void f_midi2_midi1_ep_out_complete(struct usb_ep *usb_ep,
 
 	len = req->actual >> 2;
 	for (; len; len--, buf += 4) {
-		group = *buf >> 4;
-		if (group >= ep->blks[0].info.num_groups)
+		cable = *buf >> 4;
+		ep = midi2->out_cable_mapping[cable].ep;
+		if (!ep)
 			continue;
-		group += ep->blks[0].info.first_group;
+		group = midi2->out_cable_mapping[cable].group;
 		bytes = midi1_packet_bytes[*buf & 0x0f];
 		for (c = 0; c < bytes; c++) {
 			snd_ump_convert_to_ump(cvt, group, ep->info.protocol,
@@ -1641,6 +1658,7 @@ static int append_configs(struct f_midi2_usb_config *config, void **d)
 
 static int append_midi1_in_jack(struct f_midi2 *midi2,
 				struct f_midi2_usb_config *config,
+				struct midi1_cable_mapping *map,
 				unsigned int type)
 {
 	struct usb_midi_in_jack_descriptor *jack =
@@ -1653,7 +1671,9 @@ static int append_midi1_in_jack(struct f_midi2 *midi2,
 	jack->bDescriptorSubtype = USB_MS_MIDI_IN_JACK;
 	jack->bJackType = type;
 	jack->bJackID = id;
-	jack->iJack = midi2->strings[STR_GTB1].id; // TODO: better names?
+	/* use the corresponding block name as jack name */
+	if (map->ep)
+		jack->iJack = map->ep->blks[map->block].string_id;
 
 	err = append_config(config, jack);
 	if (err < 0)
@@ -1663,6 +1683,7 @@ static int append_midi1_in_jack(struct f_midi2 *midi2,
 
 static int append_midi1_out_jack(struct f_midi2 *midi2,
 				 struct f_midi2_usb_config *config,
+				 struct midi1_cable_mapping *map,
 				 unsigned int type, unsigned int source)
 {
 	struct usb_midi_out_jack_descriptor_1 *jack =
@@ -1678,7 +1699,9 @@ static int append_midi1_out_jack(struct f_midi2 *midi2,
 	jack->bNrInputPins = 1;
 	jack->pins[0].baSourceID = source;
 	jack->pins[0].baSourcePin = 0x01;
-	jack->iJack = midi2->strings[STR_GTB1].id; // TODO: better names?
+	/* use the corresponding block name as jack name */
+	if (map->ep)
+		jack->iJack = map->ep->blks[map->block].string_id;
 
 	err = append_config(config, jack);
 	if (err < 0)
@@ -1690,7 +1713,6 @@ static int f_midi2_create_usb_configs(struct f_midi2 *midi2,
 				      struct f_midi2_usb_config *config,
 				      int speed)
 {
-	struct f_midi2_block *blk = &midi2->midi2_eps[0].blks[0];
 	void **midi1_in_eps, **midi1_out_eps;
 	int i, jack, total;
 	int err;
@@ -1724,56 +1746,55 @@ static int f_midi2_create_usb_configs(struct f_midi2 *midi2,
 	if (err < 0)
 		return err;
 
-	switch (blk->info.direction) {
-	case SNDRV_UMP_DIR_INPUT:
-	case SNDRV_UMP_DIR_OUTPUT:
-		midi2_midi1_if_desc.bNumEndpoints = 1;
-		break;
-	default:
+	if (midi2->num_midi1_in && midi2->num_midi1_out)
 		midi2_midi1_if_desc.bNumEndpoints = 2;
-		break;
-	}
+	else
+		midi2_midi1_if_desc.bNumEndpoints = 1;
 
 	err = append_configs(config, midi2_midi1_descs);
 	if (err < 0)
 		return err;
 
 	total = USB_DT_MS_HEADER_SIZE;
-	if (blk->info.direction != SNDRV_UMP_DIR_INPUT) {
+	if (midi2->num_midi1_out) {
 		midi2_midi1_ep_out_class_desc.bLength =
-			USB_DT_MS_ENDPOINT_SIZE(blk->info.num_groups);
+			USB_DT_MS_ENDPOINT_SIZE(midi2->num_midi1_out);
 		total += midi2_midi1_ep_out_class_desc.bLength;
 		midi2_midi1_ep_out_class_desc.bNumEmbMIDIJack =
-			blk->info.num_groups;
-		total += blk->info.num_groups *
+			midi2->num_midi1_out;
+		total += midi2->num_midi1_out *
 			(USB_DT_MIDI_IN_SIZE + USB_DT_MIDI_OUT_SIZE(1));
-		for (i = 0; i < blk->info.num_groups; i++) {
+		for (i = 0; i < midi2->num_midi1_out; i++) {
 			jack = append_midi1_in_jack(midi2, config,
+						    &midi2->in_cable_mapping[i],
 						    USB_MS_EMBEDDED);
 			if (jack < 0)
 				return jack;
 			midi2_midi1_ep_out_class_desc.baAssocJackID[i] = jack;
 			jack = append_midi1_out_jack(midi2, config,
+						     &midi2->in_cable_mapping[i],
 						     USB_MS_EXTERNAL, jack);
 			if (jack < 0)
 				return jack;
 		}
 	}
 
-	if (blk->info.direction != SNDRV_UMP_DIR_OUTPUT) {
+	if (midi2->num_midi1_in) {
 		midi2_midi1_ep_in_class_desc.bLength =
-			USB_DT_MS_ENDPOINT_SIZE(blk->info.num_groups);
+			USB_DT_MS_ENDPOINT_SIZE(midi2->num_midi1_in);
 		total += midi2_midi1_ep_in_class_desc.bLength;
 		midi2_midi1_ep_in_class_desc.bNumEmbMIDIJack =
-			blk->info.num_groups;
-		total += blk->info.num_groups *
+			midi2->num_midi1_in;
+		total += midi2->num_midi1_in *
 			(USB_DT_MIDI_IN_SIZE + USB_DT_MIDI_OUT_SIZE(1));
-		for (i = 0; i < blk->info.num_groups; i++) {
+		for (i = 0; i < midi2->num_midi1_in; i++) {
 			jack = append_midi1_in_jack(midi2, config,
+						    &midi2->out_cable_mapping[i],
 						    USB_MS_EXTERNAL);
 			if (jack < 0)
 				return jack;
 			jack = append_midi1_out_jack(midi2, config,
+						     &midi2->out_cable_mapping[i],
 						     USB_MS_EMBEDDED, jack);
 			if (jack < 0)
 				return jack;
@@ -1783,12 +1804,12 @@ static int f_midi2_create_usb_configs(struct f_midi2 *midi2,
 
 	midi2_midi1_class_desc.wTotalLength = cpu_to_le16(total);
 
-	if (blk->info.direction != SNDRV_UMP_DIR_INPUT) {
+	if (midi2->num_midi1_out) {
 		err = append_configs(config, midi1_out_eps);
 		if (err < 0)
 			return err;
 	}
-	if (blk->info.direction != SNDRV_UMP_DIR_OUTPUT) {
+	if (midi2->num_midi1_in) {
 		err = append_configs(config, midi1_in_eps);
 		if (err < 0)
 			return err;
@@ -2236,6 +2257,8 @@ CONFIGFS_ATTR(f_midi2_block_opts_, name)
 F_MIDI2_BLOCK_OPT(direction, "0x%x", 1, 3);
 F_MIDI2_BLOCK_OPT(first_group, "0x%x", 0, 15);
 F_MIDI2_BLOCK_OPT(num_groups, "0x%x", 1, 16);
+F_MIDI2_BLOCK_OPT(midi1_first_group, "0x%x", 0, 15);
+F_MIDI2_BLOCK_OPT(midi1_num_groups, "0x%x", 0, 16);
 F_MIDI2_BLOCK_OPT(ui_hint, "0x%x", 0, 3);
 F_MIDI2_BLOCK_OPT(midi_ci_version, "%u", 0, 1);
 F_MIDI2_BLOCK_OPT(sysex8_streams, "%u", 0, 255);
@@ -2265,6 +2288,8 @@ static struct configfs_attribute *f_midi2_block_attrs[] = {
 	&f_midi2_block_opts_attr_direction,
 	&f_midi2_block_opts_attr_first_group,
 	&f_midi2_block_opts_attr_num_groups,
+	&f_midi2_block_opts_attr_midi1_first_group,
+	&f_midi2_block_opts_attr_midi1_num_groups,
 	&f_midi2_block_opts_attr_ui_hint,
 	&f_midi2_block_opts_attr_midi_ci_version,
 	&f_midi2_block_opts_attr_sysex8_streams,
@@ -2644,6 +2669,9 @@ static struct usb_function_instance *f_midi2_alloc_inst(void)
 		return ERR_PTR(ret);
 	}
 
+	/* set up the default MIDI1 (that is mandatory) */
+	block_opts->info.midi1_num_groups = 1;
+
 	config_group_init_type_name(&opts->func_inst.group, "",
 				    &f_midi2_func_type);
 
@@ -2707,6 +2735,16 @@ static int verify_parameters(struct f_midi2_opts *opts)
 				       i, j);
 				return -EINVAL;
 			}
+
+			if (bp->midi1_num_groups) {
+				if (bp->midi1_first_group < bp->first_group ||
+				    bp->midi1_first_group + bp->midi1_num_groups >
+				    bp->first_group + bp->num_groups) {
+					pr_err("f_midi2: Invalid MIDI1 group definitions for block %d:%d\n",
+					       i, j);
+					return -EINVAL;
+				}
+			}
 		}
 	}
 	if (!num_blks) {
@@ -2717,6 +2755,46 @@ static int verify_parameters(struct f_midi2_opts *opts)
 	return num_eps;
 }
 
+/* fill mapping between MIDI 1.0 cable and UMP EP/group */
+static void fill_midi1_cable_mapping(struct f_midi2 *midi2,
+				     struct f_midi2_ep *ep,
+				     int blk)
+{
+	const struct f_midi2_block_info *binfo = &ep->blks[blk].info;
+	struct midi1_cable_mapping *map;
+	int i, group;
+
+	if (!binfo->midi1_num_groups)
+		return;
+	if (binfo->direction != SNDRV_UMP_DIR_OUTPUT) {
+		group = binfo->midi1_first_group;
+		map = midi2->in_cable_mapping + midi2->num_midi1_in;
+		for (i = 0; i < binfo->midi1_num_groups; i++, group++, map++) {
+			if (midi2->num_midi1_in >= MAX_CABLES)
+				break;
+			map->ep = ep;
+			map->block = blk;
+			map->group = group;
+			midi2->num_midi1_in++;
+			/* store 1-based cable number */
+			ep->in_group_to_cable[group] = midi2->num_midi1_in;
+		}
+	}
+
+	if (binfo->direction != SNDRV_UMP_DIR_INPUT) {
+		group = binfo->midi1_first_group;
+		map = midi2->out_cable_mapping + midi2->num_midi1_out;
+		for (i = 0; i < binfo->midi1_num_groups; i++, group++, map++) {
+			if (midi2->num_midi1_out >= MAX_CABLES)
+				break;
+			map->ep = ep;
+			map->block = blk;
+			map->group = group;
+			midi2->num_midi1_out++;
+		}
+	}
+}
+
 /* gadget alloc callback */
 static struct usb_function *f_midi2_alloc(struct usb_function_instance *fi)
 {
@@ -2786,9 +2864,17 @@ static struct usb_function *f_midi2_alloc(struct usb_function_instance *fi)
 			bp = &ep->blks[blk];
 			midi2->string_defs[gtb_to_str_id(bp->gtb_id)].s =
 				ump_fb_name(&bp->info);
+
+			fill_midi1_cable_mapping(midi2, ep, blk);
 		}
 	}
 
+	if (!midi2->num_midi1_in && !midi2->num_midi1_out) {
+		pr_err("f_midi2: MIDI1 definition is missing\n");
+		do_f_midi2_free(midi2, opts);
+		return ERR_PTR(-EINVAL);
+	}
+
 	return &midi2->func;
 }
 
diff --git a/drivers/usb/gadget/function/u_midi2.h b/drivers/usb/gadget/function/u_midi2.h
index a68dc2ea035e..4e7adb41dfb7 100644
--- a/drivers/usb/gadget/function/u_midi2.h
+++ b/drivers/usb/gadget/function/u_midi2.h
@@ -18,6 +18,8 @@ struct f_midi2_block_info {
 	unsigned int direction;		/* FB direction: 1-3 */
 	unsigned int first_group;	/* first UMP group: 0-15 */
 	unsigned int num_groups;	/* number of UMP groups: 1-16 */
+	unsigned int midi1_first_group;	/* first UMP group for MIDI 1.0 */
+	unsigned int midi1_num_groups;	/* number of UMP groups for MIDI 1.0 */
 	unsigned int ui_hint;		/* UI-hint: 0-3 */
 	unsigned int midi_ci_version;	/* MIDI-CI version: 0-255 */
 	unsigned int sysex8_streams;	/* number of sysex8 streams: 0-255 */
-- 
2.35.3


^ permalink raw reply related	[flat|nested] 9+ messages in thread

* Re: [PATCH 0/7] usb: Add USB MIDI 2.0 Gadget Function Driver
  2023-07-25  6:21 [PATCH 0/7] usb: Add USB MIDI 2.0 Gadget Function Driver Takashi Iwai
                   ` (6 preceding siblings ...)
  2023-07-25  6:22 ` [PATCH 7/7] usb: gadget: midi2: More flexible MIDI 1.0 configuration Takashi Iwai
@ 2023-07-25 16:29 ` Greg Kroah-Hartman
  7 siblings, 0 replies; 9+ messages in thread
From: Greg Kroah-Hartman @ 2023-07-25 16:29 UTC (permalink / raw)
  To: Takashi Iwai; +Cc: alsa-devel, linux-usb, linux-kernel

On Tue, Jul 25, 2023 at 08:21:59AM +0200, Takashi Iwai wrote:
> Hi,
> 
> This is a patch set for adding a USB gadget function driver for USB
> MIDI 2.0 interface.  The MIDI 2.0 support in the host side was already
> enabled since 6.5 kernel in snd-usb-audio driver.  Now, this provides
> a similar gadget functionality using the existing ALSA UMP helper
> functions.
> 
> The gadget driver provides both MIDI 2.0 and MIDI 1.0 fallback
> interfaces with one or more loopback UMP rawmidi devices.  The
> configfs implementation is flexible enough to allow users to set up
> multiple UMP Endpoints and Function Blocks.
> 
> The first patch adds the bare f_midi2 driver without configfs support,
> followed by the configfs support, MIDI 1.0 interface support and a few
> other extensions and documentation.
> 

Very cool, thanks for doing this!  All now queued up, let's see what the
builders say...

greg k-h

^ permalink raw reply	[flat|nested] 9+ messages in thread

end of thread, other threads:[~2023-07-25 16:29 UTC | newest]

Thread overview: 9+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2023-07-25  6:21 [PATCH 0/7] usb: Add USB MIDI 2.0 Gadget Function Driver Takashi Iwai
2023-07-25  6:22 ` [PATCH 1/7] usb: gadget: Add support for USB MIDI 2.0 function driver Takashi Iwai
2023-07-25  6:22 ` [PATCH 2/7] usb: gadget: midi2: Add configfs support Takashi Iwai
2023-07-25  6:22 ` [PATCH 3/7] usb: gadget: midi2: Dynamically create MIDI 1.0 altset descriptors Takashi Iwai
2023-07-25  6:22 ` [PATCH 4/7] usb: gadget: midi2: MIDI 1.0 interface (altset 0) support Takashi Iwai
2023-07-25  6:22 ` [PATCH 5/7] usb: gadget: midi2: Add testing documentation Takashi Iwai
2023-07-25  6:22 ` [PATCH 6/7] usb: gadget: midi2: Add "Operation Mode" control Takashi Iwai
2023-07-25  6:22 ` [PATCH 7/7] usb: gadget: midi2: More flexible MIDI 1.0 configuration Takashi Iwai
2023-07-25 16:29 ` [PATCH 0/7] usb: Add USB MIDI 2.0 Gadget Function Driver Greg Kroah-Hartman

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).