2872 lines
72 KiB
C
2872 lines
72 KiB
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/gadget.h>
|
|
#include <linux/usb/audio.h>
|
|
#include <linux/usb/midi-v2.h>
|
|
|
|
#include "u_f.h"
|
|
#include "u_midi2.h"
|
|
|
|
struct f_midi2;
|
|
struct f_midi2_ep;
|
|
struct f_midi2_usb_ep;
|
|
|
|
/* Context for each USB request */
|
|
struct f_midi2_req_ctx {
|
|
struct f_midi2_usb_ep *usb_ep; /* belonging USB EP */
|
|
unsigned int index; /* array index: 0-31 */
|
|
struct usb_request *req; /* assigned request */
|
|
};
|
|
|
|
/* Resources for a USB Endpoint */
|
|
struct f_midi2_usb_ep {
|
|
struct f_midi2 *card; /* belonging card */
|
|
struct f_midi2_ep *ep; /* belonging UMP EP (optional) */
|
|
struct usb_ep *usb_ep; /* assigned USB EP */
|
|
void (*complete)(struct usb_ep *usb_ep, struct usb_request *req);
|
|
unsigned long free_reqs; /* bitmap for unused requests */
|
|
unsigned int num_reqs; /* number of allocated requests */
|
|
struct f_midi2_req_ctx *reqs; /* request context array */
|
|
};
|
|
|
|
/* Resources for UMP Function Block (and USB Group Terminal Block) */
|
|
struct f_midi2_block {
|
|
struct f_midi2_block_info info; /* FB info, copied from configfs */
|
|
struct snd_ump_block *fb; /* assigned FB */
|
|
unsigned int gtb_id; /* assigned GTB id */
|
|
unsigned int string_id; /* assigned string id */
|
|
};
|
|
|
|
/* Temporary buffer for altset 0 MIDI 1.0 handling */
|
|
struct f_midi2_midi1_port {
|
|
unsigned int pending; /* pending bytes on the input buffer */
|
|
u8 buf[32]; /* raw MIDI 1.0 byte input */
|
|
u8 state; /* running status */
|
|
u8 data[2]; /* rendered USB MIDI 1.0 packet data */
|
|
};
|
|
|
|
/* MIDI 1.0 message states */
|
|
enum {
|
|
STATE_INITIAL = 0, /* pseudo state */
|
|
STATE_1PARAM,
|
|
STATE_2PARAM_1,
|
|
STATE_2PARAM_2,
|
|
STATE_SYSEX_0,
|
|
STATE_SYSEX_1,
|
|
STATE_SYSEX_2,
|
|
STATE_REAL_TIME,
|
|
STATE_FINISHED, /* pseudo state */
|
|
};
|
|
|
|
/* Resources for UMP Endpoint */
|
|
struct f_midi2_ep {
|
|
struct snd_ump_endpoint *ump; /* assigned UMP EP */
|
|
struct f_midi2 *card; /* belonging MIDI 2.0 device */
|
|
|
|
struct f_midi2_ep_info info; /* UMP EP info, copied from configfs */
|
|
unsigned int num_blks; /* number of FBs */
|
|
struct f_midi2_block blks[SNDRV_UMP_MAX_BLOCKS]; /* UMP FBs */
|
|
|
|
struct f_midi2_usb_ep ep_in; /* USB MIDI EP-in */
|
|
struct f_midi2_usb_ep ep_out; /* USB MIDI EP-out */
|
|
|
|
u8 in_group_to_cable[SNDRV_UMP_MAX_GROUPS]; /* map to cable; 1-based! */
|
|
};
|
|
|
|
/* indices for USB strings */
|
|
enum {
|
|
STR_IFACE = 0,
|
|
STR_GTB1 = 1,
|
|
};
|
|
|
|
/* 1-based GTB id to string id */
|
|
#define gtb_to_str_id(id) (STR_GTB1 + (id) - 1)
|
|
|
|
/* mapping from MIDI 1.0 cable to UMP group */
|
|
struct midi1_cable_mapping {
|
|
struct f_midi2_ep *ep;
|
|
unsigned char block;
|
|
unsigned char group;
|
|
};
|
|
|
|
/* operation mode */
|
|
enum {
|
|
MIDI_OP_MODE_UNSET, /* no altset set yet */
|
|
MIDI_OP_MODE_MIDI1, /* MIDI 1.0 (altset 0) is used */
|
|
MIDI_OP_MODE_MIDI2, /* MIDI 2.0 (altset 1) is used */
|
|
};
|
|
|
|
/* Resources for MIDI 2.0 Device */
|
|
struct f_midi2 {
|
|
struct usb_function func;
|
|
struct usb_gadget *gadget;
|
|
struct snd_card *card;
|
|
|
|
/* MIDI 1.0 in/out USB EPs */
|
|
struct f_midi2_usb_ep midi1_ep_in;
|
|
struct f_midi2_usb_ep midi1_ep_out;
|
|
|
|
/* number of MIDI 1.0 I/O cables */
|
|
unsigned int num_midi1_in;
|
|
unsigned int num_midi1_out;
|
|
|
|
/* conversion for MIDI 1.0 EP-in */
|
|
struct f_midi2_midi1_port midi1_port[MAX_CABLES];
|
|
/* conversion for MIDI 1.0 EP-out */
|
|
struct ump_cvt_to_ump midi1_ump_cvt;
|
|
/* mapping between cables and UMP groups */
|
|
struct midi1_cable_mapping in_cable_mapping[MAX_CABLES];
|
|
struct midi1_cable_mapping out_cable_mapping[MAX_CABLES];
|
|
|
|
int midi_if; /* USB MIDI interface number */
|
|
int operation_mode; /* current operation mode */
|
|
|
|
spinlock_t queue_lock;
|
|
|
|
struct f_midi2_card_info info; /* card info, copied from configfs */
|
|
|
|
unsigned int num_eps;
|
|
struct f_midi2_ep midi2_eps[MAX_UMP_EPS];
|
|
|
|
unsigned int total_blocks; /* total number of blocks of all EPs */
|
|
struct usb_string *string_defs;
|
|
struct usb_string *strings;
|
|
};
|
|
|
|
#define func_to_midi2(f) container_of(f, struct f_midi2, func)
|
|
|
|
/* get EP name string */
|
|
static const char *ump_ep_name(const struct f_midi2_ep *ep)
|
|
{
|
|
return ep->info.ep_name ? ep->info.ep_name : "MIDI 2.0 Gadget";
|
|
}
|
|
|
|
/* get EP product ID string */
|
|
static const char *ump_product_id(const struct f_midi2_ep *ep)
|
|
{
|
|
return ep->info.product_id ? ep->info.product_id : "Unique Product ID";
|
|
}
|
|
|
|
/* get FB name string */
|
|
static const char *ump_fb_name(const struct f_midi2_block_info *info)
|
|
{
|
|
return info->name ? info->name : "MIDI 2.0 Gadget I/O";
|
|
}
|
|
|
|
/*
|
|
* USB Descriptor Definitions
|
|
*/
|
|
/* GTB header descriptor */
|
|
static struct usb_ms20_gr_trm_block_header_descriptor gtb_header_desc = {
|
|
.bLength = sizeof(gtb_header_desc),
|
|
.bDescriptorType = USB_DT_CS_GR_TRM_BLOCK,
|
|
.bDescriptorSubtype = USB_MS_GR_TRM_BLOCK_HEADER,
|
|
.wTotalLength = __cpu_to_le16(0x12), // to be filled
|
|
};
|
|
|
|
/* GTB descriptor template: most items are replaced dynamically */
|
|
static struct usb_ms20_gr_trm_block_descriptor gtb_desc = {
|
|
.bLength = sizeof(gtb_desc),
|
|
.bDescriptorType = USB_DT_CS_GR_TRM_BLOCK,
|
|
.bDescriptorSubtype = USB_MS_GR_TRM_BLOCK,
|
|
.bGrpTrmBlkID = 0x01,
|
|
.bGrpTrmBlkType = USB_MS_GR_TRM_BLOCK_TYPE_BIDIRECTIONAL,
|
|
.nGroupTrm = 0x00,
|
|
.nNumGroupTrm = 1,
|
|
.iBlockItem = 0,
|
|
.bMIDIProtocol = USB_MS_MIDI_PROTO_1_0_64,
|
|
.wMaxInputBandwidth = 0,
|
|
.wMaxOutputBandwidth = 0,
|
|
};
|
|
|
|
DECLARE_USB_MIDI_OUT_JACK_DESCRIPTOR(1);
|
|
DECLARE_USB_MS_ENDPOINT_DESCRIPTOR(16);
|
|
DECLARE_UAC_AC_HEADER_DESCRIPTOR(1);
|
|
DECLARE_USB_MS20_ENDPOINT_DESCRIPTOR(32);
|
|
|
|
#define EP_MAX_PACKET_INT 8
|
|
|
|
/* Audio Control Interface */
|
|
static struct usb_interface_descriptor midi2_audio_if_desc = {
|
|
.bLength = USB_DT_INTERFACE_SIZE,
|
|
.bDescriptorType = USB_DT_INTERFACE,
|
|
.bInterfaceNumber = 0, // to be filled
|
|
.bNumEndpoints = 0,
|
|
.bInterfaceClass = USB_CLASS_AUDIO,
|
|
.bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL,
|
|
.bInterfaceProtocol = 0,
|
|
.iInterface = 0,
|
|
};
|
|
|
|
static struct uac1_ac_header_descriptor_1 midi2_audio_class_desc = {
|
|
.bLength = 0x09,
|
|
.bDescriptorType = USB_DT_CS_INTERFACE,
|
|
.bDescriptorSubtype = 0x01,
|
|
.bcdADC = __cpu_to_le16(0x0100),
|
|
.wTotalLength = __cpu_to_le16(0x0009),
|
|
.bInCollection = 0x01,
|
|
.baInterfaceNr = { 0x01 }, // to be filled
|
|
};
|
|
|
|
/* MIDI 1.0 Streaming Interface (altset 0) */
|
|
static struct usb_interface_descriptor midi2_midi1_if_desc = {
|
|
.bLength = USB_DT_INTERFACE_SIZE,
|
|
.bDescriptorType = USB_DT_INTERFACE,
|
|
.bInterfaceNumber = 0, // to be filled
|
|
.bAlternateSetting = 0,
|
|
.bNumEndpoints = 2, // to be filled
|
|
.bInterfaceClass = USB_CLASS_AUDIO,
|
|
.bInterfaceSubClass = USB_SUBCLASS_MIDISTREAMING,
|
|
.bInterfaceProtocol = 0,
|
|
.iInterface = 0, // to be filled
|
|
};
|
|
|
|
static struct usb_ms_header_descriptor midi2_midi1_class_desc = {
|
|
.bLength = 0x07,
|
|
.bDescriptorType = USB_DT_CS_INTERFACE,
|
|
.bDescriptorSubtype = USB_MS_HEADER,
|
|
.bcdMSC = __cpu_to_le16(0x0100),
|
|
.wTotalLength = __cpu_to_le16(0x41), // to be calculated
|
|
};
|
|
|
|
/* MIDI 1.0 EP OUT */
|
|
static struct usb_endpoint_descriptor midi2_midi1_ep_out_desc = {
|
|
.bLength = USB_DT_ENDPOINT_AUDIO_SIZE,
|
|
.bDescriptorType = USB_DT_ENDPOINT,
|
|
.bEndpointAddress = USB_DIR_OUT | 0, // set up dynamically
|
|
.bmAttributes = USB_ENDPOINT_XFER_BULK,
|
|
};
|
|
|
|
static struct usb_ss_ep_comp_descriptor midi2_midi1_ep_out_ss_comp_desc = {
|
|
.bLength = sizeof(midi2_midi1_ep_out_ss_comp_desc),
|
|
.bDescriptorType = USB_DT_SS_ENDPOINT_COMP,
|
|
};
|
|
|
|
static struct usb_ms_endpoint_descriptor_16 midi2_midi1_ep_out_class_desc = {
|
|
.bLength = 0x05, // to be filled
|
|
.bDescriptorType = USB_DT_CS_ENDPOINT,
|
|
.bDescriptorSubtype = USB_MS_GENERAL,
|
|
.bNumEmbMIDIJack = 1,
|
|
.baAssocJackID = { 0x01 },
|
|
};
|
|
|
|
/* MIDI 1.0 EP IN */
|
|
static struct usb_endpoint_descriptor midi2_midi1_ep_in_desc = {
|
|
.bLength = USB_DT_ENDPOINT_AUDIO_SIZE,
|
|
.bDescriptorType = USB_DT_ENDPOINT,
|
|
.bEndpointAddress = USB_DIR_IN | 0, // set up dynamically
|
|
.bmAttributes = USB_ENDPOINT_XFER_BULK,
|
|
};
|
|
|
|
static struct usb_ss_ep_comp_descriptor midi2_midi1_ep_in_ss_comp_desc = {
|
|
.bLength = sizeof(midi2_midi1_ep_in_ss_comp_desc),
|
|
.bDescriptorType = USB_DT_SS_ENDPOINT_COMP,
|
|
};
|
|
|
|
static struct usb_ms_endpoint_descriptor_16 midi2_midi1_ep_in_class_desc = {
|
|
.bLength = 0x05, // to be filled
|
|
.bDescriptorType = USB_DT_CS_ENDPOINT,
|
|
.bDescriptorSubtype = USB_MS_GENERAL,
|
|
.bNumEmbMIDIJack = 1,
|
|
.baAssocJackID = { 0x03 },
|
|
};
|
|
|
|
/* MIDI 2.0 Streaming Interface (altset 1) */
|
|
static struct usb_interface_descriptor midi2_midi2_if_desc = {
|
|
.bLength = USB_DT_INTERFACE_SIZE,
|
|
.bDescriptorType = USB_DT_INTERFACE,
|
|
.bInterfaceNumber = 0, // to be filled
|
|
.bAlternateSetting = 1,
|
|
.bNumEndpoints = 2, // to be filled
|
|
.bInterfaceClass = USB_CLASS_AUDIO,
|
|
.bInterfaceSubClass = USB_SUBCLASS_MIDISTREAMING,
|
|
.bInterfaceProtocol = 0,
|
|
.iInterface = 0, // to be filled
|
|
};
|
|
|
|
static struct usb_ms_header_descriptor midi2_midi2_class_desc = {
|
|
.bLength = 0x07,
|
|
.bDescriptorType = USB_DT_CS_INTERFACE,
|
|
.bDescriptorSubtype = USB_MS_HEADER,
|
|
.bcdMSC = __cpu_to_le16(0x0200),
|
|
.wTotalLength = __cpu_to_le16(0x07),
|
|
};
|
|
|
|
/* MIDI 2.0 EP OUT */
|
|
static struct usb_endpoint_descriptor midi2_midi2_ep_out_desc[MAX_UMP_EPS];
|
|
|
|
static struct usb_ss_ep_comp_descriptor midi2_midi2_ep_out_ss_comp_desc = {
|
|
.bLength = sizeof(midi2_midi1_ep_out_ss_comp_desc),
|
|
.bDescriptorType = USB_DT_SS_ENDPOINT_COMP,
|
|
};
|
|
|
|
static struct usb_ms20_endpoint_descriptor_32 midi2_midi2_ep_out_class_desc[MAX_UMP_EPS];
|
|
|
|
/* MIDI 2.0 EP IN */
|
|
static struct usb_endpoint_descriptor midi2_midi2_ep_in_desc[MAX_UMP_EPS];
|
|
|
|
static struct usb_ss_ep_comp_descriptor midi2_midi2_ep_in_ss_comp_desc = {
|
|
.bLength = sizeof(midi2_midi2_ep_in_ss_comp_desc),
|
|
.bDescriptorType = USB_DT_SS_ENDPOINT_COMP,
|
|
};
|
|
|
|
static struct usb_ms20_endpoint_descriptor_32 midi2_midi2_ep_in_class_desc[MAX_UMP_EPS];
|
|
|
|
/* Arrays of descriptors to be created */
|
|
static void *midi2_audio_descs[] = {
|
|
&midi2_audio_if_desc,
|
|
&midi2_audio_class_desc,
|
|
NULL
|
|
};
|
|
|
|
static void *midi2_midi1_descs[] = {
|
|
&midi2_midi1_if_desc,
|
|
&midi2_midi1_class_desc,
|
|
NULL
|
|
};
|
|
|
|
static void *midi2_midi1_ep_out_descs[] = {
|
|
&midi2_midi1_ep_out_desc,
|
|
&midi2_midi1_ep_out_class_desc,
|
|
NULL
|
|
};
|
|
|
|
static void *midi2_midi1_ep_in_descs[] = {
|
|
&midi2_midi1_ep_in_desc,
|
|
&midi2_midi1_ep_in_class_desc,
|
|
NULL
|
|
};
|
|
|
|
static void *midi2_midi1_ep_out_ss_descs[] = {
|
|
&midi2_midi1_ep_out_desc,
|
|
&midi2_midi1_ep_out_ss_comp_desc,
|
|
&midi2_midi1_ep_out_class_desc,
|
|
NULL
|
|
};
|
|
|
|
static void *midi2_midi1_ep_in_ss_descs[] = {
|
|
&midi2_midi1_ep_in_desc,
|
|
&midi2_midi1_ep_in_ss_comp_desc,
|
|
&midi2_midi1_ep_in_class_desc,
|
|
NULL
|
|
};
|
|
|
|
static void *midi2_midi2_descs[] = {
|
|
&midi2_midi2_if_desc,
|
|
&midi2_midi2_class_desc,
|
|
NULL
|
|
};
|
|
|
|
/*
|
|
* USB request handling
|
|
*/
|
|
|
|
/* get an empty request for the given EP */
|
|
static struct usb_request *get_empty_request(struct f_midi2_usb_ep *usb_ep)
|
|
{
|
|
struct usb_request *req = NULL;
|
|
unsigned long flags;
|
|
int index;
|
|
|
|
spin_lock_irqsave(&usb_ep->card->queue_lock, flags);
|
|
if (!usb_ep->free_reqs)
|
|
goto unlock;
|
|
index = find_first_bit(&usb_ep->free_reqs, usb_ep->num_reqs);
|
|
if (index >= usb_ep->num_reqs)
|
|
goto unlock;
|
|
req = usb_ep->reqs[index].req;
|
|
if (!req)
|
|
goto unlock;
|
|
clear_bit(index, &usb_ep->free_reqs);
|
|
req->length = 0;
|
|
unlock:
|
|
spin_unlock_irqrestore(&usb_ep->card->queue_lock, flags);
|
|
return req;
|
|
}
|
|
|
|
/* put the empty request back */
|
|
static void put_empty_request(struct usb_request *req)
|
|
{
|
|
struct f_midi2_req_ctx *ctx = req->context;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&ctx->usb_ep->card->queue_lock, flags);
|
|
set_bit(ctx->index, &ctx->usb_ep->free_reqs);
|
|
spin_unlock_irqrestore(&ctx->usb_ep->card->queue_lock, flags);
|
|
}
|
|
|
|
/*
|
|
* UMP v1.1 Stream message handling
|
|
*/
|
|
|
|
/* queue a request to UMP EP; request is either queued or freed after this */
|
|
static int queue_request_ep_raw(struct usb_request *req)
|
|
{
|
|
struct f_midi2_req_ctx *ctx = req->context;
|
|
int err;
|
|
|
|
req->complete = ctx->usb_ep->complete;
|
|
err = usb_ep_queue(ctx->usb_ep->usb_ep, req, GFP_ATOMIC);
|
|
if (err) {
|
|
put_empty_request(req);
|
|
return err;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* queue a request with endianness conversion */
|
|
static int queue_request_ep_in(struct usb_request *req)
|
|
{
|
|
/* UMP packets have to be converted to little-endian */
|
|
cpu_to_le32_array((u32 *)req->buf, req->length >> 2);
|
|
return queue_request_ep_raw(req);
|
|
}
|
|
|
|
/* reply a UMP packet via EP-in */
|
|
static int reply_ep_in(struct f_midi2_ep *ep, const void *buf, int len)
|
|
{
|
|
struct f_midi2_usb_ep *usb_ep = &ep->ep_in;
|
|
struct usb_request *req;
|
|
|
|
req = get_empty_request(usb_ep);
|
|
if (!req)
|
|
return -ENOSPC;
|
|
|
|
req->length = len;
|
|
memcpy(req->buf, buf, len);
|
|
return queue_request_ep_in(req);
|
|
}
|
|
|
|
/* reply a UMP stream EP info */
|
|
static void reply_ump_stream_ep_info(struct f_midi2_ep *ep)
|
|
{
|
|
struct snd_ump_stream_msg_ep_info rep = {
|
|
.type = UMP_MSG_TYPE_STREAM,
|
|
.status = UMP_STREAM_MSG_STATUS_EP_INFO,
|
|
.ump_version_major = 0x01,
|
|
.ump_version_minor = 0x01,
|
|
.num_function_blocks = ep->num_blks,
|
|
.static_function_block = !!ep->card->info.static_block,
|
|
.protocol = (UMP_STREAM_MSG_EP_INFO_CAP_MIDI1 |
|
|
UMP_STREAM_MSG_EP_INFO_CAP_MIDI2) >> 8,
|
|
};
|
|
|
|
reply_ep_in(ep, &rep, sizeof(rep));
|
|
}
|
|
|
|
/* reply a UMP EP device info */
|
|
static void reply_ump_stream_ep_device(struct f_midi2_ep *ep)
|
|
{
|
|
struct snd_ump_stream_msg_devince_info rep = {
|
|
.type = UMP_MSG_TYPE_STREAM,
|
|
.status = UMP_STREAM_MSG_STATUS_DEVICE_INFO,
|
|
.manufacture_id = ep->info.manufacturer,
|
|
.family_lsb = ep->info.family & 0xff,
|
|
.family_msb = (ep->info.family >> 8) & 0xff,
|
|
.model_lsb = ep->info.model & 0xff,
|
|
.model_msb = (ep->info.model >> 8) & 0xff,
|
|
.sw_revision = ep->info.sw_revision,
|
|
};
|
|
|
|
reply_ep_in(ep, &rep, sizeof(rep));
|
|
}
|
|
|
|
#define UMP_STREAM_PKT_BYTES 16 /* UMP stream packet size = 16 bytes*/
|
|
#define UMP_STREAM_EP_STR_OFF 2 /* offset of name string for EP info */
|
|
#define UMP_STREAM_FB_STR_OFF 3 /* offset of name string for FB info */
|
|
|
|
/* Helper to replay a string */
|
|
static void reply_ump_stream_string(struct f_midi2_ep *ep, const u8 *name,
|
|
unsigned int type, unsigned int extra,
|
|
unsigned int start_ofs)
|
|
{
|
|
struct f_midi2_usb_ep *usb_ep = &ep->ep_in;
|
|
struct f_midi2 *midi2 = ep->card;
|
|
struct usb_request *req;
|
|
unsigned int pos;
|
|
u32 *buf;
|
|
|
|
if (!*name)
|
|
return;
|
|
req = get_empty_request(usb_ep);
|
|
if (!req)
|
|
return;
|
|
|
|
buf = (u32 *)req->buf;
|
|
pos = start_ofs;
|
|
for (;;) {
|
|
if (pos == start_ofs) {
|
|
memset(buf, 0, UMP_STREAM_PKT_BYTES);
|
|
buf[0] = ump_stream_compose(type, 0) | extra;
|
|
}
|
|
buf[pos / 4] |= *name++ << ((3 - (pos % 4)) * 8);
|
|
if (!*name) {
|
|
if (req->length)
|
|
buf[0] |= UMP_STREAM_MSG_FORMAT_END << 26;
|
|
req->length += UMP_STREAM_PKT_BYTES;
|
|
break;
|
|
}
|
|
if (++pos == UMP_STREAM_PKT_BYTES) {
|
|
if (!req->length)
|
|
buf[0] |= UMP_STREAM_MSG_FORMAT_START << 26;
|
|
else
|
|
buf[0] |= UMP_STREAM_MSG_FORMAT_CONTINUE << 26;
|
|
req->length += UMP_STREAM_PKT_BYTES;
|
|
if (midi2->info.req_buf_size - req->length < UMP_STREAM_PKT_BYTES)
|
|
break;
|
|
buf += 4;
|
|
pos = start_ofs;
|
|
}
|
|
}
|
|
|
|
if (req->length)
|
|
queue_request_ep_in(req);
|
|
else
|
|
put_empty_request(req);
|
|
}
|
|
|
|
/* Reply a UMP EP name string */
|
|
static void reply_ump_stream_ep_name(struct f_midi2_ep *ep)
|
|
{
|
|
reply_ump_stream_string(ep, ump_ep_name(ep),
|
|
UMP_STREAM_MSG_STATUS_EP_NAME, 0,
|
|
UMP_STREAM_EP_STR_OFF);
|
|
}
|
|
|
|
/* Reply a UMP EP product ID string */
|
|
static void reply_ump_stream_ep_pid(struct f_midi2_ep *ep)
|
|
{
|
|
reply_ump_stream_string(ep, ump_product_id(ep),
|
|
UMP_STREAM_MSG_STATUS_PRODUCT_ID, 0,
|
|
UMP_STREAM_EP_STR_OFF);
|
|
}
|
|
|
|
/* Reply a UMP EP stream config */
|
|
static void reply_ump_stream_ep_config(struct f_midi2_ep *ep)
|
|
{
|
|
struct snd_ump_stream_msg_stream_cfg rep = {
|
|
.type = UMP_MSG_TYPE_STREAM,
|
|
.status = UMP_STREAM_MSG_STATUS_STREAM_CFG,
|
|
};
|
|
|
|
if ((ep->info.protocol & SNDRV_UMP_EP_INFO_PROTO_MIDI_MASK) ==
|
|
SNDRV_UMP_EP_INFO_PROTO_MIDI2)
|
|
rep.protocol = UMP_STREAM_MSG_EP_INFO_CAP_MIDI2 >> 8;
|
|
else
|
|
rep.protocol = UMP_STREAM_MSG_EP_INFO_CAP_MIDI1 >> 8;
|
|
|
|
reply_ep_in(ep, &rep, sizeof(rep));
|
|
}
|
|
|
|
/* Reply a UMP FB info */
|
|
static void reply_ump_stream_fb_info(struct f_midi2_ep *ep, int blk)
|
|
{
|
|
struct f_midi2_block_info *b = &ep->blks[blk].info;
|
|
struct snd_ump_stream_msg_fb_info rep = {
|
|
.type = UMP_MSG_TYPE_STREAM,
|
|
.status = UMP_STREAM_MSG_STATUS_FB_INFO,
|
|
.active = !!b->active,
|
|
.function_block_id = blk,
|
|
.ui_hint = b->ui_hint,
|
|
.midi_10 = b->is_midi1,
|
|
.direction = b->direction,
|
|
.first_group = b->first_group,
|
|
.num_groups = b->num_groups,
|
|
.midi_ci_version = b->midi_ci_version,
|
|
.sysex8_streams = b->sysex8_streams,
|
|
};
|
|
|
|
reply_ep_in(ep, &rep, sizeof(rep));
|
|
}
|
|
|
|
/* Reply a FB name string */
|
|
static void reply_ump_stream_fb_name(struct f_midi2_ep *ep, unsigned int blk)
|
|
{
|
|
reply_ump_stream_string(ep, ump_fb_name(&ep->blks[blk].info),
|
|
UMP_STREAM_MSG_STATUS_FB_NAME, blk << 8,
|
|
UMP_STREAM_FB_STR_OFF);
|
|
}
|
|
|
|
/* Process a UMP Stream message */
|
|
static void process_ump_stream_msg(struct f_midi2_ep *ep, const u32 *data)
|
|
{
|
|
struct f_midi2 *midi2 = ep->card;
|
|
unsigned int format, status, blk;
|
|
|
|
format = ump_stream_message_format(*data);
|
|
status = ump_stream_message_status(*data);
|
|
switch (status) {
|
|
case UMP_STREAM_MSG_STATUS_EP_DISCOVERY:
|
|
if (format)
|
|
return; // invalid
|
|
if (data[1] & UMP_STREAM_MSG_REQUEST_EP_INFO)
|
|
reply_ump_stream_ep_info(ep);
|
|
if (data[1] & UMP_STREAM_MSG_REQUEST_DEVICE_INFO)
|
|
reply_ump_stream_ep_device(ep);
|
|
if (data[1] & UMP_STREAM_MSG_REQUEST_EP_NAME)
|
|
reply_ump_stream_ep_name(ep);
|
|
if (data[1] & UMP_STREAM_MSG_REQUEST_PRODUCT_ID)
|
|
reply_ump_stream_ep_pid(ep);
|
|
if (data[1] & UMP_STREAM_MSG_REQUEST_STREAM_CFG)
|
|
reply_ump_stream_ep_config(ep);
|
|
return;
|
|
case UMP_STREAM_MSG_STATUS_STREAM_CFG_REQUEST:
|
|
if (*data & UMP_STREAM_MSG_EP_INFO_CAP_MIDI2) {
|
|
ep->info.protocol = SNDRV_UMP_EP_INFO_PROTO_MIDI2;
|
|
DBG(midi2, "Switching Protocol to MIDI2\n");
|
|
} else {
|
|
ep->info.protocol = SNDRV_UMP_EP_INFO_PROTO_MIDI1;
|
|
DBG(midi2, "Switching Protocol to MIDI1\n");
|
|
}
|
|
snd_ump_switch_protocol(ep->ump, ep->info.protocol);
|
|
reply_ump_stream_ep_config(ep);
|
|
return;
|
|
case UMP_STREAM_MSG_STATUS_FB_DISCOVERY:
|
|
if (format)
|
|
return; // invalid
|
|
blk = (*data >> 8) & 0xff;
|
|
if (blk >= ep->num_blks)
|
|
return;
|
|
if (*data & UMP_STREAM_MSG_REQUEST_FB_INFO)
|
|
reply_ump_stream_fb_info(ep, blk);
|
|
if (*data & UMP_STREAM_MSG_REQUEST_FB_NAME)
|
|
reply_ump_stream_fb_name(ep, blk);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* Process UMP messages included in a USB request */
|
|
static void process_ump(struct f_midi2_ep *ep, const struct usb_request *req)
|
|
{
|
|
const u32 *data = (u32 *)req->buf;
|
|
int len = req->actual >> 2;
|
|
const u32 *in_buf = ep->ump->input_buf;
|
|
|
|
for (; len > 0; len--, data++) {
|
|
if (snd_ump_receive_ump_val(ep->ump, *data) <= 0)
|
|
continue;
|
|
if (ump_message_type(*in_buf) == UMP_MSG_TYPE_STREAM)
|
|
process_ump_stream_msg(ep, in_buf);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* MIDI 2.0 UMP USB request handling
|
|
*/
|
|
|
|
/* complete handler for UMP EP-out requests */
|
|
static void f_midi2_ep_out_complete(struct usb_ep *usb_ep,
|
|
struct usb_request *req)
|
|
{
|
|
struct f_midi2_req_ctx *ctx = req->context;
|
|
struct f_midi2_ep *ep = ctx->usb_ep->ep;
|
|
struct f_midi2 *midi2 = ep->card;
|
|
int status = req->status;
|
|
|
|
if (status) {
|
|
DBG(midi2, "%s complete error %d: %d/%d\n",
|
|
usb_ep->name, status, req->actual, req->length);
|
|
goto error;
|
|
}
|
|
|
|
/* convert to UMP packet in native endianness */
|
|
le32_to_cpu_array((u32 *)req->buf, req->actual >> 2);
|
|
|
|
if (midi2->info.process_ump)
|
|
process_ump(ep, req);
|
|
|
|
snd_ump_receive(ep->ump, req->buf, req->actual & ~3);
|
|
|
|
if (midi2->operation_mode != MIDI_OP_MODE_MIDI2)
|
|
goto error;
|
|
|
|
if (queue_request_ep_raw(req))
|
|
goto error;
|
|
return;
|
|
|
|
error:
|
|
put_empty_request(req);
|
|
}
|
|
|
|
/* Transmit UMP packets received from user-space to the gadget */
|
|
static void process_ump_transmit(struct f_midi2_ep *ep)
|
|
{
|
|
struct f_midi2_usb_ep *usb_ep = &ep->ep_in;
|
|
struct f_midi2 *midi2 = ep->card;
|
|
struct usb_request *req;
|
|
int len;
|
|
|
|
if (!usb_ep->usb_ep->enabled)
|
|
return;
|
|
|
|
for (;;) {
|
|
req = get_empty_request(usb_ep);
|
|
if (!req)
|
|
break;
|
|
len = snd_ump_transmit(ep->ump, (u32 *)req->buf,
|
|
midi2->info.req_buf_size);
|
|
if (len <= 0) {
|
|
put_empty_request(req);
|
|
break;
|
|
}
|
|
|
|
req->length = len;
|
|
if (queue_request_ep_in(req) < 0)
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Complete handler for UMP EP-in requests */
|
|
static void f_midi2_ep_in_complete(struct usb_ep *usb_ep,
|
|
struct usb_request *req)
|
|
{
|
|
struct f_midi2_req_ctx *ctx = req->context;
|
|
struct f_midi2_ep *ep = ctx->usb_ep->ep;
|
|
struct f_midi2 *midi2 = ep->card;
|
|
int status = req->status;
|
|
|
|
put_empty_request(req);
|
|
|
|
if (status) {
|
|
DBG(midi2, "%s complete error %d: %d/%d\n",
|
|
usb_ep->name, status, req->actual, req->length);
|
|
return;
|
|
}
|
|
|
|
process_ump_transmit(ep);
|
|
}
|
|
|
|
/*
|
|
* MIDI1 (altset 0) USB request handling
|
|
*/
|
|
|
|
/* process one MIDI byte -- copied from f_midi.c
|
|
*
|
|
* fill the packet or request if needed
|
|
* returns true if the request became empty (queued)
|
|
*/
|
|
static bool process_midi1_byte(struct f_midi2 *midi2, u8 cable, u8 b,
|
|
struct usb_request **req_p)
|
|
{
|
|
struct f_midi2_midi1_port *port = &midi2->midi1_port[cable];
|
|
u8 p[4] = { cable << 4, 0, 0, 0 };
|
|
int next_state = STATE_INITIAL;
|
|
struct usb_request *req = *req_p;
|
|
|
|
switch (b) {
|
|
case 0xf8 ... 0xff:
|
|
/* System Real-Time Messages */
|
|
p[0] |= 0x0f;
|
|
p[1] = b;
|
|
next_state = port->state;
|
|
port->state = STATE_REAL_TIME;
|
|
break;
|
|
|
|
case 0xf7:
|
|
/* End of SysEx */
|
|
switch (port->state) {
|
|
case STATE_SYSEX_0:
|
|
p[0] |= 0x05;
|
|
p[1] = 0xf7;
|
|
next_state = STATE_FINISHED;
|
|
break;
|
|
case STATE_SYSEX_1:
|
|
p[0] |= 0x06;
|
|
p[1] = port->data[0];
|
|
p[2] = 0xf7;
|
|
next_state = STATE_FINISHED;
|
|
break;
|
|
case STATE_SYSEX_2:
|
|
p[0] |= 0x07;
|
|
p[1] = port->data[0];
|
|
p[2] = port->data[1];
|
|
p[3] = 0xf7;
|
|
next_state = STATE_FINISHED;
|
|
break;
|
|
default:
|
|
/* Ignore byte */
|
|
next_state = port->state;
|
|
port->state = STATE_INITIAL;
|
|
}
|
|
break;
|
|
|
|
case 0xf0 ... 0xf6:
|
|
/* System Common Messages */
|
|
port->data[0] = port->data[1] = 0;
|
|
port->state = STATE_INITIAL;
|
|
switch (b) {
|
|
case 0xf0:
|
|
port->data[0] = b;
|
|
port->data[1] = 0;
|
|
next_state = STATE_SYSEX_1;
|
|
break;
|
|
case 0xf1:
|
|
case 0xf3:
|
|
port->data[0] = b;
|
|
next_state = STATE_1PARAM;
|
|
break;
|
|
case 0xf2:
|
|
port->data[0] = b;
|
|
next_state = STATE_2PARAM_1;
|
|
break;
|
|
case 0xf4:
|
|
case 0xf5:
|
|
next_state = STATE_INITIAL;
|
|
break;
|
|
case 0xf6:
|
|
p[0] |= 0x05;
|
|
p[1] = 0xf6;
|
|
next_state = STATE_FINISHED;
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case 0x80 ... 0xef:
|
|
/*
|
|
* Channel Voice Messages, Channel Mode Messages
|
|
* and Control Change Messages.
|
|
*/
|
|
port->data[0] = b;
|
|
port->data[1] = 0;
|
|
port->state = STATE_INITIAL;
|
|
if (b >= 0xc0 && b <= 0xdf)
|
|
next_state = STATE_1PARAM;
|
|
else
|
|
next_state = STATE_2PARAM_1;
|
|
break;
|
|
|
|
case 0x00 ... 0x7f:
|
|
/* Message parameters */
|
|
switch (port->state) {
|
|
case STATE_1PARAM:
|
|
if (port->data[0] < 0xf0)
|
|
p[0] |= port->data[0] >> 4;
|
|
else
|
|
p[0] |= 0x02;
|
|
|
|
p[1] = port->data[0];
|
|
p[2] = b;
|
|
/* This is to allow Running State Messages */
|
|
next_state = STATE_1PARAM;
|
|
break;
|
|
case STATE_2PARAM_1:
|
|
port->data[1] = b;
|
|
next_state = STATE_2PARAM_2;
|
|
break;
|
|
case STATE_2PARAM_2:
|
|
if (port->data[0] < 0xf0)
|
|
p[0] |= port->data[0] >> 4;
|
|
else
|
|
p[0] |= 0x03;
|
|
|
|
p[1] = port->data[0];
|
|
p[2] = port->data[1];
|
|
p[3] = b;
|
|
/* This is to allow Running State Messages */
|
|
next_state = STATE_2PARAM_1;
|
|
break;
|
|
case STATE_SYSEX_0:
|
|
port->data[0] = b;
|
|
next_state = STATE_SYSEX_1;
|
|
break;
|
|
case STATE_SYSEX_1:
|
|
port->data[1] = b;
|
|
next_state = STATE_SYSEX_2;
|
|
break;
|
|
case STATE_SYSEX_2:
|
|
p[0] |= 0x04;
|
|
p[1] = port->data[0];
|
|
p[2] = port->data[1];
|
|
p[3] = b;
|
|
next_state = STATE_SYSEX_0;
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
|
|
/* States where we have to write into the USB request */
|
|
if (next_state == STATE_FINISHED ||
|
|
port->state == STATE_SYSEX_2 ||
|
|
port->state == STATE_1PARAM ||
|
|
port->state == STATE_2PARAM_2 ||
|
|
port->state == STATE_REAL_TIME) {
|
|
memcpy(req->buf + req->length, p, sizeof(p));
|
|
req->length += sizeof(p);
|
|
|
|
if (next_state == STATE_FINISHED) {
|
|
next_state = STATE_INITIAL;
|
|
port->data[0] = port->data[1] = 0;
|
|
}
|
|
|
|
if (midi2->info.req_buf_size - req->length <= 4) {
|
|
queue_request_ep_raw(req);
|
|
*req_p = NULL;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
port->state = next_state;
|
|
return false;
|
|
}
|
|
|
|
/* process all pending MIDI bytes in the internal buffer;
|
|
* returns true if the request gets empty
|
|
* returns false if all have been processed
|
|
*/
|
|
static bool process_midi1_pending_buf(struct f_midi2 *midi2,
|
|
struct usb_request **req_p)
|
|
{
|
|
unsigned int cable, c;
|
|
|
|
for (cable = 0; cable < midi2->num_midi1_in; cable++) {
|
|
struct f_midi2_midi1_port *port = &midi2->midi1_port[cable];
|
|
|
|
if (!port->pending)
|
|
continue;
|
|
for (c = 0; c < port->pending; c++) {
|
|
if (process_midi1_byte(midi2, cable, port->buf[c],
|
|
req_p)) {
|
|
port->pending -= c;
|
|
if (port->pending)
|
|
memmove(port->buf, port->buf + c,
|
|
port->pending);
|
|
return true;
|
|
}
|
|
}
|
|
port->pending = 0;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/* fill the MIDI bytes onto the temporary buffer
|
|
*/
|
|
static void fill_midi1_pending_buf(struct f_midi2 *midi2, u8 cable, u8 *buf,
|
|
unsigned int size)
|
|
{
|
|
struct f_midi2_midi1_port *port = &midi2->midi1_port[cable];
|
|
|
|
if (port->pending + size > sizeof(port->buf))
|
|
return;
|
|
memcpy(port->buf + port->pending, buf, size);
|
|
port->pending += size;
|
|
}
|
|
|
|
/* try to process data given from the associated UMP stream */
|
|
static void process_midi1_transmit(struct f_midi2 *midi2)
|
|
{
|
|
struct f_midi2_usb_ep *usb_ep = &midi2->midi1_ep_in;
|
|
struct f_midi2_ep *ep = &midi2->midi2_eps[0];
|
|
struct usb_request *req = NULL;
|
|
/* 12 is the largest outcome (4 MIDI1 cmds) for a single UMP packet */
|
|
unsigned char outbuf[12];
|
|
unsigned char group, cable;
|
|
int len, size;
|
|
u32 ump;
|
|
|
|
if (!usb_ep->usb_ep || !usb_ep->usb_ep->enabled)
|
|
return;
|
|
|
|
for (;;) {
|
|
if (!req) {
|
|
req = get_empty_request(usb_ep);
|
|
if (!req)
|
|
break;
|
|
}
|
|
|
|
if (process_midi1_pending_buf(midi2, &req))
|
|
continue;
|
|
|
|
len = snd_ump_transmit(ep->ump, &ump, 4);
|
|
if (len <= 0)
|
|
break;
|
|
if (snd_ump_receive_ump_val(ep->ump, ump) <= 0)
|
|
continue;
|
|
size = snd_ump_convert_from_ump(ep->ump->input_buf, outbuf,
|
|
&group);
|
|
if (size <= 0)
|
|
continue;
|
|
cable = ep->in_group_to_cable[group];
|
|
if (!cable)
|
|
continue;
|
|
cable--; /* to 0-base */
|
|
fill_midi1_pending_buf(midi2, cable, outbuf, size);
|
|
}
|
|
|
|
if (req) {
|
|
if (req->length)
|
|
queue_request_ep_raw(req);
|
|
else
|
|
put_empty_request(req);
|
|
}
|
|
}
|
|
|
|
/* complete handler for MIDI1 EP-in requests */
|
|
static void f_midi2_midi1_ep_in_complete(struct usb_ep *usb_ep,
|
|
struct usb_request *req)
|
|
{
|
|
struct f_midi2_req_ctx *ctx = req->context;
|
|
struct f_midi2 *midi2 = ctx->usb_ep->card;
|
|
int status = req->status;
|
|
|
|
put_empty_request(req);
|
|
|
|
if (status) {
|
|
DBG(midi2, "%s complete error %d: %d/%d\n",
|
|
usb_ep->name, status, req->actual, req->length);
|
|
return;
|
|
}
|
|
|
|
process_midi1_transmit(midi2);
|
|
}
|
|
|
|
/* complete handler for MIDI1 EP-out requests */
|
|
static void f_midi2_midi1_ep_out_complete(struct usb_ep *usb_ep,
|
|
struct usb_request *req)
|
|
{
|
|
struct f_midi2_req_ctx *ctx = req->context;
|
|
struct f_midi2 *midi2 = ctx->usb_ep->card;
|
|
struct f_midi2_ep *ep;
|
|
struct ump_cvt_to_ump *cvt = &midi2->midi1_ump_cvt;
|
|
static const u8 midi1_packet_bytes[16] = {
|
|
0, 0, 2, 3, 3, 1, 2, 3, 3, 3, 3, 3, 2, 2, 3, 1
|
|
};
|
|
unsigned int group, cable, bytes, c, len;
|
|
int status = req->status;
|
|
const u8 *buf = req->buf;
|
|
|
|
if (status) {
|
|
DBG(midi2, "%s complete error %d: %d/%d\n",
|
|
usb_ep->name, status, req->actual, req->length);
|
|
goto error;
|
|
}
|
|
|
|
len = req->actual >> 2;
|
|
for (; len; len--, buf += 4) {
|
|
cable = *buf >> 4;
|
|
ep = midi2->out_cable_mapping[cable].ep;
|
|
if (!ep)
|
|
continue;
|
|
group = midi2->out_cable_mapping[cable].group;
|
|
bytes = midi1_packet_bytes[*buf & 0x0f];
|
|
for (c = 0; c < bytes; c++) {
|
|
snd_ump_convert_to_ump(cvt, group, ep->info.protocol,
|
|
buf[c + 1]);
|
|
if (cvt->ump_bytes) {
|
|
snd_ump_receive(ep->ump, cvt->ump,
|
|
cvt->ump_bytes);
|
|
cvt->ump_bytes = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (midi2->operation_mode != MIDI_OP_MODE_MIDI1)
|
|
goto error;
|
|
|
|
if (queue_request_ep_raw(req))
|
|
goto error;
|
|
return;
|
|
|
|
error:
|
|
put_empty_request(req);
|
|
}
|
|
|
|
/*
|
|
* Common EP handling helpers
|
|
*/
|
|
|
|
/* Start MIDI EP */
|
|
static int f_midi2_start_ep(struct f_midi2_usb_ep *usb_ep,
|
|
struct usb_function *fn)
|
|
{
|
|
int err;
|
|
|
|
if (!usb_ep->usb_ep)
|
|
return 0;
|
|
|
|
usb_ep_disable(usb_ep->usb_ep);
|
|
err = config_ep_by_speed(usb_ep->card->gadget, fn, usb_ep->usb_ep);
|
|
if (err)
|
|
return err;
|
|
return usb_ep_enable(usb_ep->usb_ep);
|
|
}
|
|
|
|
/* Drop pending requests */
|
|
static void f_midi2_drop_reqs(struct f_midi2_usb_ep *usb_ep)
|
|
{
|
|
int i;
|
|
|
|
if (!usb_ep->usb_ep || !usb_ep->num_reqs)
|
|
return;
|
|
|
|
for (i = 0; i < usb_ep->num_reqs; i++) {
|
|
if (!test_bit(i, &usb_ep->free_reqs) && usb_ep->reqs[i].req) {
|
|
usb_ep_dequeue(usb_ep->usb_ep, usb_ep->reqs[i].req);
|
|
set_bit(i, &usb_ep->free_reqs);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Allocate requests for the given EP */
|
|
static int f_midi2_alloc_ep_reqs(struct f_midi2_usb_ep *usb_ep)
|
|
{
|
|
struct f_midi2 *midi2 = usb_ep->card;
|
|
int i;
|
|
|
|
if (!usb_ep->usb_ep)
|
|
return 0;
|
|
if (!usb_ep->reqs)
|
|
return -EINVAL;
|
|
|
|
for (i = 0; i < midi2->info.num_reqs; i++) {
|
|
if (usb_ep->reqs[i].req)
|
|
continue;
|
|
usb_ep->reqs[i].req = alloc_ep_req(usb_ep->usb_ep,
|
|
midi2->info.req_buf_size);
|
|
if (!usb_ep->reqs[i].req)
|
|
return -ENOMEM;
|
|
usb_ep->reqs[i].req->context = &usb_ep->reqs[i];
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Free allocated requests */
|
|
static void f_midi2_free_ep_reqs(struct f_midi2_usb_ep *usb_ep)
|
|
{
|
|
struct f_midi2 *midi2 = usb_ep->card;
|
|
int i;
|
|
|
|
for (i = 0; i < midi2->info.num_reqs; i++) {
|
|
if (!usb_ep->reqs[i].req)
|
|
continue;
|
|
free_ep_req(usb_ep->usb_ep, usb_ep->reqs[i].req);
|
|
usb_ep->reqs[i].req = NULL;
|
|
}
|
|
}
|
|
|
|
/* Initialize EP */
|
|
static int f_midi2_init_ep(struct f_midi2 *midi2, struct f_midi2_ep *ep,
|
|
struct f_midi2_usb_ep *usb_ep,
|
|
void *desc,
|
|
void (*complete)(struct usb_ep *usb_ep,
|
|
struct usb_request *req))
|
|
{
|
|
int i;
|
|
|
|
usb_ep->card = midi2;
|
|
usb_ep->ep = ep;
|
|
usb_ep->usb_ep = usb_ep_autoconfig(midi2->gadget, desc);
|
|
if (!usb_ep->usb_ep)
|
|
return -ENODEV;
|
|
usb_ep->complete = complete;
|
|
|
|
usb_ep->reqs = kcalloc(midi2->info.num_reqs, sizeof(*usb_ep->reqs),
|
|
GFP_KERNEL);
|
|
if (!usb_ep->reqs)
|
|
return -ENOMEM;
|
|
for (i = 0; i < midi2->info.num_reqs; i++) {
|
|
usb_ep->reqs[i].index = i;
|
|
usb_ep->reqs[i].usb_ep = usb_ep;
|
|
set_bit(i, &usb_ep->free_reqs);
|
|
usb_ep->num_reqs++;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Free EP */
|
|
static void f_midi2_free_ep(struct f_midi2_usb_ep *usb_ep)
|
|
{
|
|
f_midi2_drop_reqs(usb_ep);
|
|
|
|
f_midi2_free_ep_reqs(usb_ep);
|
|
|
|
kfree(usb_ep->reqs);
|
|
usb_ep->num_reqs = 0;
|
|
usb_ep->free_reqs = 0;
|
|
usb_ep->reqs = NULL;
|
|
}
|
|
|
|
/* Queue requests for EP-out at start */
|
|
static void f_midi2_queue_out_reqs(struct f_midi2_usb_ep *usb_ep)
|
|
{
|
|
int i, err;
|
|
|
|
if (!usb_ep->usb_ep)
|
|
return;
|
|
|
|
for (i = 0; i < usb_ep->num_reqs; i++) {
|
|
if (!test_bit(i, &usb_ep->free_reqs) || !usb_ep->reqs[i].req)
|
|
continue;
|
|
usb_ep->reqs[i].req->complete = usb_ep->complete;
|
|
err = usb_ep_queue(usb_ep->usb_ep, usb_ep->reqs[i].req,
|
|
GFP_ATOMIC);
|
|
if (!err)
|
|
clear_bit(i, &usb_ep->free_reqs);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Gadget Function callbacks
|
|
*/
|
|
|
|
/* stop both IN and OUT EPs */
|
|
static void f_midi2_stop_eps(struct f_midi2_usb_ep *ep_in,
|
|
struct f_midi2_usb_ep *ep_out)
|
|
{
|
|
f_midi2_drop_reqs(ep_in);
|
|
f_midi2_drop_reqs(ep_out);
|
|
f_midi2_free_ep_reqs(ep_in);
|
|
f_midi2_free_ep_reqs(ep_out);
|
|
}
|
|
|
|
/* start/queue both IN and OUT EPs */
|
|
static int f_midi2_start_eps(struct f_midi2_usb_ep *ep_in,
|
|
struct f_midi2_usb_ep *ep_out,
|
|
struct usb_function *fn)
|
|
{
|
|
int err;
|
|
|
|
err = f_midi2_start_ep(ep_in, fn);
|
|
if (err)
|
|
return err;
|
|
err = f_midi2_start_ep(ep_out, fn);
|
|
if (err)
|
|
return err;
|
|
|
|
err = f_midi2_alloc_ep_reqs(ep_in);
|
|
if (err)
|
|
return err;
|
|
err = f_midi2_alloc_ep_reqs(ep_out);
|
|
if (err)
|
|
return err;
|
|
|
|
f_midi2_queue_out_reqs(ep_out);
|
|
return 0;
|
|
}
|
|
|
|
/* gadget function set_alt callback */
|
|
static int f_midi2_set_alt(struct usb_function *fn, unsigned int intf,
|
|
unsigned int alt)
|
|
{
|
|
struct f_midi2 *midi2 = func_to_midi2(fn);
|
|
struct f_midi2_ep *ep;
|
|
int i, op_mode, err;
|
|
|
|
if (intf != midi2->midi_if || alt > 1)
|
|
return 0;
|
|
|
|
if (alt == 0)
|
|
op_mode = MIDI_OP_MODE_MIDI1;
|
|
else if (alt == 1)
|
|
op_mode = MIDI_OP_MODE_MIDI2;
|
|
else
|
|
op_mode = MIDI_OP_MODE_UNSET;
|
|
|
|
if (midi2->operation_mode == op_mode)
|
|
return 0;
|
|
|
|
midi2->operation_mode = op_mode;
|
|
|
|
if (op_mode != MIDI_OP_MODE_MIDI1)
|
|
f_midi2_stop_eps(&midi2->midi1_ep_in, &midi2->midi1_ep_out);
|
|
|
|
if (op_mode != MIDI_OP_MODE_MIDI2) {
|
|
for (i = 0; i < midi2->num_eps; i++) {
|
|
ep = &midi2->midi2_eps[i];
|
|
f_midi2_stop_eps(&ep->ep_in, &ep->ep_out);
|
|
}
|
|
}
|
|
|
|
if (op_mode == MIDI_OP_MODE_MIDI1)
|
|
return f_midi2_start_eps(&midi2->midi1_ep_in,
|
|
&midi2->midi1_ep_out, fn);
|
|
|
|
if (op_mode == MIDI_OP_MODE_MIDI2) {
|
|
for (i = 0; i < midi2->num_eps; i++) {
|
|
ep = &midi2->midi2_eps[i];
|
|
|
|
err = f_midi2_start_eps(&ep->ep_in, &ep->ep_out, fn);
|
|
if (err)
|
|
return err;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* gadget function get_alt callback */
|
|
static int f_midi2_get_alt(struct usb_function *fn, unsigned int intf)
|
|
{
|
|
struct f_midi2 *midi2 = func_to_midi2(fn);
|
|
|
|
if (intf == midi2->midi_if &&
|
|
midi2->operation_mode == MIDI_OP_MODE_MIDI2)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
/* convert UMP direction to USB MIDI 2.0 direction */
|
|
static unsigned int ump_to_usb_dir(unsigned int ump_dir)
|
|
{
|
|
switch (ump_dir) {
|
|
case SNDRV_UMP_DIR_INPUT:
|
|
return USB_MS_GR_TRM_BLOCK_TYPE_INPUT_ONLY;
|
|
case SNDRV_UMP_DIR_OUTPUT:
|
|
return USB_MS_GR_TRM_BLOCK_TYPE_OUTPUT_ONLY;
|
|
default:
|
|
return USB_MS_GR_TRM_BLOCK_TYPE_BIDIRECTIONAL;
|
|
}
|
|
}
|
|
|
|
/* assign GTB descriptors (for the given request) */
|
|
static void assign_block_descriptors(struct f_midi2 *midi2,
|
|
struct usb_request *req,
|
|
int max_len)
|
|
{
|
|
struct usb_ms20_gr_trm_block_header_descriptor header;
|
|
struct usb_ms20_gr_trm_block_descriptor *desc;
|
|
struct f_midi2_block_info *b;
|
|
struct f_midi2_ep *ep;
|
|
int i, blk, len;
|
|
char *data;
|
|
|
|
len = sizeof(gtb_header_desc) + sizeof(gtb_desc) * midi2->total_blocks;
|
|
if (WARN_ON(len > midi2->info.req_buf_size))
|
|
return;
|
|
|
|
header = gtb_header_desc;
|
|
header.wTotalLength = cpu_to_le16(len);
|
|
if (max_len < len) {
|
|
len = min_t(int, len, sizeof(header));
|
|
memcpy(req->buf, &header, len);
|
|
req->length = len;
|
|
req->zero = len < max_len;
|
|
return;
|
|
}
|
|
|
|
memcpy(req->buf, &header, sizeof(header));
|
|
data = req->buf + sizeof(header);
|
|
for (i = 0; i < midi2->num_eps; i++) {
|
|
ep = &midi2->midi2_eps[i];
|
|
for (blk = 0; blk < ep->num_blks; blk++) {
|
|
b = &ep->blks[blk].info;
|
|
desc = (struct usb_ms20_gr_trm_block_descriptor *)data;
|
|
|
|
*desc = gtb_desc;
|
|
desc->bGrpTrmBlkID = ep->blks[blk].gtb_id;
|
|
desc->bGrpTrmBlkType = ump_to_usb_dir(b->direction);
|
|
desc->nGroupTrm = b->first_group;
|
|
desc->nNumGroupTrm = b->num_groups;
|
|
desc->iBlockItem = ep->blks[blk].string_id;
|
|
|
|
if (ep->info.protocol & SNDRV_UMP_EP_INFO_PROTO_MIDI2)
|
|
desc->bMIDIProtocol = USB_MS_MIDI_PROTO_2_0;
|
|
else
|
|
desc->bMIDIProtocol = USB_MS_MIDI_PROTO_1_0_128;
|
|
|
|
if (b->is_midi1 == 2) {
|
|
desc->wMaxInputBandwidth = cpu_to_le16(1);
|
|
desc->wMaxOutputBandwidth = cpu_to_le16(1);
|
|
}
|
|
|
|
data += sizeof(*desc);
|
|
}
|
|
}
|
|
|
|
req->length = len;
|
|
req->zero = len < max_len;
|
|
}
|
|
|
|
/* gadget function setup callback: handle GTB requests */
|
|
static int f_midi2_setup(struct usb_function *fn,
|
|
const struct usb_ctrlrequest *ctrl)
|
|
{
|
|
struct f_midi2 *midi2 = func_to_midi2(fn);
|
|
struct usb_composite_dev *cdev = fn->config->cdev;
|
|
struct usb_request *req = cdev->req;
|
|
u16 value, length;
|
|
|
|
if ((ctrl->bRequestType & USB_TYPE_MASK) != USB_TYPE_STANDARD ||
|
|
ctrl->bRequest != USB_REQ_GET_DESCRIPTOR)
|
|
return -EOPNOTSUPP;
|
|
|
|
value = le16_to_cpu(ctrl->wValue);
|
|
length = le16_to_cpu(ctrl->wLength);
|
|
|
|
if ((value >> 8) != USB_DT_CS_GR_TRM_BLOCK)
|
|
return -EOPNOTSUPP;
|
|
|
|
/* handle only altset 1 */
|
|
if ((value & 0xff) != 1)
|
|
return -EOPNOTSUPP;
|
|
|
|
assign_block_descriptors(midi2, req, length);
|
|
return usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC);
|
|
}
|
|
|
|
/* gadget function disable callback */
|
|
static void f_midi2_disable(struct usb_function *fn)
|
|
{
|
|
struct f_midi2 *midi2 = func_to_midi2(fn);
|
|
|
|
midi2->operation_mode = MIDI_OP_MODE_UNSET;
|
|
}
|
|
|
|
/*
|
|
* ALSA UMP ops: most of them are NOPs, only trigger for write is needed
|
|
*/
|
|
static int f_midi2_ump_open(struct snd_ump_endpoint *ump, int dir)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static void f_midi2_ump_close(struct snd_ump_endpoint *ump, int dir)
|
|
{
|
|
}
|
|
|
|
static void f_midi2_ump_trigger(struct snd_ump_endpoint *ump, int dir, int up)
|
|
{
|
|
struct f_midi2_ep *ep = ump->private_data;
|
|
struct f_midi2 *midi2 = ep->card;
|
|
|
|
if (up && dir == SNDRV_RAWMIDI_STREAM_OUTPUT) {
|
|
switch (midi2->operation_mode) {
|
|
case MIDI_OP_MODE_MIDI1:
|
|
process_midi1_transmit(midi2);
|
|
break;
|
|
case MIDI_OP_MODE_MIDI2:
|
|
process_ump_transmit(ep);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void f_midi2_ump_drain(struct snd_ump_endpoint *ump, int dir)
|
|
{
|
|
}
|
|
|
|
static const struct snd_ump_ops f_midi2_ump_ops = {
|
|
.open = f_midi2_ump_open,
|
|
.close = f_midi2_ump_close,
|
|
.trigger = f_midi2_ump_trigger,
|
|
.drain = f_midi2_ump_drain,
|
|
};
|
|
|
|
/*
|
|
* "Operation Mode" control element
|
|
*/
|
|
static int f_midi2_operation_mode_info(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_info *uinfo)
|
|
{
|
|
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
|
|
uinfo->count = 1;
|
|
uinfo->value.integer.min = MIDI_OP_MODE_UNSET;
|
|
uinfo->value.integer.max = MIDI_OP_MODE_MIDI2;
|
|
return 0;
|
|
}
|
|
|
|
static int f_midi2_operation_mode_get(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct f_midi2 *midi2 = snd_kcontrol_chip(kcontrol);
|
|
|
|
ucontrol->value.integer.value[0] = midi2->operation_mode;
|
|
return 0;
|
|
}
|
|
|
|
static const struct snd_kcontrol_new operation_mode_ctl = {
|
|
.iface = SNDRV_CTL_ELEM_IFACE_RAWMIDI,
|
|
.name = "Operation Mode",
|
|
.access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE,
|
|
.info = f_midi2_operation_mode_info,
|
|
.get = f_midi2_operation_mode_get,
|
|
};
|
|
|
|
/*
|
|
* ALSA UMP instance creation / deletion
|
|
*/
|
|
static void f_midi2_free_card(struct f_midi2 *midi2)
|
|
{
|
|
if (midi2->card) {
|
|
snd_card_free_when_closed(midi2->card);
|
|
midi2->card = NULL;
|
|
}
|
|
}
|
|
|
|
/* use a reverse direction for the gadget host */
|
|
static int reverse_dir(int dir)
|
|
{
|
|
if (!dir || dir == SNDRV_UMP_DIR_BIDIRECTION)
|
|
return dir;
|
|
return (dir == SNDRV_UMP_DIR_OUTPUT) ?
|
|
SNDRV_UMP_DIR_INPUT : SNDRV_UMP_DIR_OUTPUT;
|
|
}
|
|
|
|
static int f_midi2_create_card(struct f_midi2 *midi2)
|
|
{
|
|
struct snd_card *card;
|
|
struct snd_ump_endpoint *ump;
|
|
struct f_midi2_ep *ep;
|
|
int i, id, blk, err;
|
|
__be32 sw;
|
|
|
|
err = snd_card_new(&midi2->gadget->dev, -1, NULL, THIS_MODULE, 0,
|
|
&card);
|
|
if (err < 0)
|
|
return err;
|
|
midi2->card = card;
|
|
|
|
strcpy(card->driver, "f_midi2");
|
|
strcpy(card->shortname, "MIDI 2.0 Gadget");
|
|
strcpy(card->longname, "MIDI 2.0 Gadget");
|
|
|
|
id = 0;
|
|
for (i = 0; i < midi2->num_eps; i++) {
|
|
ep = &midi2->midi2_eps[i];
|
|
err = snd_ump_endpoint_new(card, "MIDI 2.0 Gadget", id,
|
|
1, 1, &ump);
|
|
if (err < 0)
|
|
goto error;
|
|
id++;
|
|
|
|
ep->ump = ump;
|
|
ump->no_process_stream = true;
|
|
ump->private_data = ep;
|
|
ump->ops = &f_midi2_ump_ops;
|
|
if (midi2->info.static_block)
|
|
ump->info.flags |= SNDRV_UMP_EP_INFO_STATIC_BLOCKS;
|
|
ump->info.protocol_caps = (ep->info.protocol_caps & 3) << 8;
|
|
ump->info.protocol = (ep->info.protocol & 3) << 8;
|
|
ump->info.version = 0x0101;
|
|
ump->info.family_id = ep->info.family;
|
|
ump->info.model_id = ep->info.model;
|
|
ump->info.manufacturer_id = ep->info.manufacturer & 0xffffff;
|
|
sw = cpu_to_be32(ep->info.sw_revision);
|
|
memcpy(ump->info.sw_revision, &sw, 4);
|
|
|
|
strscpy(ump->info.name, ump_ep_name(ep),
|
|
sizeof(ump->info.name));
|
|
strscpy(ump->info.product_id, ump_product_id(ep),
|
|
sizeof(ump->info.product_id));
|
|
strscpy(ump->core.name, ump->info.name, sizeof(ump->core.name));
|
|
|
|
for (blk = 0; blk < ep->num_blks; blk++) {
|
|
const struct f_midi2_block_info *b = &ep->blks[blk].info;
|
|
struct snd_ump_block *fb;
|
|
|
|
err = snd_ump_block_new(ump, blk,
|
|
reverse_dir(b->direction),
|
|
b->first_group, b->num_groups,
|
|
&ep->blks[blk].fb);
|
|
if (err < 0)
|
|
goto error;
|
|
fb = ep->blks[blk].fb;
|
|
fb->info.active = !!b->active;
|
|
fb->info.midi_ci_version = b->midi_ci_version;
|
|
fb->info.ui_hint = reverse_dir(b->ui_hint);
|
|
fb->info.sysex8_streams = b->sysex8_streams;
|
|
fb->info.flags |= b->is_midi1;
|
|
strscpy(fb->info.name, ump_fb_name(b),
|
|
sizeof(fb->info.name));
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < midi2->num_eps; i++) {
|
|
err = snd_ump_attach_legacy_rawmidi(midi2->midi2_eps[i].ump,
|
|
"Legacy MIDI", id);
|
|
if (err < 0)
|
|
goto error;
|
|
id++;
|
|
}
|
|
|
|
err = snd_ctl_add(card, snd_ctl_new1(&operation_mode_ctl, midi2));
|
|
if (err < 0)
|
|
goto error;
|
|
|
|
err = snd_card_register(card);
|
|
if (err < 0)
|
|
goto error;
|
|
|
|
return 0;
|
|
|
|
error:
|
|
f_midi2_free_card(midi2);
|
|
return err;
|
|
}
|
|
|
|
/*
|
|
* Creation of USB descriptors
|
|
*/
|
|
struct f_midi2_usb_config {
|
|
struct usb_descriptor_header **list;
|
|
unsigned int size;
|
|
unsigned int alloc;
|
|
|
|
/* MIDI 1.0 jacks */
|
|
unsigned char jack_in, jack_out, jack_id;
|
|
struct usb_midi_in_jack_descriptor jack_ins[MAX_CABLES];
|
|
struct usb_midi_out_jack_descriptor_1 jack_outs[MAX_CABLES];
|
|
};
|
|
|
|
static int append_config(struct f_midi2_usb_config *config, void *d)
|
|
{
|
|
unsigned int size;
|
|
void *buf;
|
|
|
|
if (config->size + 2 >= config->alloc) {
|
|
size = config->size + 16;
|
|
buf = krealloc(config->list, size * sizeof(void *), GFP_KERNEL);
|
|
if (!buf)
|
|
return -ENOMEM;
|
|
config->list = buf;
|
|
config->alloc = size;
|
|
}
|
|
|
|
config->list[config->size] = d;
|
|
config->size++;
|
|
config->list[config->size] = NULL;
|
|
return 0;
|
|
}
|
|
|
|
static int append_configs(struct f_midi2_usb_config *config, void **d)
|
|
{
|
|
int err;
|
|
|
|
for (; *d; d++) {
|
|
err = append_config(config, *d);
|
|
if (err)
|
|
return err;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int append_midi1_in_jack(struct f_midi2 *midi2,
|
|
struct f_midi2_usb_config *config,
|
|
struct midi1_cable_mapping *map,
|
|
unsigned int type)
|
|
{
|
|
struct usb_midi_in_jack_descriptor *jack =
|
|
&config->jack_ins[config->jack_in++];
|
|
int id = ++config->jack_id;
|
|
int err;
|
|
|
|
jack->bLength = 0x06;
|
|
jack->bDescriptorType = USB_DT_CS_INTERFACE;
|
|
jack->bDescriptorSubtype = USB_MS_MIDI_IN_JACK;
|
|
jack->bJackType = type;
|
|
jack->bJackID = id;
|
|
/* use the corresponding block name as jack name */
|
|
if (map->ep)
|
|
jack->iJack = map->ep->blks[map->block].string_id;
|
|
|
|
err = append_config(config, jack);
|
|
if (err < 0)
|
|
return err;
|
|
return id;
|
|
}
|
|
|
|
static int append_midi1_out_jack(struct f_midi2 *midi2,
|
|
struct f_midi2_usb_config *config,
|
|
struct midi1_cable_mapping *map,
|
|
unsigned int type, unsigned int source)
|
|
{
|
|
struct usb_midi_out_jack_descriptor_1 *jack =
|
|
&config->jack_outs[config->jack_out++];
|
|
int id = ++config->jack_id;
|
|
int err;
|
|
|
|
jack->bLength = 0x09;
|
|
jack->bDescriptorType = USB_DT_CS_INTERFACE;
|
|
jack->bDescriptorSubtype = USB_MS_MIDI_OUT_JACK;
|
|
jack->bJackType = type;
|
|
jack->bJackID = id;
|
|
jack->bNrInputPins = 1;
|
|
jack->pins[0].baSourceID = source;
|
|
jack->pins[0].baSourcePin = 0x01;
|
|
/* use the corresponding block name as jack name */
|
|
if (map->ep)
|
|
jack->iJack = map->ep->blks[map->block].string_id;
|
|
|
|
err = append_config(config, jack);
|
|
if (err < 0)
|
|
return err;
|
|
return id;
|
|
}
|
|
|
|
static int f_midi2_create_usb_configs(struct f_midi2 *midi2,
|
|
struct f_midi2_usb_config *config,
|
|
int speed)
|
|
{
|
|
void **midi1_in_eps, **midi1_out_eps;
|
|
int i, jack, total;
|
|
int err;
|
|
|
|
switch (speed) {
|
|
default:
|
|
case USB_SPEED_HIGH:
|
|
midi2_midi1_ep_out_desc.wMaxPacketSize = cpu_to_le16(512);
|
|
midi2_midi1_ep_in_desc.wMaxPacketSize = cpu_to_le16(512);
|
|
for (i = 0; i < midi2->num_eps; i++)
|
|
midi2_midi2_ep_out_desc[i].wMaxPacketSize =
|
|
cpu_to_le16(512);
|
|
fallthrough;
|
|
case USB_SPEED_FULL:
|
|
midi1_in_eps = midi2_midi1_ep_in_descs;
|
|
midi1_out_eps = midi2_midi1_ep_out_descs;
|
|
break;
|
|
case USB_SPEED_SUPER:
|
|
midi2_midi1_ep_out_desc.wMaxPacketSize = cpu_to_le16(1024);
|
|
midi2_midi1_ep_in_desc.wMaxPacketSize = cpu_to_le16(1024);
|
|
for (i = 0; i < midi2->num_eps; i++)
|
|
midi2_midi2_ep_out_desc[i].wMaxPacketSize =
|
|
cpu_to_le16(1024);
|
|
midi1_in_eps = midi2_midi1_ep_in_ss_descs;
|
|
midi1_out_eps = midi2_midi1_ep_out_ss_descs;
|
|
break;
|
|
}
|
|
|
|
err = append_configs(config, midi2_audio_descs);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
if (midi2->num_midi1_in && midi2->num_midi1_out)
|
|
midi2_midi1_if_desc.bNumEndpoints = 2;
|
|
else
|
|
midi2_midi1_if_desc.bNumEndpoints = 1;
|
|
|
|
err = append_configs(config, midi2_midi1_descs);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
total = USB_DT_MS_HEADER_SIZE;
|
|
if (midi2->num_midi1_out) {
|
|
midi2_midi1_ep_out_class_desc.bLength =
|
|
USB_DT_MS_ENDPOINT_SIZE(midi2->num_midi1_out);
|
|
total += midi2_midi1_ep_out_class_desc.bLength;
|
|
midi2_midi1_ep_out_class_desc.bNumEmbMIDIJack =
|
|
midi2->num_midi1_out;
|
|
total += midi2->num_midi1_out *
|
|
(USB_DT_MIDI_IN_SIZE + USB_DT_MIDI_OUT_SIZE(1));
|
|
for (i = 0; i < midi2->num_midi1_out; i++) {
|
|
jack = append_midi1_in_jack(midi2, config,
|
|
&midi2->in_cable_mapping[i],
|
|
USB_MS_EMBEDDED);
|
|
if (jack < 0)
|
|
return jack;
|
|
midi2_midi1_ep_out_class_desc.baAssocJackID[i] = jack;
|
|
jack = append_midi1_out_jack(midi2, config,
|
|
&midi2->in_cable_mapping[i],
|
|
USB_MS_EXTERNAL, jack);
|
|
if (jack < 0)
|
|
return jack;
|
|
}
|
|
}
|
|
|
|
if (midi2->num_midi1_in) {
|
|
midi2_midi1_ep_in_class_desc.bLength =
|
|
USB_DT_MS_ENDPOINT_SIZE(midi2->num_midi1_in);
|
|
total += midi2_midi1_ep_in_class_desc.bLength;
|
|
midi2_midi1_ep_in_class_desc.bNumEmbMIDIJack =
|
|
midi2->num_midi1_in;
|
|
total += midi2->num_midi1_in *
|
|
(USB_DT_MIDI_IN_SIZE + USB_DT_MIDI_OUT_SIZE(1));
|
|
for (i = 0; i < midi2->num_midi1_in; i++) {
|
|
jack = append_midi1_in_jack(midi2, config,
|
|
&midi2->out_cable_mapping[i],
|
|
USB_MS_EXTERNAL);
|
|
if (jack < 0)
|
|
return jack;
|
|
jack = append_midi1_out_jack(midi2, config,
|
|
&midi2->out_cable_mapping[i],
|
|
USB_MS_EMBEDDED, jack);
|
|
if (jack < 0)
|
|
return jack;
|
|
midi2_midi1_ep_in_class_desc.baAssocJackID[i] = jack;
|
|
}
|
|
}
|
|
|
|
midi2_midi1_class_desc.wTotalLength = cpu_to_le16(total);
|
|
|
|
if (midi2->num_midi1_out) {
|
|
err = append_configs(config, midi1_out_eps);
|
|
if (err < 0)
|
|
return err;
|
|
}
|
|
if (midi2->num_midi1_in) {
|
|
err = append_configs(config, midi1_in_eps);
|
|
if (err < 0)
|
|
return err;
|
|
}
|
|
|
|
err = append_configs(config, midi2_midi2_descs);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
for (i = 0; i < midi2->num_eps; i++) {
|
|
err = append_config(config, &midi2_midi2_ep_out_desc[i]);
|
|
if (err < 0)
|
|
return err;
|
|
if (speed == USB_SPEED_SUPER || speed == USB_SPEED_SUPER_PLUS) {
|
|
err = append_config(config, &midi2_midi2_ep_out_ss_comp_desc);
|
|
if (err < 0)
|
|
return err;
|
|
}
|
|
err = append_config(config, &midi2_midi2_ep_out_class_desc[i]);
|
|
if (err < 0)
|
|
return err;
|
|
err = append_config(config, &midi2_midi2_ep_in_desc[i]);
|
|
if (err < 0)
|
|
return err;
|
|
if (speed == USB_SPEED_SUPER || speed == USB_SPEED_SUPER_PLUS) {
|
|
err = append_config(config, &midi2_midi2_ep_in_ss_comp_desc);
|
|
if (err < 0)
|
|
return err;
|
|
}
|
|
err = append_config(config, &midi2_midi2_ep_in_class_desc[i]);
|
|
if (err < 0)
|
|
return err;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void f_midi2_free_usb_configs(struct f_midi2_usb_config *config)
|
|
{
|
|
kfree(config->list);
|
|
memset(config, 0, sizeof(*config));
|
|
}
|
|
|
|
/* as we use the static descriptors for simplicity, serialize bind call */
|
|
static DEFINE_MUTEX(f_midi2_desc_mutex);
|
|
|
|
/* fill MIDI2 EP class-specific descriptor */
|
|
static void fill_midi2_class_desc(struct f_midi2_ep *ep,
|
|
struct usb_ms20_endpoint_descriptor_32 *cdesc)
|
|
{
|
|
int blk;
|
|
|
|
cdesc->bLength = USB_DT_MS20_ENDPOINT_SIZE(ep->num_blks);
|
|
cdesc->bDescriptorType = USB_DT_CS_ENDPOINT;
|
|
cdesc->bDescriptorSubtype = USB_MS_GENERAL_2_0;
|
|
cdesc->bNumGrpTrmBlock = ep->num_blks;
|
|
for (blk = 0; blk < ep->num_blks; blk++)
|
|
cdesc->baAssoGrpTrmBlkID[blk] = ep->blks[blk].gtb_id;
|
|
}
|
|
|
|
/* initialize MIDI2 EP-in */
|
|
static int f_midi2_init_midi2_ep_in(struct f_midi2 *midi2, int index)
|
|
{
|
|
struct f_midi2_ep *ep = &midi2->midi2_eps[index];
|
|
struct usb_endpoint_descriptor *desc = &midi2_midi2_ep_in_desc[index];
|
|
|
|
desc->bLength = USB_DT_ENDPOINT_SIZE;
|
|
desc->bDescriptorType = USB_DT_ENDPOINT;
|
|
desc->bEndpointAddress = USB_DIR_IN;
|
|
desc->bmAttributes = USB_ENDPOINT_XFER_INT;
|
|
desc->wMaxPacketSize = cpu_to_le16(EP_MAX_PACKET_INT);
|
|
desc->bInterval = 1;
|
|
|
|
fill_midi2_class_desc(ep, &midi2_midi2_ep_in_class_desc[index]);
|
|
|
|
return f_midi2_init_ep(midi2, ep, &ep->ep_in, desc,
|
|
f_midi2_ep_in_complete);
|
|
}
|
|
|
|
/* initialize MIDI2 EP-out */
|
|
static int f_midi2_init_midi2_ep_out(struct f_midi2 *midi2, int index)
|
|
{
|
|
struct f_midi2_ep *ep = &midi2->midi2_eps[index];
|
|
struct usb_endpoint_descriptor *desc = &midi2_midi2_ep_out_desc[index];
|
|
|
|
desc->bLength = USB_DT_ENDPOINT_SIZE;
|
|
desc->bDescriptorType = USB_DT_ENDPOINT;
|
|
desc->bEndpointAddress = USB_DIR_OUT;
|
|
desc->bmAttributes = USB_ENDPOINT_XFER_BULK;
|
|
|
|
fill_midi2_class_desc(ep, &midi2_midi2_ep_out_class_desc[index]);
|
|
|
|
return f_midi2_init_ep(midi2, ep, &ep->ep_out, desc,
|
|
f_midi2_ep_out_complete);
|
|
}
|
|
|
|
/* gadget function bind callback */
|
|
static int f_midi2_bind(struct usb_configuration *c, struct usb_function *f)
|
|
{
|
|
struct usb_composite_dev *cdev = c->cdev;
|
|
struct f_midi2 *midi2 = func_to_midi2(f);
|
|
struct f_midi2_ep *ep;
|
|
struct f_midi2_usb_config config = {};
|
|
struct usb_gadget_strings string_fn = {
|
|
.language = 0x0409, /* en-us */
|
|
.strings = midi2->string_defs,
|
|
};
|
|
struct usb_gadget_strings *strings[] = {
|
|
&string_fn,
|
|
NULL,
|
|
};
|
|
int i, blk, status;
|
|
|
|
midi2->gadget = cdev->gadget;
|
|
midi2->operation_mode = MIDI_OP_MODE_UNSET;
|
|
|
|
status = f_midi2_create_card(midi2);
|
|
if (status < 0)
|
|
goto fail_register;
|
|
|
|
/* maybe allocate device-global string ID */
|
|
midi2->strings = usb_gstrings_attach(c->cdev, strings,
|
|
midi2->total_blocks + 1);
|
|
if (IS_ERR(midi2->strings)) {
|
|
status = PTR_ERR(midi2->strings);
|
|
goto fail_string;
|
|
}
|
|
|
|
mutex_lock(&f_midi2_desc_mutex);
|
|
midi2_midi1_if_desc.iInterface = midi2->strings[STR_IFACE].id;
|
|
midi2_midi2_if_desc.iInterface = midi2->strings[STR_IFACE].id;
|
|
for (i = 0; i < midi2->num_eps; i++) {
|
|
ep = &midi2->midi2_eps[i];
|
|
for (blk = 0; blk < ep->num_blks; blk++)
|
|
ep->blks[blk].string_id =
|
|
midi2->strings[gtb_to_str_id(ep->blks[blk].gtb_id)].id;
|
|
}
|
|
|
|
midi2_midi2_if_desc.bNumEndpoints = midi2->num_eps * 2;
|
|
|
|
/* audio interface */
|
|
status = usb_interface_id(c, f);
|
|
if (status < 0)
|
|
goto fail;
|
|
midi2_audio_if_desc.bInterfaceNumber = status;
|
|
|
|
/* MIDI streaming */
|
|
status = usb_interface_id(c, f);
|
|
if (status < 0)
|
|
goto fail;
|
|
midi2->midi_if = status;
|
|
midi2_midi1_if_desc.bInterfaceNumber = status;
|
|
midi2_midi2_if_desc.bInterfaceNumber = status;
|
|
midi2_audio_class_desc.baInterfaceNr[0] = status;
|
|
|
|
/* allocate instance-specific endpoints */
|
|
if (midi2->midi2_eps[0].blks[0].info.direction != SNDRV_UMP_DIR_OUTPUT) {
|
|
status = f_midi2_init_ep(midi2, NULL, &midi2->midi1_ep_in,
|
|
&midi2_midi1_ep_in_desc,
|
|
f_midi2_midi1_ep_in_complete);
|
|
if (status)
|
|
goto fail;
|
|
}
|
|
|
|
if (midi2->midi2_eps[0].blks[0].info.direction != SNDRV_UMP_DIR_INPUT) {
|
|
status = f_midi2_init_ep(midi2, NULL, &midi2->midi1_ep_out,
|
|
&midi2_midi1_ep_out_desc,
|
|
f_midi2_midi1_ep_out_complete);
|
|
if (status)
|
|
goto fail;
|
|
}
|
|
|
|
for (i = 0; i < midi2->num_eps; i++) {
|
|
status = f_midi2_init_midi2_ep_in(midi2, i);
|
|
if (status)
|
|
goto fail;
|
|
status = f_midi2_init_midi2_ep_out(midi2, i);
|
|
if (status)
|
|
goto fail;
|
|
}
|
|
|
|
status = f_midi2_create_usb_configs(midi2, &config, USB_SPEED_FULL);
|
|
if (status < 0)
|
|
goto fail;
|
|
f->fs_descriptors = usb_copy_descriptors(config.list);
|
|
if (!f->fs_descriptors) {
|
|
status = -ENOMEM;
|
|
goto fail;
|
|
}
|
|
f_midi2_free_usb_configs(&config);
|
|
|
|
status = f_midi2_create_usb_configs(midi2, &config, USB_SPEED_HIGH);
|
|
if (status < 0)
|
|
goto fail;
|
|
f->hs_descriptors = usb_copy_descriptors(config.list);
|
|
if (!f->hs_descriptors) {
|
|
status = -ENOMEM;
|
|
goto fail;
|
|
}
|
|
f_midi2_free_usb_configs(&config);
|
|
|
|
status = f_midi2_create_usb_configs(midi2, &config, USB_SPEED_SUPER);
|
|
if (status < 0)
|
|
goto fail;
|
|
f->ss_descriptors = usb_copy_descriptors(config.list);
|
|
if (!f->ss_descriptors) {
|
|
status = -ENOMEM;
|
|
goto fail;
|
|
}
|
|
f_midi2_free_usb_configs(&config);
|
|
|
|
mutex_unlock(&f_midi2_desc_mutex);
|
|
return 0;
|
|
|
|
fail:
|
|
f_midi2_free_usb_configs(&config);
|
|
mutex_unlock(&f_midi2_desc_mutex);
|
|
usb_free_all_descriptors(f);
|
|
fail_string:
|
|
f_midi2_free_card(midi2);
|
|
fail_register:
|
|
ERROR(midi2, "%s: can't bind, err %d\n", f->name, status);
|
|
return status;
|
|
}
|
|
|
|
/* gadget function unbind callback */
|
|
static void f_midi2_unbind(struct usb_configuration *c, struct usb_function *f)
|
|
{
|
|
struct f_midi2 *midi2 = func_to_midi2(f);
|
|
int i;
|
|
|
|
f_midi2_free_card(midi2);
|
|
|
|
f_midi2_free_ep(&midi2->midi1_ep_in);
|
|
f_midi2_free_ep(&midi2->midi1_ep_out);
|
|
for (i = 0; i < midi2->num_eps; i++) {
|
|
f_midi2_free_ep(&midi2->midi2_eps[i].ep_in);
|
|
f_midi2_free_ep(&midi2->midi2_eps[i].ep_out);
|
|
}
|
|
|
|
usb_free_all_descriptors(f);
|
|
}
|
|
|
|
/*
|
|
* ConfigFS interface
|
|
*/
|
|
|
|
/* type conversion helpers */
|
|
static inline struct f_midi2_opts *to_f_midi2_opts(struct config_item *item)
|
|
{
|
|
return container_of(to_config_group(item), struct f_midi2_opts,
|
|
func_inst.group);
|
|
}
|
|
|
|
static inline struct f_midi2_ep_opts *
|
|
to_f_midi2_ep_opts(struct config_item *item)
|
|
{
|
|
return container_of(to_config_group(item), struct f_midi2_ep_opts,
|
|
group);
|
|
}
|
|
|
|
static inline struct f_midi2_block_opts *
|
|
to_f_midi2_block_opts(struct config_item *item)
|
|
{
|
|
return container_of(to_config_group(item), struct f_midi2_block_opts,
|
|
group);
|
|
}
|
|
|
|
/* trim the string to be usable for EP and FB name strings */
|
|
static void make_name_string(char *s)
|
|
{
|
|
char *p;
|
|
|
|
p = strchr(s, '\n');
|
|
if (p)
|
|
*p = 0;
|
|
|
|
p = s + strlen(s);
|
|
for (; p > s && isspace(*p); p--)
|
|
*p = 0;
|
|
}
|
|
|
|
/* configfs helpers: generic show/store for unisnged int */
|
|
static ssize_t f_midi2_opts_uint_show(struct f_midi2_opts *opts,
|
|
u32 val, const char *format, char *page)
|
|
{
|
|
int result;
|
|
|
|
mutex_lock(&opts->lock);
|
|
result = sprintf(page, format, val);
|
|
mutex_unlock(&opts->lock);
|
|
return result;
|
|
}
|
|
|
|
static ssize_t f_midi2_opts_uint_store(struct f_midi2_opts *opts,
|
|
u32 *valp, u32 minval, u32 maxval,
|
|
const char *page, size_t len)
|
|
{
|
|
int ret;
|
|
u32 val;
|
|
|
|
mutex_lock(&opts->lock);
|
|
if (opts->refcnt) {
|
|
ret = -EBUSY;
|
|
goto end;
|
|
}
|
|
|
|
ret = kstrtou32(page, 0, &val);
|
|
if (ret)
|
|
goto end;
|
|
if (val < minval || val > maxval) {
|
|
ret = -EINVAL;
|
|
goto end;
|
|
}
|
|
|
|
*valp = val;
|
|
ret = len;
|
|
|
|
end:
|
|
mutex_unlock(&opts->lock);
|
|
return ret;
|
|
}
|
|
|
|
/* generic store for bool */
|
|
static ssize_t f_midi2_opts_bool_store(struct f_midi2_opts *opts,
|
|
bool *valp, const char *page, size_t len)
|
|
{
|
|
int ret;
|
|
bool val;
|
|
|
|
mutex_lock(&opts->lock);
|
|
if (opts->refcnt) {
|
|
ret = -EBUSY;
|
|
goto end;
|
|
}
|
|
|
|
ret = kstrtobool(page, &val);
|
|
if (ret)
|
|
goto end;
|
|
*valp = val;
|
|
ret = len;
|
|
|
|
end:
|
|
mutex_unlock(&opts->lock);
|
|
return ret;
|
|
}
|
|
|
|
/* generic show/store for string */
|
|
static ssize_t f_midi2_opts_str_show(struct f_midi2_opts *opts,
|
|
const char *str, char *page)
|
|
{
|
|
int result = 0;
|
|
|
|
mutex_lock(&opts->lock);
|
|
if (str)
|
|
result = scnprintf(page, PAGE_SIZE, "%s\n", str);
|
|
mutex_unlock(&opts->lock);
|
|
return result;
|
|
}
|
|
|
|
static ssize_t f_midi2_opts_str_store(struct f_midi2_opts *opts,
|
|
const char **strp, size_t maxlen,
|
|
const char *page, size_t len)
|
|
{
|
|
char *c;
|
|
int ret;
|
|
|
|
mutex_lock(&opts->lock);
|
|
if (opts->refcnt) {
|
|
ret = -EBUSY;
|
|
goto end;
|
|
}
|
|
|
|
c = kstrndup(page, min(len, maxlen), GFP_KERNEL);
|
|
if (!c) {
|
|
ret = -ENOMEM;
|
|
goto end;
|
|
}
|
|
|
|
kfree(*strp);
|
|
make_name_string(c);
|
|
*strp = c;
|
|
ret = len;
|
|
|
|
end:
|
|
mutex_unlock(&opts->lock);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Definitions for UMP Block config
|
|
*/
|
|
|
|
/* define an uint option for block */
|
|
#define F_MIDI2_BLOCK_OPT(name, format, minval, maxval) \
|
|
static ssize_t f_midi2_block_opts_##name##_show(struct config_item *item,\
|
|
char *page) \
|
|
{ \
|
|
struct f_midi2_block_opts *opts = to_f_midi2_block_opts(item); \
|
|
return f_midi2_opts_uint_show(opts->ep->opts, opts->info.name, \
|
|
format "\n", page); \
|
|
} \
|
|
\
|
|
static ssize_t f_midi2_block_opts_##name##_store(struct config_item *item,\
|
|
const char *page, size_t len) \
|
|
{ \
|
|
struct f_midi2_block_opts *opts = to_f_midi2_block_opts(item); \
|
|
return f_midi2_opts_uint_store(opts->ep->opts, &opts->info.name,\
|
|
minval, maxval, page, len); \
|
|
} \
|
|
\
|
|
CONFIGFS_ATTR(f_midi2_block_opts_, name)
|
|
|
|
/* define a boolean option for block */
|
|
#define F_MIDI2_BLOCK_BOOL_OPT(name) \
|
|
static ssize_t f_midi2_block_opts_##name##_show(struct config_item *item,\
|
|
char *page) \
|
|
{ \
|
|
struct f_midi2_block_opts *opts = to_f_midi2_block_opts(item); \
|
|
return f_midi2_opts_uint_show(opts->ep->opts, opts->info.name, \
|
|
"%u\n", page); \
|
|
} \
|
|
\
|
|
static ssize_t f_midi2_block_opts_##name##_store(struct config_item *item,\
|
|
const char *page, size_t len) \
|
|
{ \
|
|
struct f_midi2_block_opts *opts = to_f_midi2_block_opts(item); \
|
|
return f_midi2_opts_bool_store(opts->ep->opts, &opts->info.name,\
|
|
page, len); \
|
|
} \
|
|
\
|
|
CONFIGFS_ATTR(f_midi2_block_opts_, name)
|
|
|
|
F_MIDI2_BLOCK_OPT(direction, "0x%x", 1, 3);
|
|
F_MIDI2_BLOCK_OPT(first_group, "0x%x", 0, 15);
|
|
F_MIDI2_BLOCK_OPT(num_groups, "0x%x", 1, 16);
|
|
F_MIDI2_BLOCK_OPT(midi1_first_group, "0x%x", 0, 15);
|
|
F_MIDI2_BLOCK_OPT(midi1_num_groups, "0x%x", 0, 16);
|
|
F_MIDI2_BLOCK_OPT(ui_hint, "0x%x", 0, 3);
|
|
F_MIDI2_BLOCK_OPT(midi_ci_version, "%u", 0, 1);
|
|
F_MIDI2_BLOCK_OPT(sysex8_streams, "%u", 0, 255);
|
|
F_MIDI2_BLOCK_OPT(is_midi1, "%u", 0, 2);
|
|
F_MIDI2_BLOCK_BOOL_OPT(active);
|
|
|
|
static ssize_t f_midi2_block_opts_name_show(struct config_item *item,
|
|
char *page)
|
|
{
|
|
struct f_midi2_block_opts *opts = to_f_midi2_block_opts(item);
|
|
|
|
return f_midi2_opts_str_show(opts->ep->opts, opts->info.name, page);
|
|
}
|
|
|
|
static ssize_t f_midi2_block_opts_name_store(struct config_item *item,
|
|
const char *page, size_t len)
|
|
{
|
|
struct f_midi2_block_opts *opts = to_f_midi2_block_opts(item);
|
|
|
|
return f_midi2_opts_str_store(opts->ep->opts, &opts->info.name, 128,
|
|
page, len);
|
|
}
|
|
|
|
CONFIGFS_ATTR(f_midi2_block_opts_, name);
|
|
|
|
static struct configfs_attribute *f_midi2_block_attrs[] = {
|
|
&f_midi2_block_opts_attr_direction,
|
|
&f_midi2_block_opts_attr_first_group,
|
|
&f_midi2_block_opts_attr_num_groups,
|
|
&f_midi2_block_opts_attr_midi1_first_group,
|
|
&f_midi2_block_opts_attr_midi1_num_groups,
|
|
&f_midi2_block_opts_attr_ui_hint,
|
|
&f_midi2_block_opts_attr_midi_ci_version,
|
|
&f_midi2_block_opts_attr_sysex8_streams,
|
|
&f_midi2_block_opts_attr_is_midi1,
|
|
&f_midi2_block_opts_attr_active,
|
|
&f_midi2_block_opts_attr_name,
|
|
NULL,
|
|
};
|
|
|
|
static void f_midi2_block_opts_release(struct config_item *item)
|
|
{
|
|
struct f_midi2_block_opts *opts = to_f_midi2_block_opts(item);
|
|
|
|
kfree(opts->info.name);
|
|
kfree(opts);
|
|
}
|
|
|
|
static struct configfs_item_operations f_midi2_block_item_ops = {
|
|
.release = f_midi2_block_opts_release,
|
|
};
|
|
|
|
static const struct config_item_type f_midi2_block_type = {
|
|
.ct_item_ops = &f_midi2_block_item_ops,
|
|
.ct_attrs = f_midi2_block_attrs,
|
|
.ct_owner = THIS_MODULE,
|
|
};
|
|
|
|
/* create a f_midi2_block_opts instance for the given block number */
|
|
static int f_midi2_block_opts_create(struct f_midi2_ep_opts *ep_opts,
|
|
unsigned int blk,
|
|
struct f_midi2_block_opts **block_p)
|
|
{
|
|
struct f_midi2_block_opts *block_opts;
|
|
int ret = 0;
|
|
|
|
mutex_lock(&ep_opts->opts->lock);
|
|
if (ep_opts->opts->refcnt || ep_opts->blks[blk]) {
|
|
ret = -EBUSY;
|
|
goto out;
|
|
}
|
|
|
|
block_opts = kzalloc(sizeof(*block_opts), GFP_KERNEL);
|
|
if (!block_opts) {
|
|
ret = -ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
block_opts->ep = ep_opts;
|
|
block_opts->id = blk;
|
|
|
|
/* set up the default values */
|
|
block_opts->info.direction = SNDRV_UMP_DIR_BIDIRECTION;
|
|
block_opts->info.first_group = 0;
|
|
block_opts->info.num_groups = 1;
|
|
block_opts->info.ui_hint = SNDRV_UMP_BLOCK_UI_HINT_BOTH;
|
|
block_opts->info.active = 1;
|
|
|
|
ep_opts->blks[blk] = block_opts;
|
|
*block_p = block_opts;
|
|
|
|
out:
|
|
mutex_unlock(&ep_opts->opts->lock);
|
|
return ret;
|
|
}
|
|
|
|
/* make_group callback for a block */
|
|
static struct config_group *
|
|
f_midi2_opts_block_make(struct config_group *group, const char *name)
|
|
{
|
|
struct f_midi2_ep_opts *ep_opts;
|
|
struct f_midi2_block_opts *block_opts;
|
|
unsigned int blk;
|
|
int ret;
|
|
|
|
if (strncmp(name, "block.", 6))
|
|
return ERR_PTR(-EINVAL);
|
|
ret = kstrtouint(name + 6, 10, &blk);
|
|
if (ret)
|
|
return ERR_PTR(ret);
|
|
|
|
ep_opts = to_f_midi2_ep_opts(&group->cg_item);
|
|
|
|
if (blk >= SNDRV_UMP_MAX_BLOCKS)
|
|
return ERR_PTR(-EINVAL);
|
|
if (ep_opts->blks[blk])
|
|
return ERR_PTR(-EBUSY);
|
|
ret = f_midi2_block_opts_create(ep_opts, blk, &block_opts);
|
|
if (ret)
|
|
return ERR_PTR(ret);
|
|
|
|
config_group_init_type_name(&block_opts->group, name,
|
|
&f_midi2_block_type);
|
|
return &block_opts->group;
|
|
}
|
|
|
|
/* drop_item callback for a block */
|
|
static void
|
|
f_midi2_opts_block_drop(struct config_group *group, struct config_item *item)
|
|
{
|
|
struct f_midi2_block_opts *block_opts = to_f_midi2_block_opts(item);
|
|
|
|
mutex_lock(&block_opts->ep->opts->lock);
|
|
block_opts->ep->blks[block_opts->id] = NULL;
|
|
mutex_unlock(&block_opts->ep->opts->lock);
|
|
config_item_put(item);
|
|
}
|
|
|
|
/*
|
|
* Definitions for UMP Endpoint config
|
|
*/
|
|
|
|
/* define an uint option for EP */
|
|
#define F_MIDI2_EP_OPT(name, format, minval, maxval) \
|
|
static ssize_t f_midi2_ep_opts_##name##_show(struct config_item *item, \
|
|
char *page) \
|
|
{ \
|
|
struct f_midi2_ep_opts *opts = to_f_midi2_ep_opts(item); \
|
|
return f_midi2_opts_uint_show(opts->opts, opts->info.name, \
|
|
format "\n", page); \
|
|
} \
|
|
\
|
|
static ssize_t f_midi2_ep_opts_##name##_store(struct config_item *item, \
|
|
const char *page, size_t len)\
|
|
{ \
|
|
struct f_midi2_ep_opts *opts = to_f_midi2_ep_opts(item); \
|
|
return f_midi2_opts_uint_store(opts->opts, &opts->info.name, \
|
|
minval, maxval, page, len); \
|
|
} \
|
|
\
|
|
CONFIGFS_ATTR(f_midi2_ep_opts_, name)
|
|
|
|
/* define a string option for EP */
|
|
#define F_MIDI2_EP_STR_OPT(name, maxlen) \
|
|
static ssize_t f_midi2_ep_opts_##name##_show(struct config_item *item, \
|
|
char *page) \
|
|
{ \
|
|
struct f_midi2_ep_opts *opts = to_f_midi2_ep_opts(item); \
|
|
return f_midi2_opts_str_show(opts->opts, opts->info.name, page);\
|
|
} \
|
|
\
|
|
static ssize_t f_midi2_ep_opts_##name##_store(struct config_item *item, \
|
|
const char *page, size_t len) \
|
|
{ \
|
|
struct f_midi2_ep_opts *opts = to_f_midi2_ep_opts(item); \
|
|
return f_midi2_opts_str_store(opts->opts, &opts->info.name, maxlen,\
|
|
page, len); \
|
|
} \
|
|
\
|
|
CONFIGFS_ATTR(f_midi2_ep_opts_, name)
|
|
|
|
F_MIDI2_EP_OPT(protocol, "0x%x", 1, 2);
|
|
F_MIDI2_EP_OPT(protocol_caps, "0x%x", 1, 3);
|
|
F_MIDI2_EP_OPT(manufacturer, "0x%x", 0, 0xffffff);
|
|
F_MIDI2_EP_OPT(family, "0x%x", 0, 0xffff);
|
|
F_MIDI2_EP_OPT(model, "0x%x", 0, 0xffff);
|
|
F_MIDI2_EP_OPT(sw_revision, "0x%x", 0, 0xffffffff);
|
|
F_MIDI2_EP_STR_OPT(ep_name, 128);
|
|
F_MIDI2_EP_STR_OPT(product_id, 128);
|
|
|
|
static struct configfs_attribute *f_midi2_ep_attrs[] = {
|
|
&f_midi2_ep_opts_attr_protocol,
|
|
&f_midi2_ep_opts_attr_protocol_caps,
|
|
&f_midi2_ep_opts_attr_ep_name,
|
|
&f_midi2_ep_opts_attr_product_id,
|
|
&f_midi2_ep_opts_attr_manufacturer,
|
|
&f_midi2_ep_opts_attr_family,
|
|
&f_midi2_ep_opts_attr_model,
|
|
&f_midi2_ep_opts_attr_sw_revision,
|
|
NULL,
|
|
};
|
|
|
|
static void f_midi2_ep_opts_release(struct config_item *item)
|
|
{
|
|
struct f_midi2_ep_opts *opts = to_f_midi2_ep_opts(item);
|
|
|
|
kfree(opts->info.ep_name);
|
|
kfree(opts->info.product_id);
|
|
kfree(opts);
|
|
}
|
|
|
|
static struct configfs_item_operations f_midi2_ep_item_ops = {
|
|
.release = f_midi2_ep_opts_release,
|
|
};
|
|
|
|
static struct configfs_group_operations f_midi2_ep_group_ops = {
|
|
.make_group = f_midi2_opts_block_make,
|
|
.drop_item = f_midi2_opts_block_drop,
|
|
};
|
|
|
|
static const struct config_item_type f_midi2_ep_type = {
|
|
.ct_item_ops = &f_midi2_ep_item_ops,
|
|
.ct_group_ops = &f_midi2_ep_group_ops,
|
|
.ct_attrs = f_midi2_ep_attrs,
|
|
.ct_owner = THIS_MODULE,
|
|
};
|
|
|
|
/* create a f_midi2_ep_opts instance */
|
|
static int f_midi2_ep_opts_create(struct f_midi2_opts *opts,
|
|
unsigned int index,
|
|
struct f_midi2_ep_opts **ep_p)
|
|
{
|
|
struct f_midi2_ep_opts *ep_opts;
|
|
|
|
ep_opts = kzalloc(sizeof(*ep_opts), GFP_KERNEL);
|
|
if (!ep_opts)
|
|
return -ENOMEM;
|
|
|
|
ep_opts->opts = opts;
|
|
ep_opts->index = index;
|
|
|
|
/* set up the default values */
|
|
ep_opts->info.protocol = 2;
|
|
ep_opts->info.protocol_caps = 3;
|
|
|
|
opts->eps[index] = ep_opts;
|
|
*ep_p = ep_opts;
|
|
return 0;
|
|
}
|
|
|
|
/* make_group callback for an EP */
|
|
static struct config_group *
|
|
f_midi2_opts_ep_make(struct config_group *group, const char *name)
|
|
{
|
|
struct f_midi2_opts *opts;
|
|
struct f_midi2_ep_opts *ep_opts;
|
|
unsigned int index;
|
|
int ret;
|
|
|
|
if (strncmp(name, "ep.", 3))
|
|
return ERR_PTR(-EINVAL);
|
|
ret = kstrtouint(name + 3, 10, &index);
|
|
if (ret)
|
|
return ERR_PTR(ret);
|
|
|
|
opts = to_f_midi2_opts(&group->cg_item);
|
|
if (index >= MAX_UMP_EPS)
|
|
return ERR_PTR(-EINVAL);
|
|
if (opts->eps[index])
|
|
return ERR_PTR(-EBUSY);
|
|
ret = f_midi2_ep_opts_create(opts, index, &ep_opts);
|
|
if (ret)
|
|
return ERR_PTR(ret);
|
|
|
|
config_group_init_type_name(&ep_opts->group, name, &f_midi2_ep_type);
|
|
return &ep_opts->group;
|
|
}
|
|
|
|
/* drop_item callback for an EP */
|
|
static void
|
|
f_midi2_opts_ep_drop(struct config_group *group, struct config_item *item)
|
|
{
|
|
struct f_midi2_ep_opts *ep_opts = to_f_midi2_ep_opts(item);
|
|
|
|
mutex_lock(&ep_opts->opts->lock);
|
|
ep_opts->opts->eps[ep_opts->index] = NULL;
|
|
mutex_unlock(&ep_opts->opts->lock);
|
|
config_item_put(item);
|
|
}
|
|
|
|
/*
|
|
* Definitions for card config
|
|
*/
|
|
|
|
/* define a bool option for card */
|
|
#define F_MIDI2_BOOL_OPT(name) \
|
|
static ssize_t f_midi2_opts_##name##_show(struct config_item *item, \
|
|
char *page) \
|
|
{ \
|
|
struct f_midi2_opts *opts = to_f_midi2_opts(item); \
|
|
return f_midi2_opts_uint_show(opts, opts->info.name, \
|
|
"%u\n", page); \
|
|
} \
|
|
\
|
|
static ssize_t f_midi2_opts_##name##_store(struct config_item *item, \
|
|
const char *page, size_t len) \
|
|
{ \
|
|
struct f_midi2_opts *opts = to_f_midi2_opts(item); \
|
|
return f_midi2_opts_bool_store(opts, &opts->info.name, \
|
|
page, len); \
|
|
} \
|
|
\
|
|
CONFIGFS_ATTR(f_midi2_opts_, name)
|
|
|
|
F_MIDI2_BOOL_OPT(process_ump);
|
|
F_MIDI2_BOOL_OPT(static_block);
|
|
|
|
static ssize_t f_midi2_opts_iface_name_show(struct config_item *item,
|
|
char *page)
|
|
{
|
|
struct f_midi2_opts *opts = to_f_midi2_opts(item);
|
|
|
|
return f_midi2_opts_str_show(opts, opts->info.iface_name, page);
|
|
}
|
|
|
|
static ssize_t f_midi2_opts_iface_name_store(struct config_item *item,
|
|
const char *page, size_t len)
|
|
{
|
|
struct f_midi2_opts *opts = to_f_midi2_opts(item);
|
|
|
|
return f_midi2_opts_str_store(opts, &opts->info.iface_name, 128,
|
|
page, len);
|
|
}
|
|
|
|
CONFIGFS_ATTR(f_midi2_opts_, iface_name);
|
|
|
|
static struct configfs_attribute *f_midi2_attrs[] = {
|
|
&f_midi2_opts_attr_process_ump,
|
|
&f_midi2_opts_attr_static_block,
|
|
&f_midi2_opts_attr_iface_name,
|
|
NULL
|
|
};
|
|
|
|
static void f_midi2_opts_release(struct config_item *item)
|
|
{
|
|
struct f_midi2_opts *opts = to_f_midi2_opts(item);
|
|
|
|
usb_put_function_instance(&opts->func_inst);
|
|
}
|
|
|
|
static struct configfs_item_operations f_midi2_item_ops = {
|
|
.release = f_midi2_opts_release,
|
|
};
|
|
|
|
static struct configfs_group_operations f_midi2_group_ops = {
|
|
.make_group = f_midi2_opts_ep_make,
|
|
.drop_item = f_midi2_opts_ep_drop,
|
|
};
|
|
|
|
static const struct config_item_type f_midi2_func_type = {
|
|
.ct_item_ops = &f_midi2_item_ops,
|
|
.ct_group_ops = &f_midi2_group_ops,
|
|
.ct_attrs = f_midi2_attrs,
|
|
.ct_owner = THIS_MODULE,
|
|
};
|
|
|
|
static void f_midi2_free_inst(struct usb_function_instance *f)
|
|
{
|
|
struct f_midi2_opts *opts;
|
|
|
|
opts = container_of(f, struct f_midi2_opts, func_inst);
|
|
|
|
kfree(opts->info.iface_name);
|
|
kfree(opts);
|
|
}
|
|
|
|
/* gadget alloc_inst */
|
|
static struct usb_function_instance *f_midi2_alloc_inst(void)
|
|
{
|
|
struct f_midi2_opts *opts;
|
|
struct f_midi2_ep_opts *ep_opts;
|
|
struct f_midi2_block_opts *block_opts;
|
|
int ret;
|
|
|
|
opts = kzalloc(sizeof(*opts), GFP_KERNEL);
|
|
if (!opts)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
mutex_init(&opts->lock);
|
|
opts->func_inst.free_func_inst = f_midi2_free_inst;
|
|
opts->info.process_ump = true;
|
|
opts->info.static_block = true;
|
|
opts->info.num_reqs = 32;
|
|
opts->info.req_buf_size = 512;
|
|
|
|
/* create the default ep */
|
|
ret = f_midi2_ep_opts_create(opts, 0, &ep_opts);
|
|
if (ret) {
|
|
kfree(opts);
|
|
return ERR_PTR(ret);
|
|
}
|
|
|
|
/* create the default block */
|
|
ret = f_midi2_block_opts_create(ep_opts, 0, &block_opts);
|
|
if (ret) {
|
|
kfree(ep_opts);
|
|
kfree(opts);
|
|
return ERR_PTR(ret);
|
|
}
|
|
|
|
/* set up the default MIDI1 (that is mandatory) */
|
|
block_opts->info.midi1_num_groups = 1;
|
|
|
|
config_group_init_type_name(&opts->func_inst.group, "",
|
|
&f_midi2_func_type);
|
|
|
|
config_group_init_type_name(&ep_opts->group, "ep.0",
|
|
&f_midi2_ep_type);
|
|
configfs_add_default_group(&ep_opts->group, &opts->func_inst.group);
|
|
|
|
config_group_init_type_name(&block_opts->group, "block.0",
|
|
&f_midi2_block_type);
|
|
configfs_add_default_group(&block_opts->group, &ep_opts->group);
|
|
|
|
return &opts->func_inst;
|
|
}
|
|
|
|
static void do_f_midi2_free(struct f_midi2 *midi2, struct f_midi2_opts *opts)
|
|
{
|
|
mutex_lock(&opts->lock);
|
|
--opts->refcnt;
|
|
mutex_unlock(&opts->lock);
|
|
kfree(midi2->string_defs);
|
|
kfree(midi2);
|
|
}
|
|
|
|
static void f_midi2_free(struct usb_function *f)
|
|
{
|
|
do_f_midi2_free(func_to_midi2(f),
|
|
container_of(f->fi, struct f_midi2_opts, func_inst));
|
|
}
|
|
|
|
/* verify the parameters set up via configfs;
|
|
* return the number of EPs or a negative error
|
|
*/
|
|
static int verify_parameters(struct f_midi2_opts *opts)
|
|
{
|
|
int i, j, num_eps, num_blks;
|
|
struct f_midi2_ep_info *ep;
|
|
struct f_midi2_block_info *bp;
|
|
|
|
for (num_eps = 0; num_eps < MAX_UMP_EPS && opts->eps[num_eps];
|
|
num_eps++)
|
|
;
|
|
if (!num_eps) {
|
|
pr_err("f_midi2: No EP is defined\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
num_blks = 0;
|
|
for (i = 0; i < num_eps; i++) {
|
|
ep = &opts->eps[i]->info;
|
|
if (!(ep->protocol_caps & ep->protocol)) {
|
|
pr_err("f_midi2: Invalid protocol 0x%x (caps 0x%x) for EP %d\n",
|
|
ep->protocol, ep->protocol_caps, i);
|
|
return -EINVAL;
|
|
}
|
|
|
|
for (j = 0; j < SNDRV_UMP_MAX_BLOCKS && opts->eps[i]->blks[j];
|
|
j++, num_blks++) {
|
|
bp = &opts->eps[i]->blks[j]->info;
|
|
if (bp->first_group + bp->num_groups > SNDRV_UMP_MAX_GROUPS) {
|
|
pr_err("f_midi2: Invalid group definitions for block %d:%d\n",
|
|
i, j);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (bp->midi1_num_groups) {
|
|
if (bp->midi1_first_group < bp->first_group ||
|
|
bp->midi1_first_group + bp->midi1_num_groups >
|
|
bp->first_group + bp->num_groups) {
|
|
pr_err("f_midi2: Invalid MIDI1 group definitions for block %d:%d\n",
|
|
i, j);
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (!num_blks) {
|
|
pr_err("f_midi2: No block is defined\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
return num_eps;
|
|
}
|
|
|
|
/* fill mapping between MIDI 1.0 cable and UMP EP/group */
|
|
static void fill_midi1_cable_mapping(struct f_midi2 *midi2,
|
|
struct f_midi2_ep *ep,
|
|
int blk)
|
|
{
|
|
const struct f_midi2_block_info *binfo = &ep->blks[blk].info;
|
|
struct midi1_cable_mapping *map;
|
|
int i, group;
|
|
|
|
if (!binfo->midi1_num_groups)
|
|
return;
|
|
if (binfo->direction != SNDRV_UMP_DIR_OUTPUT) {
|
|
group = binfo->midi1_first_group;
|
|
map = midi2->in_cable_mapping + midi2->num_midi1_in;
|
|
for (i = 0; i < binfo->midi1_num_groups; i++, group++, map++) {
|
|
if (midi2->num_midi1_in >= MAX_CABLES)
|
|
break;
|
|
map->ep = ep;
|
|
map->block = blk;
|
|
map->group = group;
|
|
midi2->num_midi1_in++;
|
|
/* store 1-based cable number */
|
|
ep->in_group_to_cable[group] = midi2->num_midi1_in;
|
|
}
|
|
}
|
|
|
|
if (binfo->direction != SNDRV_UMP_DIR_INPUT) {
|
|
group = binfo->midi1_first_group;
|
|
map = midi2->out_cable_mapping + midi2->num_midi1_out;
|
|
for (i = 0; i < binfo->midi1_num_groups; i++, group++, map++) {
|
|
if (midi2->num_midi1_out >= MAX_CABLES)
|
|
break;
|
|
map->ep = ep;
|
|
map->block = blk;
|
|
map->group = group;
|
|
midi2->num_midi1_out++;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* gadget alloc callback */
|
|
static struct usb_function *f_midi2_alloc(struct usb_function_instance *fi)
|
|
{
|
|
struct f_midi2 *midi2;
|
|
struct f_midi2_opts *opts;
|
|
struct f_midi2_ep *ep;
|
|
struct f_midi2_block *bp;
|
|
int i, num_eps, blk;
|
|
|
|
midi2 = kzalloc(sizeof(*midi2), GFP_KERNEL);
|
|
if (!midi2)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
opts = container_of(fi, struct f_midi2_opts, func_inst);
|
|
mutex_lock(&opts->lock);
|
|
num_eps = verify_parameters(opts);
|
|
if (num_eps < 0) {
|
|
mutex_unlock(&opts->lock);
|
|
kfree(midi2);
|
|
return ERR_PTR(num_eps);
|
|
}
|
|
++opts->refcnt;
|
|
mutex_unlock(&opts->lock);
|
|
|
|
spin_lock_init(&midi2->queue_lock);
|
|
|
|
midi2->func.name = "midi2_func";
|
|
midi2->func.bind = f_midi2_bind;
|
|
midi2->func.unbind = f_midi2_unbind;
|
|
midi2->func.get_alt = f_midi2_get_alt;
|
|
midi2->func.set_alt = f_midi2_set_alt;
|
|
midi2->func.setup = f_midi2_setup;
|
|
midi2->func.disable = f_midi2_disable;
|
|
midi2->func.free_func = f_midi2_free;
|
|
|
|
midi2->info = opts->info;
|
|
midi2->num_eps = num_eps;
|
|
|
|
for (i = 0; i < num_eps; i++) {
|
|
ep = &midi2->midi2_eps[i];
|
|
ep->info = opts->eps[i]->info;
|
|
ep->card = midi2;
|
|
for (blk = 0; blk < SNDRV_UMP_MAX_BLOCKS &&
|
|
opts->eps[i]->blks[blk]; blk++) {
|
|
bp = &ep->blks[blk];
|
|
ep->num_blks++;
|
|
bp->info = opts->eps[i]->blks[blk]->info;
|
|
bp->gtb_id = ++midi2->total_blocks;
|
|
}
|
|
}
|
|
|
|
midi2->string_defs = kcalloc(midi2->total_blocks + 1,
|
|
sizeof(*midi2->string_defs), GFP_KERNEL);
|
|
if (!midi2->string_defs) {
|
|
do_f_midi2_free(midi2, opts);
|
|
return ERR_PTR(-ENOMEM);
|
|
}
|
|
|
|
if (opts->info.iface_name && *opts->info.iface_name)
|
|
midi2->string_defs[STR_IFACE].s = opts->info.iface_name;
|
|
else
|
|
midi2->string_defs[STR_IFACE].s = ump_ep_name(&midi2->midi2_eps[0]);
|
|
|
|
for (i = 0; i < midi2->num_eps; i++) {
|
|
ep = &midi2->midi2_eps[i];
|
|
for (blk = 0; blk < ep->num_blks; blk++) {
|
|
bp = &ep->blks[blk];
|
|
midi2->string_defs[gtb_to_str_id(bp->gtb_id)].s =
|
|
ump_fb_name(&bp->info);
|
|
|
|
fill_midi1_cable_mapping(midi2, ep, blk);
|
|
}
|
|
}
|
|
|
|
if (!midi2->num_midi1_in && !midi2->num_midi1_out) {
|
|
pr_err("f_midi2: MIDI1 definition is missing\n");
|
|
do_f_midi2_free(midi2, opts);
|
|
return ERR_PTR(-EINVAL);
|
|
}
|
|
|
|
return &midi2->func;
|
|
}
|
|
|
|
DECLARE_USB_FUNCTION_INIT(midi2, f_midi2_alloc_inst, f_midi2_alloc);
|
|
|
|
MODULE_LICENSE("GPL");
|