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