root/drivers/usb/gadget/function/f_midi2.c
// 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/func_utils.h>
#include <linux/usb/gadget.h>
#include <linux/usb/audio.h>
#include <linux/usb/midi-v2.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)

/* convert from MIDI protocol number (1 or 2) to SNDRV_UMP_EP_INFO_PROTO_* */
#define to_ump_protocol(v)      (((v) & 3) << 8)

/* 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_device_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 == 2)
                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 = 2;
                        DBG(midi2, "Switching Protocol to MIDI2\n");
                } else {
                        ep->info.protocol = 1;
                        DBG(midi2, "Switching Protocol to MIDI1\n");
                }
                snd_ump_switch_protocol(ep->ump, to_ump_protocol(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 == 0xff) {
                        /* inquiry for all blocks */
                        for (blk = 0; blk < ep->num_blks; blk++) {
                                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);
                        }
                } else if (blk < ep->num_blks) {
                        /* only the specified block */
                        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,
                                               to_ump_protocol(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 = kzalloc_objs(*usb_ep->reqs, midi2->info.num_reqs);
        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
                op_mode = MIDI_OP_MODE_MIDI2;

        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 == 2)
                                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 = to_ump_protocol(ep->info.protocol);
                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;
                        if (b->is_midi1 < 2)
                                fb->info.flags |= b->is_midi1;
                        else
                                fb->info.flags |= SNDRV_UMP_BLOCK_IS_MIDI1 |
                                        SNDRV_UMP_BLOCK_IS_LOWSPEED;
                        strscpy(fb->info.name, ump_fb_name(b),
                                sizeof(fb->info.name));
                }
                snd_ump_update_group_attrs(ump);
        }

        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);
                        midi2_midi2_ep_in_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);
                        midi2_midi2_ep_in_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 const 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_obj(*block_opts);
        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 const struct configfs_item_operations f_midi2_ep_item_ops = {
        .release        = f_midi2_ep_opts_release,
};

static const 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_obj(*ep_opts);
        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 const struct configfs_item_operations f_midi2_item_ops = {
        .release        = f_midi2_opts_release,
};

static const 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_obj(*opts);
        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_obj(*midi2);
        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 = kzalloc_objs(*midi2->string_defs,
                                          midi2->total_blocks + 1);
        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_DESCRIPTION("USB MIDI 2.0 class function driver");
MODULE_LICENSE("GPL");