1
0
Fork 0
mirror of synced 2025-03-06 20:59:54 +01:00
linux/drivers/usb/gadget/function/f_midi2.c
Linyu Yuan 46decc82ff usb: gadget: unconditionally allocate hs/ss descriptor in bind operation
Take f_midi_bind() for example,  when composite layer call it, it will
allocate hs descriptor by calling gadget_is_dualspeed() API to check
gadget max support speed capability, but most other gadget function didn't
do like this.

To follow other function drivers, it is safe to remove the check which
mean support all possible link speed by default in function driver.

Similar change apply to midi2 and uvc.

Also in midi and midi2, as there is no descriptor difference between
super speed and super speed plus, follow other gadget function drivers,
do not allocate descriptor for super speed plus, composite layer will
handle it properly.

Signed-off-by: Linyu Yuan <quic_linyyuan@quicinc.com>
Link: https://lore.kernel.org/r/20230803091053.9714-5-quic_linyyuan@quicinc.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2023-08-22 14:48:33 +02:00

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