Contributors: 2
Author Tokens Token Proportion Commits Commit Proportion
Takashi Iwai 13469 99.99% 6 85.71%
Yang Yingliang 2 0.01% 1 14.29%
Total 13471 7


// 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/control.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>
#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 */
};

/* 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 */
	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 */

	u8 in_group_to_cable[SNDRV_UMP_MAX_GROUPS]; /* map to cable; 1-based! */
};

/* 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)

/* 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 */
	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;

	/* 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 */

	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(16);
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, // to be filled
	.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 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_16 midi2_midi1_ep_out_class_desc = {
	.bLength =		0x05, // to be filled
	.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_16 midi2_midi1_ep_in_class_desc = {
	.bLength =		0x05, // to be filled
	.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, // to be filled
	.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,
	NULL
};

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_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,
	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);
}

/*
 * 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->num_midi1_in; 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, cable;
	int len, size;
	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 = ep->in_group_to_cable[group];
		if (!cable)
			continue;
		cable--; /* to 0-base */
		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;
	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, cable, 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) {
		cable = *buf >> 4;
		ep = midi2->out_cable_mapping[cable].ep;
		if (!ep)
			continue;
		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,
					       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)
		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->usb_ep || !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->usb_ep)
		return 0;
	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,
			   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;

	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;
}

/* 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;

	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;
		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
 */

/* 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)
{
	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_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_stop_eps(&ep->ep_in, &ep->ep_out);
		}
	}

	if (op_mode == MIDI_OP_MODE_MIDI1)
		return f_midi2_start_eps(&midi2->midi1_ep_in,
					 &midi2->midi1_ep_out, fn);

	if (op_mode == MIDI_OP_MODE_MIDI2) {
		for (i = 0; i < midi2->num_eps; i++) {
			ep = &midi2->midi2_eps[i];

			err = f_midi2_start_eps(&ep->ep_in, &ep->ep_out, fn);
			if (err)
				return err;
		}
	}

	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;
	struct f_midi2 *midi2 = ep->card;

	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)
{
}

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,
};

/*
 * "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
 */
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_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;

	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;

	/* MIDI 1.0 jacks */
	unsigned char jack_in, jack_out, jack_id;
	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)
{
	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 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 =
		&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;
	/* 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)
		return err;
	return id;
}

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 =
		&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;
	/* 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)
		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_in_eps, **midi1_out_eps;
	int i, jack, total;
	int 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_in_eps = midi2_midi1_ep_in_descs;
		midi1_out_eps = midi2_midi1_ep_out_descs;
		break;
	case USB_SPEED_SUPER:
		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_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;

	if (midi2->num_midi1_in && midi2->num_midi1_out)
		midi2_midi1_if_desc.bNumEndpoints = 2;
	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 (midi2->num_midi1_out) {
		midi2_midi1_ep_out_class_desc.bLength =
			USB_DT_MS_ENDPOINT_SIZE(midi2->num_midi1_out);
		total += midi2_midi1_ep_out_class_desc.bLength;
		midi2_midi1_ep_out_class_desc.bNumEmbMIDIJack =
			midi2->num_midi1_out;
		total += midi2->num_midi1_out *
			(USB_DT_MIDI_IN_SIZE + USB_DT_MIDI_OUT_SIZE(1));
		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 (midi2->num_midi1_in) {
		midi2_midi1_ep_in_class_desc.bLength =
			USB_DT_MS_ENDPOINT_SIZE(midi2->num_midi1_in);
		total += midi2_midi1_ep_in_class_desc.bLength;
		midi2_midi1_ep_in_class_desc.bNumEmbMIDIJack =
			midi2->num_midi1_in;
		total += midi2->num_midi1_in *
			(USB_DT_MIDI_IN_SIZE + USB_DT_MIDI_OUT_SIZE(1));
		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;
			midi2_midi1_ep_in_class_desc.baAssocJackID[i] = jack;
		}
	}

	midi2_midi1_class_desc.wTotalLength = cpu_to_le16(total);

	if (midi2->num_midi1_out) {
		err = append_configs(config, midi1_out_eps);
		if (err < 0)
			return err;
	}
	if (midi2->num_midi1_in) {
		err = append_configs(config, midi1_in_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,
			       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,
			       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;
	}

	midi2_midi2_if_desc.bNumEndpoints = midi2->num_eps * 2;

	/* 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 */
	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,
					 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,
					 f_midi2_midi1_ep_out_complete);
		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);

	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);

	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;
	}
	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);
}

/*
 * 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(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);
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_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,
	&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) {
		ret = -ENOMEM;
		goto out;
	}

	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;

 out:
	mutex_unlock(&ep_opts->opts->lock);
	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,
				  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;
}

/* 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,
};

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);

	kfree(opts->info.iface_name);
	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;

	/* create the default ep */
	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);
	}

	/* 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);

	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;
}

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));
}

/* 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 (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) {
		pr_err("f_midi2: No block is defined\n");
		return -EINVAL;
	}

	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)
{
	struct f_midi2 *midi2;
	struct f_midi2_opts *opts;
	struct f_midi2_ep *ep;
	struct f_midi2_block *bp;
	int i, num_eps, blk;

	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);
	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);

	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;
	midi2->num_eps = num_eps;

	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);
	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[STR_IFACE].s = opts->info.iface_name;
	else
		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);

			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;
}

DECLARE_USB_FUNCTION_INIT(midi2, f_midi2_alloc_inst, f_midi2_alloc);

MODULE_LICENSE("GPL");