From patchwork Tue Jul 25 06:22:00 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Takashi Iwai X-Patchwork-Id: 125350 Return-Path: Delivered-To: ouuuleilei@gmail.com Received: by 2002:a59:9010:0:b0:3e4:2afc:c1 with SMTP id l16csp2279325vqg; Mon, 24 Jul 2023 23:49:57 -0700 (PDT) X-Google-Smtp-Source: APBJJlGgg5KHON1MrmErzKpov9WoDy7MJm7o9w1II+//4zDwrVkfF7V3OlApN8wXCNOqkQcbwv75 X-Received: by 2002:a17:906:5a4c:b0:992:345e:831b with SMTP id my12-20020a1709065a4c00b00992345e831bmr12726767ejc.50.1690267797060; Mon, 24 Jul 2023 23:49:57 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1690267797; cv=none; d=google.com; s=arc-20160816; b=xSsoDqztw8a1R1XpDaXVU2sP2iHr+uUWn29BMpFd5ADMrZclxEjzYIpW0cLEpDiMiK 0TLNp4+VdwYMCQ4ZaeY0U0MtXkKEcTjcgIziKabZefgQv19BQi2a2rO3AqzCAIXf49mZ 9YMEfyRYi8oDDk8F3qDA5sCsqSY95LfFeiIX0xb6e/qhWAXefKazHsETerAfWnSeZOvt UUhkrVo2oq7IoxpJi9YTix5Dr2kK6jD8tAAjdFPFWGPsy9hxrqkMHyLYRuqKRrWFUD07 xSX5d8iK0udRCvNowVuYiVnci2gLwiqaFzO0pt23mBjagjuR4qV4LAVhXAIJPPZjHFDZ IKyQ== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:content-transfer-encoding:mime-version :references:in-reply-to:message-id:date:subject:cc:to:from :dkim-signature:dkim-signature; bh=EcdxqCmG3mAOAhJ1fymJ/Fn/pVctZV38y883Oxgbo8g=; fh=BbVOx8xHLmKqd0mM1otOmPdkL6v0ikLC29Avu+/pNMM=; b=tq4IsKn5IVz3Z6oixkweJjJvj0E4v4O/nhtTqq03UDgZEwCuw4g/CJMyrGvcmUObc9 kLGE05dCXB4CCILXXN8JmqipZS2mH8gvyeQ6eoB6T4FrfwSwn2LXZ1W2WTKZsWGRjYD0 tPpLiaSrGjkmAstw9gYVMEGuJ/uLGWLLV6gjwwA3h2fjHk6fPk5bolzgy3rAEbrAxVVK anJTkAjZPd9W4nZRnE75pCB+qL8Dueuq+ugTNK4pqWCU194brePHGRfKleUAzW1ZThfd j7JOpVR0H1HI7SexPL5R5kKSHsYhQXG/k03EPmolrs8/barVe3jfKIAxg0NMeS8oHwMp sd8w== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@suse.de header.s=susede2_rsa header.b=xJuOSiny; dkim=neutral (no key) header.i=@suse.de; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=suse.de Received: from out1.vger.email (out1.vger.email. [2620:137:e000::1:20]) by mx.google.com with ESMTP id hb10-20020a170906b88a00b00993689daad0si7055872ejb.269.2023.07.24.23.49.32; Mon, 24 Jul 2023 23:49:57 -0700 (PDT) Received-SPF: pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) client-ip=2620:137:e000::1:20; Authentication-Results: mx.google.com; dkim=pass header.i=@suse.de header.s=susede2_rsa header.b=xJuOSiny; dkim=neutral (no key) header.i=@suse.de; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=suse.de Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S232106AbjGYGWg (ORCPT + 99 others); Tue, 25 Jul 2023 02:22:36 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:43058 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231575AbjGYGWR (ORCPT ); Tue, 25 Jul 2023 02:22:17 -0400 Received: from smtp-out1.suse.de (smtp-out1.suse.de [IPv6:2001:67c:2178:6::1c]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 1B73512E; Mon, 24 Jul 2023 23:22:13 -0700 (PDT) Received: from imap2.suse-dmz.suse.de (imap2.suse-dmz.suse.de [192.168.254.74]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (P-521) server-digest SHA512) (No client certificate requested) by smtp-out1.suse.de (Postfix) with ESMTPS id CA0C1224A6; Tue, 25 Jul 2023 06:22:11 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=suse.de; s=susede2_rsa; t=1690266131; h=from:from:reply-to:date:date:message-id:message-id:to:to:cc:cc: mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=EcdxqCmG3mAOAhJ1fymJ/Fn/pVctZV38y883Oxgbo8g=; b=xJuOSinyF9CfS4YYb+ramDKanfvRdJ3wiFBUve+MkKJBHYvVhKXLgsDdufk9ioemGBXpTN gHP758+3Qgg8R4rjLSwyF/7zRsm47NGxu6pwruOtzCaLR2Nr4U7ScXAgeQ0AMgmcghfOUR AR3S81C0AlK7H6FvqCcfJUxmSm7DHRA= DKIM-Signature: v=1; a=ed25519-sha256; c=relaxed/relaxed; d=suse.de; s=susede2_ed25519; t=1690266131; h=from:from:reply-to:date:date:message-id:message-id:to:to:cc:cc: mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=EcdxqCmG3mAOAhJ1fymJ/Fn/pVctZV38y883Oxgbo8g=; b=IYFQ/KGv8X0WGqR39ShmyIxBClJ6VpM8cSdDz+thxnczp0z5haGLo8rXbNT85yccGvi1Tw YEz/FGDytZis5eDw== Received: from imap2.suse-dmz.suse.de (imap2.suse-dmz.suse.de [192.168.254.74]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (P-521) server-digest SHA512) (No client certificate requested) by imap2.suse-dmz.suse.de (Postfix) with ESMTPS id 82F1113342; Tue, 25 Jul 2023 06:22:11 +0000 (UTC) Received: from dovecot-director2.suse.de ([192.168.254.65]) by imap2.suse-dmz.suse.de with ESMTPSA id ENf5HhNqv2S0dQAAMHmgww (envelope-from ); Tue, 25 Jul 2023 06:22:11 +0000 From: Takashi Iwai To: Greg Kroah-Hartman Cc: alsa-devel@alsa-project.org, linux-usb@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH 1/7] usb: gadget: Add support for USB MIDI 2.0 function driver Date: Tue, 25 Jul 2023 08:22:00 +0200 Message-Id: <20230725062206.9674-2-tiwai@suse.de> X-Mailer: git-send-email 2.35.3 In-Reply-To: <20230725062206.9674-1-tiwai@suse.de> References: <20230725062206.9674-1-tiwai@suse.de> MIME-Version: 1.0 X-Spam-Status: No, score=-2.1 required=5.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,RCVD_IN_DNSWL_BLOCKED, SPF_HELO_NONE,SPF_PASS,T_SCC_BODY_TEXT_LINE,URIBL_BLOCKED autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on lindbergh.monkeyblade.net Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org X-getmail-retrieved-from-mailbox: INBOX X-GMAIL-THRID: 1772374245568798553 X-GMAIL-MSGID: 1772374245568798553 This patch adds the support for USB MIDI 2.0 gadget function driver. The driver emulates a USB MIDI 2.0 interface with one or more UMP Endpoints, where each of UMP Endpoint is a pair of MIDI Endpoints for handling MIDI 2.0 UMP packets. When the function driver is bound, the driver creates an ALSA card object with UMP rawmidi devices. This is a kind of loop-back where the incoming and upcoming UMP packets from/to the MIDI 2.0 UMP Endpoints are transferred as-is. In addition, legacy (MIDI 1.0) rawmidi devices are created, so that legacy applications can work in the gadget side, too. When a USB MIDI 2.0 gadget interface appears, the connected host can use it with the snd-usb-audio driver where MIDI 2.0 support is enabled. Both gadget and connected hosts will have the similar UMP Endpoint and Function Block (or Group Terminal Block) information. Slight differences are the direction and UI-hint bits; it's due to the nature of gadget driver, and the input/output direction is swapped in both sides (the input for gadget is the output for host, and vice versa). The driver supports the brand-new UMP v1.1 feature, including the UMP Stream message handling for providing UMP Endpoint and Function Block information as well as dealing with the MIDI protocol switch. The driver responds to UMP Stream messages by itself. OTOH, MIDI-CI message handling isn't implemented in the kernel driver; it should be processed in the user-space through the loopback UMP device. As of this patch, the whole configuration is fixed, providing only one bidirectional UMP Endpoint containing a single FB/GTB with a single UMP Group. The configuration will be dynamically changeable in the following patches. The traditional MIDI 1.0 is still provided in the altset 0 (which is mandatory per spec). But it's only about the configuration, and no actual I/O will be running for the altset 0 as of this patch. The proper support MIDI 1.0 altset will follow in later patches, too. Signed-off-by: Takashi Iwai --- drivers/usb/gadget/Kconfig | 18 + drivers/usb/gadget/function/Makefile | 2 + drivers/usb/gadget/function/f_midi2.c | 1691 +++++++++++++++++++++++++ drivers/usb/gadget/function/u_midi2.h | 79 ++ 4 files changed, 1790 insertions(+) create mode 100644 drivers/usb/gadget/function/f_midi2.c create mode 100644 drivers/usb/gadget/function/u_midi2.h diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig index 336db8f92afa..b3592bcb0f96 100644 --- a/drivers/usb/gadget/Kconfig +++ b/drivers/usb/gadget/Kconfig @@ -208,6 +208,9 @@ config USB_F_UVC config USB_F_MIDI tristate +config USB_F_MIDI2 + tristate + config USB_F_HID tristate @@ -436,6 +439,21 @@ config USB_CONFIGFS_F_MIDI connections can then be made on the gadget system, using ALSA's aconnect utility etc. +config USB_CONFIGFS_F_MIDI2 + bool "MIDI 2.0 function" + depends on USB_CONFIGFS + depends on SND + select USB_LIBCOMPOSITE + select SND_UMP + select SND_UMP_LEGACY_RAWMIDI + select USB_F_MIDI2 + help + The MIDI 2.0 function driver provides the generic emulated + USB MIDI 2.0 interface, looped back to ALSA UMP rawmidi + device on the gadget host. It supports UMP 1.1 spec and + responds UMP Stream messages for UMP Endpoint and Function + Block information / configuration. + config USB_CONFIGFS_F_HID bool "HID function" depends on USB_CONFIGFS diff --git a/drivers/usb/gadget/function/Makefile b/drivers/usb/gadget/function/Makefile index 5d3a6cf02218..87917a7d4a9b 100644 --- a/drivers/usb/gadget/function/Makefile +++ b/drivers/usb/gadget/function/Makefile @@ -44,6 +44,8 @@ usb_f_uvc-y := f_uvc.o uvc_queue.o uvc_v4l2.o uvc_video.o uvc_configfs.o obj-$(CONFIG_USB_F_UVC) += usb_f_uvc.o usb_f_midi-y := f_midi.o obj-$(CONFIG_USB_F_MIDI) += usb_f_midi.o +usb_f_midi2-y := f_midi2.o +obj-$(CONFIG_USB_F_MIDI2) += usb_f_midi2.o usb_f_hid-y := f_hid.o obj-$(CONFIG_USB_F_HID) += usb_f_hid.o usb_f_printer-y := f_printer.o diff --git a/drivers/usb/gadget/function/f_midi2.c b/drivers/usb/gadget/function/f_midi2.c new file mode 100644 index 000000000000..848cb3150deb --- /dev/null +++ b/drivers/usb/gadget/function/f_midi2.c @@ -0,0 +1,1691 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * f_midi2.c -- USB MIDI 2.0 class function driver + */ + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +#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 */ +}; + +/* 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 */ +}; + +/* 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) + +/* 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; + + 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(1); +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, + .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 IN (Embedded) Jack */ +static struct usb_midi_in_jack_descriptor midi2_midi1_in_jack1_desc = { + .bLength = 0x06, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = USB_MS_MIDI_IN_JACK, + .bJackType = USB_MS_EMBEDDED, + .bJackID = 0x01, + .iJack = 0, +}; + +/* MIDI 1.0 IN (External) Jack */ +static struct usb_midi_in_jack_descriptor midi2_midi1_in_jack2_desc = { + .bLength = 0x06, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = USB_MS_MIDI_IN_JACK, + .bJackType = USB_MS_EXTERNAL, + .bJackID = 0x02, + .iJack = 0, +}; + +/* MIDI 1.0 OUT (Embedded) Jack */ +static struct usb_midi_out_jack_descriptor_1 midi2_midi1_out_jack1_desc = { + .bLength = 0x09, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = USB_MS_MIDI_OUT_JACK, + .bJackType = USB_MS_EMBEDDED, + .bJackID = 0x03, + .bNrInputPins = 1, + .pins = { { 0x02, 0x01 } }, + .iJack = 0, +}; + +/* MIDI 1.0 OUT (External) Jack */ +static struct usb_midi_out_jack_descriptor_1 midi2_midi1_out_jack2_desc = { + .bLength = 0x09, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = USB_MS_MIDI_OUT_JACK, + .bJackType = USB_MS_EXTERNAL, + .bJackID = 0x04, + .bNrInputPins = 1, + .pins = { { 0x01, 0x01 } }, + .iJack = 0, +}; + +/* 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_1 midi2_midi1_ep_out_class_desc = { + .bLength = 0x05, + .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_1 midi2_midi1_ep_in_class_desc = { + .bLength = 0x05, + .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, + .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, + &midi2_midi1_in_jack1_desc, + &midi2_midi1_in_jack2_desc, + &midi2_midi1_out_jack1_desc, + &midi2_midi1_out_jack2_desc, + NULL +}; + +static void *midi2_midi1_ep_descs[] = { + &midi2_midi1_ep_out_desc, + &midi2_midi1_ep_out_class_desc, + &midi2_midi1_ep_in_desc, + &midi2_midi1_ep_in_class_desc, + NULL +}; + +static void *midi2_midi1_ep_ss_descs[] = { + &midi2_midi1_ep_out_desc, + &midi2_midi1_ep_out_ss_comp_desc, + &midi2_midi1_ep_out_class_desc, + &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); +} + +/* Start MIDI EP */ +static int f_midi2_start_ep(struct f_midi2_usb_ep *usb_ep, + struct usb_function *fn) +{ + int err; + + 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->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->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, int num_reqs, + 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; + + if (num_reqs) { + usb_ep->reqs = kcalloc(num_reqs, sizeof(*usb_ep->reqs), + GFP_KERNEL); + if (!usb_ep->reqs) + return -ENOMEM; + for (i = 0; i < 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; + + 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 + */ + +/* 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_MIDI2) { + for (i = 0; i < midi2->num_eps; i++) { + ep = &midi2->midi2_eps[i]; + f_midi2_drop_reqs(&ep->ep_in); + f_midi2_drop_reqs(&ep->ep_out); + f_midi2_free_ep_reqs(&ep->ep_in); + f_midi2_free_ep_reqs(&ep->ep_out); + } + return 0; + } + + for (i = 0; i < midi2->num_eps; i++) { + ep = &midi2->midi2_eps[i]; + + err = f_midi2_start_ep(&ep->ep_in, fn); + if (err) + return err; + err = f_midi2_start_ep(&ep->ep_out, fn); + if (err) + return err; + + err = f_midi2_alloc_ep_reqs(&ep->ep_in); + if (err) + return err; + err = f_midi2_alloc_ep_reqs(&ep->ep_out); + if (err) + return err; + + f_midi2_queue_out_reqs(&ep->ep_out); + } + + 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; + + if (up && dir == SNDRV_RAWMIDI_STREAM_OUTPUT) + process_ump_transmit(ep); +} + +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, +}; + +/* + * 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_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; +}; + +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 f_midi2_create_usb_configs(struct f_midi2 *midi2, + struct f_midi2_usb_config *config, + int speed) +{ + void **midi1_eps; + int i, 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_eps = midi2_midi1_ep_descs; + break; + case USB_SPEED_SUPER: + case USB_SPEED_SUPER_PLUS: + 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_eps = midi2_midi1_ep_ss_descs; + break; + } + + err = append_configs(config, midi2_audio_descs); + if (err < 0) + return err; + err = append_configs(config, midi2_midi1_descs); + if (err < 0) + return err; + err = append_configs(config, midi1_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, + midi2->info.num_reqs, 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, + midi2->info.num_reqs, 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; + } + + /* 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 */ + status = f_midi2_init_ep(midi2, NULL, &midi2->midi1_ep_in, + &midi2_midi1_ep_in_desc, 0, NULL); + if (status) + goto fail; + status = f_midi2_init_ep(midi2, NULL, &midi2->midi1_ep_out, + &midi2_midi1_ep_out_desc, 0, NULL); + 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); + + if (gadget_is_dualspeed(midi2->gadget)) { + 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); + } + + if (gadget_is_superspeed(midi2->gadget)) { + 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; + } + if (gadget_is_superspeed_plus(midi2->gadget)) { + f->ssp_descriptors = usb_copy_descriptors(config.list); + if (!f->ssp_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); +} + +/* 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; + + block_opts = kzalloc(sizeof(*block_opts), GFP_KERNEL); + if (!block_opts) + return -ENOMEM; + + 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; + return 0; +} + +/* 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; +} + +static const struct config_item_type f_midi2_func_type = { + .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); + + /* we have only one EP and one FB */ + if (opts->eps[0]) { + kfree(opts->eps[0]->blks[0]); + kfree(opts->eps[0]); + } + 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; + + 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); + } + + config_group_init_type_name(&opts->func_inst.group, "", + &f_midi2_func_type); + 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)); +} + +/* gadget alloc callback */ +static struct usb_function *f_midi2_alloc(struct usb_function_instance *fi) +{ + struct f_midi2 *midi2; + struct f_midi2_opts *opts; + int i; + + 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); + ++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; + + /* fixed 1 UMP EP and 1 UMP FB as of now */ + midi2->num_eps = 1; + midi2->midi2_eps[0].info = opts->eps[0]->info; + midi2->midi2_eps[0].card = midi2; + midi2->midi2_eps[0].num_blks = 1; + midi2->midi2_eps[0].blks[0].info = opts->eps[0]->blks[0]->info; + midi2->midi2_eps[0].blks[0].gtb_id = 1; + + for (i = 0; i < midi2->num_eps; i++) + midi2->total_blocks += midi2->midi2_eps[i].num_blks; + + 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[0].s = opts->info.iface_name; + else + midi2->string_defs[0].s = ump_ep_name(&midi2->midi2_eps[0]); + midi2->string_defs[1].s = ump_fb_name(&midi2->midi2_eps[0].blks[0].info); + + return &midi2->func; +} + +DECLARE_USB_FUNCTION_INIT(midi2, f_midi2_alloc_inst, f_midi2_alloc); + +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/gadget/function/u_midi2.h b/drivers/usb/gadget/function/u_midi2.h new file mode 100644 index 000000000000..a68dc2ea035e --- /dev/null +++ b/drivers/usb/gadget/function/u_midi2.h @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Utility definitions for MIDI 2.0 function + */ + +#ifndef U_MIDI2_H +#define U_MIDI2_H + +#include +#include + +struct f_midi2_opts; +struct f_midi2_ep_opts; +struct f_midi2_block_opts; + +/* UMP Function Block info */ +struct f_midi2_block_info { + unsigned int direction; /* FB direction: 1-3 */ + unsigned int first_group; /* first UMP group: 0-15 */ + unsigned int num_groups; /* number of UMP groups: 1-16 */ + unsigned int ui_hint; /* UI-hint: 0-3 */ + unsigned int midi_ci_version; /* MIDI-CI version: 0-255 */ + unsigned int sysex8_streams; /* number of sysex8 streams: 0-255 */ + unsigned int is_midi1; /* MIDI 1.0 port: 0-2 */ + bool active; /* FB active flag: bool */ + const char *name; /* FB name */ +}; + +/* UMP Endpoint info */ +struct f_midi2_ep_info { + unsigned int protocol_caps; /* protocol capabilities: 1-3 */ + unsigned int protocol; /* default protocol: 1-2 */ + unsigned int manufacturer; /* manufacturer id: 0-0xffffff */ + unsigned int family; /* device family id: 0-0xffff */ + unsigned int model; /* device model id: 0x-0xffff */ + unsigned int sw_revision; /* software revision: 32bit */ + + const char *ep_name; /* Endpoint name */ + const char *product_id; /* Product ID */ +}; + +struct f_midi2_card_info { + bool process_ump; /* process UMP stream: bool */ + bool static_block; /* static FBs: bool */ + unsigned int req_buf_size; /* request buffer size */ + unsigned int num_reqs; /* number of requests */ + const char *iface_name; /* interface name */ +}; + +struct f_midi2_block_opts { + struct config_group group; + unsigned int id; + struct f_midi2_block_info info; + struct f_midi2_ep_opts *ep; +}; + +struct f_midi2_ep_opts { + struct config_group group; + unsigned int index; + struct f_midi2_ep_info info; + struct f_midi2_block_opts *blks[SNDRV_UMP_MAX_BLOCKS]; + struct f_midi2_opts *opts; +}; + +#define MAX_UMP_EPS 4 +#define MAX_CABLES 16 + +struct f_midi2_opts { + struct usb_function_instance func_inst; + struct mutex lock; + int refcnt; + + struct f_midi2_card_info info; + + unsigned int num_eps; + struct f_midi2_ep_opts *eps[MAX_UMP_EPS]; +}; + +#endif /* U_MIDI2_H */ From patchwork Tue Jul 25 06:22:01 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Takashi Iwai X-Patchwork-Id: 125351 Return-Path: Delivered-To: ouuuleilei@gmail.com Received: by 2002:a59:9010:0:b0:3e4:2afc:c1 with SMTP id l16csp2281771vqg; Mon, 24 Jul 2023 23:56:41 -0700 (PDT) X-Google-Smtp-Source: APBJJlFJlahm0f5fqNgAJGdtipfKcGnB2JPY3lqWwI3RooVzg/okaso/UedhcSGdvPT5QadQFEGP X-Received: by 2002:a17:907:2c68:b0:97e:aace:b6bc with SMTP id ib8-20020a1709072c6800b0097eaaceb6bcmr12890147ejc.53.1690268201017; Mon, 24 Jul 2023 23:56:41 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1690268201; cv=none; d=google.com; s=arc-20160816; b=G3iFHkxzioInh444fuEULilylHjWcxf5nWu1kvRr4sPgwN0CaNOgublLtIb2PfkKR3 Cj8mmKTIiomAszysHotq2UZ7vJOKTjOSCT50C0JYQvoPPsZfXs2i4rP0jfVBI6k/li44 JWOk+ptj+LQWPnL3AokhWwDXBkKcYxZW9pkNt/cJSdbAQsXEnTT5JJW91+x3L7Y7w3MX jfjEnmi6703bz2vgH6JJnJ92mKaqKwkGmMvS/kfFA3r0Et3Y8Xtfmve+4LLbsc/GUvYp QmnAQEtFkV4BNMqAkJulNubtVcC6rCiQVbyz65XxXWnH9nvDRD8j18iVvyPWumuc8Ch6 6C2A== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:content-transfer-encoding:mime-version :references:in-reply-to:message-id:date:subject:cc:to:from :dkim-signature:dkim-signature; bh=wz+K+MF0E29R6xWk5/OwTjFBIPIM9z4mkUoZpufqQ+E=; fh=BbVOx8xHLmKqd0mM1otOmPdkL6v0ikLC29Avu+/pNMM=; b=usohPrCLxojGCUZ49CAlQssSJOfbpWGYof11N3JH6qa2EvOifpGhxHZqO0a4IeR+py fr5u/3dqHgK+3xr+/fxTflY1oAEU87dLClaZlQN5ToE4si/41hmOvr7S/EnhKZvWNia8 Y3mR8rquNAvm6amw1R0SrB9urvEKLFy3gsfsFZpEIgonWEGIgBrPa6NEdo4FgwBdRLMb OK8MGAT3s4vtQfKqMYR2nBJe070KZ+Z1faXQVVR244JdhMA+0DIbbH9BOx0RnM8Rfp3q uEXnHqP0CNvBB8yVLm6oZSvtiF5vlFu+iw2sV1ZynKZcnzg/dOQWMQiEvCUAiRprU8kY 3WJw== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@suse.de header.s=susede2_rsa header.b=ni3Ck6Lo; dkim=neutral (no key) header.i=@suse.de header.s=susede2_ed25519 header.b=8i0aWKdw; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=suse.de Received: from out1.vger.email (out1.vger.email. [2620:137:e000::1:20]) by mx.google.com with ESMTP id y2-20020a17090668c200b0098e31cd141dsi7629159ejr.744.2023.07.24.23.56.17; Mon, 24 Jul 2023 23:56:41 -0700 (PDT) Received-SPF: pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) client-ip=2620:137:e000::1:20; Authentication-Results: mx.google.com; dkim=pass header.i=@suse.de header.s=susede2_rsa header.b=ni3Ck6Lo; dkim=neutral (no key) header.i=@suse.de header.s=susede2_ed25519 header.b=8i0aWKdw; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=suse.de Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S232118AbjGYGWp (ORCPT + 99 others); Tue, 25 Jul 2023 02:22:45 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:43062 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231612AbjGYGWS (ORCPT ); Tue, 25 Jul 2023 02:22:18 -0400 Received: from smtp-out2.suse.de (smtp-out2.suse.de [195.135.220.29]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 25171173F; Mon, 24 Jul 2023 23:22:13 -0700 (PDT) Received: from imap2.suse-dmz.suse.de (imap2.suse-dmz.suse.de [192.168.254.74]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (P-521) server-digest SHA512) (No client certificate requested) by smtp-out2.suse.de (Postfix) with ESMTPS id 0A9581F460; Tue, 25 Jul 2023 06:22:12 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=suse.de; s=susede2_rsa; t=1690266132; h=from:from:reply-to:date:date:message-id:message-id:to:to:cc:cc: mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=wz+K+MF0E29R6xWk5/OwTjFBIPIM9z4mkUoZpufqQ+E=; b=ni3Ck6LoN4UvMEBGxjs3FKRLIWY0aJbKCv2N2ij9x0IO/tNgQvWfvIGTwcNz9TR0ZKQjtp 1VHcthr6Xm1MwT8ufk4EXlaz/8AfZHE0W+W8wahWmJWuBgrjGsyrLeT1Un2GrBpBhwGuS9 8bUj3nbJwzdt7qYVslyqivN+MZ0CBpM= DKIM-Signature: v=1; a=ed25519-sha256; c=relaxed/relaxed; d=suse.de; s=susede2_ed25519; t=1690266132; h=from:from:reply-to:date:date:message-id:message-id:to:to:cc:cc: mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=wz+K+MF0E29R6xWk5/OwTjFBIPIM9z4mkUoZpufqQ+E=; b=8i0aWKdw1aDkfnXyAEIh53ce+FERunv+DBzZZO2L8jPrrdC6iHJZnAa4a41r6vVLoPsn3P rqMCaWTXbVzdA0Aw== Received: from imap2.suse-dmz.suse.de (imap2.suse-dmz.suse.de [192.168.254.74]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (P-521) server-digest SHA512) (No client certificate requested) by imap2.suse-dmz.suse.de (Postfix) with ESMTPS id CEA7C1390F; Tue, 25 Jul 2023 06:22:11 +0000 (UTC) Received: from dovecot-director2.suse.de ([192.168.254.65]) by imap2.suse-dmz.suse.de with ESMTPSA id 0IGQMRNqv2S0dQAAMHmgww (envelope-from ); Tue, 25 Jul 2023 06:22:11 +0000 From: Takashi Iwai To: Greg Kroah-Hartman Cc: alsa-devel@alsa-project.org, linux-usb@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH 2/7] usb: gadget: midi2: Add configfs support Date: Tue, 25 Jul 2023 08:22:01 +0200 Message-Id: <20230725062206.9674-3-tiwai@suse.de> X-Mailer: git-send-email 2.35.3 In-Reply-To: <20230725062206.9674-1-tiwai@suse.de> References: <20230725062206.9674-1-tiwai@suse.de> MIME-Version: 1.0 X-Spam-Status: No, score=-2.1 required=5.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,RCVD_IN_DNSWL_BLOCKED, SPF_HELO_NONE,SPF_PASS,T_SCC_BODY_TEXT_LINE,URIBL_BLOCKED autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on lindbergh.monkeyblade.net Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org X-getmail-retrieved-from-mailbox: INBOX X-GMAIL-THRID: 1772374669381466330 X-GMAIL-MSGID: 1772374669381466330 This patch adds the support of configfs to MIDI 2.0 function driver for users to allow configuring the UMP Endpoint and Function Blocks more flexibly. The configuration is in a tree form. The top-most contains some card-level configurations. UMP Endpoints are defined in subdirectories (ep.0, ep.1, etc) that contain Endpoint-specific configurations such as ep_name, etc. And, UMP Function Blocks are defined in the subdirectories (block.0, block.1, etc) under EP subdirectory. As default, the driver creates a single UMP Endpoint (ep.0) and a single Function Block (block.0) to work in a minimalistic manner. User can modify those attributes freely to fit with the demands. When multiple Function Blocks are required, user can create another directory as block.1, block.2, and so on (up to block.31). A block.* directory can be deleted dynamically, too. A caveat is that the block number has to be continuous. Similarly, when multiple UMP Endpoints are required, user can create another directory as ep.1, ep.2, up to ep.3. Also, some driver behavior can be controlled in the card top-level configs. e.g. you can pass process_ump=0 to disable the processing of UMP Stream messages. This would be equivalent with the older MIDI 2.0 spec that doesn't support UMP v1.1 features. The configfs interface checks upper- / lower-bound of input values, and more sanity checks are performed at binding. Attributes can't be changed any longer once when the instance is linked to UDC. Signed-off-by: Takashi Iwai --- .../ABI/testing/configfs-usb-gadget-midi2 | 52 ++ drivers/usb/gadget/function/f_midi2.c | 621 +++++++++++++++++- 2 files changed, 650 insertions(+), 23 deletions(-) create mode 100644 Documentation/ABI/testing/configfs-usb-gadget-midi2 diff --git a/Documentation/ABI/testing/configfs-usb-gadget-midi2 b/Documentation/ABI/testing/configfs-usb-gadget-midi2 new file mode 100644 index 000000000000..a3a036d784c7 --- /dev/null +++ b/Documentation/ABI/testing/configfs-usb-gadget-midi2 @@ -0,0 +1,52 @@ +What: /config/usb-gadget/gadget/functions/midi2.name +Date: Jul 2023 +KernelVersion: 6.6 +Description: + The attributes: + + ============ =============================================== + process_ump Flag to process UMP Stream messages (0 or 1) + static_block Flag for static blocks (0 or 1) + iface_name MIDI interface name string + ============ =============================================== + +What: /config/usb-gadget/gadget/functions/midi2.name/ep.number +Date: Jul 2023 +KernelVersion: 6.6 +Description: + This group contains a UMP Endpoint configuration. + A new Endpoint starts from 0, and can be up to 3. + + The attributes: + + ============= =============================================== + protocol_caps MIDI protocol capabilities (1, 2 or 3 for both) + protocol Default MIDI protocol (1 or 2) + ep_name UMP Endpoint name string + product_id Product ID string + manufacturer Manufacture ID (24 bit) + family Device family ID (16 bit) + model Device model ID (16 bit) + sw_revision Software Revision (32 bit) + ============= =============================================== + +What: /config/usb-gadget/gadget/functions/midi2.name/ep.number/block.number +Date: Jul 2023 +KernelVersion: 6.6 +Description: + This group contains a UMP Function Block configuration. + A new block starts from 0, and can be up to 31. + + The attributes: + + =============== =============================================== + name Function Block name string + direction 1: input, 2: output, 3: bidirectional + first_group The first UMP Group number (0-15) + num_groups The number of groups in this FB (1-16) + ui_hint 0: unknown, 1: receiver, 2: sender, 3: both + midi_ci_verison Supported MIDI-CI version number (8 bit) + is_midi1 Legacy MIDI 1.0 device (0, 1 or 2) + sysex8_streams Max number of SysEx8 streams (8 bit) + active Active FB flag (0 or 1) + =============== =============================================== diff --git a/drivers/usb/gadget/function/f_midi2.c b/drivers/usb/gadget/function/f_midi2.c index 848cb3150deb..c68a6fa0d237 100644 --- a/drivers/usb/gadget/function/f_midi2.c +++ b/drivers/usb/gadget/function/f_midi2.c @@ -292,7 +292,7 @@ static struct usb_interface_descriptor midi2_midi2_if_desc = { .bDescriptorType = USB_DT_INTERFACE, .bInterfaceNumber = 0, // to be filled .bAlternateSetting = 1, - .bNumEndpoints = 2, + .bNumEndpoints = 2, // to be filled .bInterfaceClass = USB_CLASS_AUDIO, .bInterfaceSubClass = USB_SUBCLASS_MIDISTREAMING, .bInterfaceProtocol = 0, @@ -1403,6 +1403,8 @@ static int f_midi2_bind(struct usb_configuration *c, struct usb_function *f) 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) @@ -1510,16 +1512,274 @@ static void f_midi2_unbind(struct usb_configuration *c, struct usb_function *f) 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(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_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) - return -ENOMEM; + if (!block_opts) { + ret = -ENOMEM; + goto out; + } block_opts->ep = ep_opts; block_opts->id = blk; @@ -1533,9 +1793,143 @@ static int f_midi2_block_opts_create(struct f_midi2_ep_opts *ep_opts, ep_opts->blks[blk] = block_opts; *block_p = block_opts; - return 0; + + mutex_unlock(&ep_opts->opts->lock); + out: + 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, @@ -1559,7 +1953,119 @@ static int f_midi2_ep_opts_create(struct f_midi2_opts *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, }; @@ -1569,11 +2075,7 @@ static void f_midi2_free_inst(struct usb_function_instance *f) opts = container_of(f, struct f_midi2_opts, func_inst); - /* we have only one EP and one FB */ - if (opts->eps[0]) { - kfree(opts->eps[0]->blks[0]); - kfree(opts->eps[0]); - } + kfree(opts->info.iface_name); kfree(opts); } @@ -1596,6 +2098,7 @@ static struct usb_function_instance *f_midi2_alloc_inst(void) 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); @@ -1612,6 +2115,15 @@ static struct usb_function_instance *f_midi2_alloc_inst(void) 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; } @@ -1630,12 +2142,58 @@ static void f_midi2_free(struct usb_function *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 (!num_blks) { + pr_err("f_midi2: No block is defined\n"); + return -EINVAL; + } + + return num_eps; +} + /* gadget alloc callback */ static struct usb_function *f_midi2_alloc(struct usb_function_instance *fi) { struct f_midi2 *midi2; struct f_midi2_opts *opts; - int i; + struct f_midi2_ep *ep; + struct f_midi2_block *bp; + int i, num_eps, blk; midi2 = kzalloc(sizeof(*midi2), GFP_KERNEL); if (!midi2) @@ -1643,6 +2201,12 @@ static struct usb_function *f_midi2_alloc(struct usb_function_instance *fi) 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); @@ -1658,17 +2222,20 @@ static struct usb_function *f_midi2_alloc(struct usb_function_instance *fi) midi2->func.free_func = f_midi2_free; midi2->info = opts->info; + midi2->num_eps = num_eps; - /* fixed 1 UMP EP and 1 UMP FB as of now */ - midi2->num_eps = 1; - midi2->midi2_eps[0].info = opts->eps[0]->info; - midi2->midi2_eps[0].card = midi2; - midi2->midi2_eps[0].num_blks = 1; - midi2->midi2_eps[0].blks[0].info = opts->eps[0]->blks[0]->info; - midi2->midi2_eps[0].blks[0].gtb_id = 1; - - for (i = 0; i < midi2->num_eps; i++) - midi2->total_blocks += midi2->midi2_eps[i].num_blks; + 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); @@ -1678,10 +2245,18 @@ static struct usb_function *f_midi2_alloc(struct usb_function_instance *fi) } if (opts->info.iface_name && *opts->info.iface_name) - midi2->string_defs[0].s = opts->info.iface_name; + midi2->string_defs[STR_IFACE].s = opts->info.iface_name; else - midi2->string_defs[0].s = ump_ep_name(&midi2->midi2_eps[0]); - midi2->string_defs[1].s = ump_fb_name(&midi2->midi2_eps[0].blks[0].info); + 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); + } + } return &midi2->func; } From patchwork Tue Jul 25 06:22:02 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Takashi Iwai X-Patchwork-Id: 125352 Return-Path: Delivered-To: ouuuleilei@gmail.com Received: by 2002:a59:9010:0:b0:3e4:2afc:c1 with SMTP id l16csp2282250vqg; Mon, 24 Jul 2023 23:57:56 -0700 (PDT) X-Google-Smtp-Source: APBJJlGONxifbP+fXyKryNywEkGdoOS9l/rpjQDxvq6ace+sfm/hFdoCwTmP0ViN9SUHq0/0ELBj X-Received: by 2002:a17:90a:8b92:b0:268:3ae2:410d with SMTP id z18-20020a17090a8b9200b002683ae2410dmr525845pjn.19.1690268276212; Mon, 24 Jul 2023 23:57:56 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1690268276; cv=none; d=google.com; s=arc-20160816; b=y7auO0e9GZllmROPQCIviGDfHdvN66+lDQwRHJ+v1ymoqg3Btlz6+7lKNt1lalNXI4 g1VwIr9DlfOAf5F/FimOUaiBe37i87d8K6jlrRc1P6BLIR46hTBp5SJhY4RBxn/eYEDs 8MqvOiSP9TRz3022GBNfo7eXvmhbk0pKkbMTxtQ27PZNFR/ILhAnPoE+nSlOPa7j7fCz vi2uRNww0Rhr2hZCUY9VXw1h1POOwQNchvx0Z4/fWYyYARm8WXHmfyJBtWr3p/m33kFu QaE3FWaXP+VXjS6DBvIf/WS5J1BPRZ3APUTvOU+GSu0ueGmqJ+smuOCOeGIS998NxWLz 05sQ== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:content-transfer-encoding:mime-version :references:in-reply-to:message-id:date:subject:cc:to:from :dkim-signature:dkim-signature; bh=NRKx0VeOYoR12sVFtMBeiLGHl5ooEUwXfcy64qyVtcY=; fh=BbVOx8xHLmKqd0mM1otOmPdkL6v0ikLC29Avu+/pNMM=; b=XYJUWGc+DHY/R+7FM7Zkh7suDD1rx1/fAggT1edgUZch/+3lzUe8JycEBJ7cO8/JBd hs+ZWcf9peEBuNdoZw3GHm3H6nuCiLYiqUUaWjeCW+uaTd7aQHz2PSazUrED+qp3Xp34 jfEc906PWeiUVp0YE4OWfR7Ru9EuX/ujVJQ+0bwZdtabgU72Kp4XVcV9GlNPT0PiPqpt 4ogey65k/lJHgMpXpddu0BIBAC6uQnofMvpWr3WntyGlC9VXCeETDnIlSjCzoBYPlT6Q IhVq+WZxLLlwAnlHhEfHAWX8FNyEYuHaQxt5zv1dgie2zkvftayiUxNlZrlOVncM0ot/ VfVw== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@suse.de header.s=susede2_rsa header.b="zw/9WulH"; dkim=neutral (no key) header.i=@suse.de header.s=susede2_ed25519; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=suse.de Received: from out1.vger.email (out1.vger.email. [2620:137:e000::1:20]) by mx.google.com with ESMTP id o23-20020a17090aac1700b002682c370e32si2181104pjq.188.2023.07.24.23.57.39; Mon, 24 Jul 2023 23:57:56 -0700 (PDT) Received-SPF: pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) client-ip=2620:137:e000::1:20; Authentication-Results: mx.google.com; dkim=pass header.i=@suse.de header.s=susede2_rsa header.b="zw/9WulH"; dkim=neutral (no key) header.i=@suse.de header.s=susede2_ed25519; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=suse.de Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S232077AbjGYGW0 (ORCPT + 99 others); Tue, 25 Jul 2023 02:22:26 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:43044 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231239AbjGYGWP (ORCPT ); Tue, 25 Jul 2023 02:22:15 -0400 Received: from smtp-out1.suse.de (smtp-out1.suse.de [IPv6:2001:67c:2178:6::1c]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 917F310E2; Mon, 24 Jul 2023 23:22:13 -0700 (PDT) Received: from imap2.suse-dmz.suse.de (imap2.suse-dmz.suse.de [192.168.254.74]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (P-521) server-digest SHA512) (No client certificate requested) by smtp-out1.suse.de (Postfix) with ESMTPS id 390192249D; Tue, 25 Jul 2023 06:22:12 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=suse.de; s=susede2_rsa; t=1690266132; h=from:from:reply-to:date:date:message-id:message-id:to:to:cc:cc: mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=NRKx0VeOYoR12sVFtMBeiLGHl5ooEUwXfcy64qyVtcY=; b=zw/9WulH1Jlb9JoRC09Al9CXvMWAqtlYioidBpO2WxNFBPVwFIdjwsjHikb/w6UKv7Z8RE HleebipqFnEHZ44/a9u7qH2F4a6zauaujpZU7jMcNnTiiQ+OT28WGOdspBQOpdsXDoAja7 CykYNzJG8n1wakESfkoC+N67IqdvSgo= DKIM-Signature: v=1; a=ed25519-sha256; c=relaxed/relaxed; d=suse.de; s=susede2_ed25519; t=1690266132; h=from:from:reply-to:date:date:message-id:message-id:to:to:cc:cc: mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=NRKx0VeOYoR12sVFtMBeiLGHl5ooEUwXfcy64qyVtcY=; b=DzLAgFUkmNyD91roXAO9nQVKgMidL+Zk8vVs087iYNL2yC6oL4eh+a3h8P0tp+0rbzrsiw +D2lyu7amGk8giBA== Received: from imap2.suse-dmz.suse.de (imap2.suse-dmz.suse.de [192.168.254.74]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (P-521) server-digest SHA512) (No client certificate requested) by imap2.suse-dmz.suse.de (Postfix) with ESMTPS id 0D6C813342; Tue, 25 Jul 2023 06:22:12 +0000 (UTC) Received: from dovecot-director2.suse.de ([192.168.254.65]) by imap2.suse-dmz.suse.de with ESMTPSA id yANkAhRqv2S0dQAAMHmgww (envelope-from ); Tue, 25 Jul 2023 06:22:12 +0000 From: Takashi Iwai To: Greg Kroah-Hartman Cc: alsa-devel@alsa-project.org, linux-usb@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH 3/7] usb: gadget: midi2: Dynamically create MIDI 1.0 altset descriptors Date: Tue, 25 Jul 2023 08:22:02 +0200 Message-Id: <20230725062206.9674-4-tiwai@suse.de> X-Mailer: git-send-email 2.35.3 In-Reply-To: <20230725062206.9674-1-tiwai@suse.de> References: <20230725062206.9674-1-tiwai@suse.de> MIME-Version: 1.0 X-Spam-Status: No, score=-2.1 required=5.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,RCVD_IN_DNSWL_BLOCKED, SPF_HELO_NONE,SPF_PASS,T_SCC_BODY_TEXT_LINE,URIBL_BLOCKED autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on lindbergh.monkeyblade.net Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org X-getmail-retrieved-from-mailbox: INBOX X-GMAIL-THRID: 1772374747644154901 X-GMAIL-MSGID: 1772374747644154901 This patch extends MIDI 2.0 function driver to deal with more MIDI1 Jacks depending on the given Block configuration. For MIDI 1.0, we take the configuration given in Function Block 0, and create MIDI Jacks and Endpoints depending on the definition there. That is, when more UMP Groups are defined in the Block 0, the corresponding MIDI1 Jacks will be created. Signed-off-by: Takashi Iwai --- drivers/usb/gadget/function/f_midi2.c | 228 ++++++++++++++++++-------- 1 file changed, 157 insertions(+), 71 deletions(-) diff --git a/drivers/usb/gadget/function/f_midi2.c b/drivers/usb/gadget/function/f_midi2.c index c68a6fa0d237..b15d832ff441 100644 --- a/drivers/usb/gadget/function/f_midi2.c +++ b/drivers/usb/gadget/function/f_midi2.c @@ -151,7 +151,7 @@ static struct usb_ms20_gr_trm_block_descriptor gtb_desc = { }; DECLARE_USB_MIDI_OUT_JACK_DESCRIPTOR(1); -DECLARE_USB_MS_ENDPOINT_DESCRIPTOR(1); +DECLARE_USB_MS_ENDPOINT_DESCRIPTOR(16); DECLARE_UAC_AC_HEADER_DESCRIPTOR(1); DECLARE_USB_MS20_ENDPOINT_DESCRIPTOR(32); @@ -185,7 +185,7 @@ static struct usb_interface_descriptor midi2_midi1_if_desc = { .bDescriptorType = USB_DT_INTERFACE, .bInterfaceNumber = 0, // to be filled .bAlternateSetting = 0, - .bNumEndpoints = 2, + .bNumEndpoints = 2, // to be filled .bInterfaceClass = USB_CLASS_AUDIO, .bInterfaceSubClass = USB_SUBCLASS_MIDISTREAMING, .bInterfaceProtocol = 0, @@ -200,50 +200,6 @@ static struct usb_ms_header_descriptor midi2_midi1_class_desc = { .wTotalLength = __cpu_to_le16(0x41), // to be calculated }; -/* MIDI 1.0 IN (Embedded) Jack */ -static struct usb_midi_in_jack_descriptor midi2_midi1_in_jack1_desc = { - .bLength = 0x06, - .bDescriptorType = USB_DT_CS_INTERFACE, - .bDescriptorSubtype = USB_MS_MIDI_IN_JACK, - .bJackType = USB_MS_EMBEDDED, - .bJackID = 0x01, - .iJack = 0, -}; - -/* MIDI 1.0 IN (External) Jack */ -static struct usb_midi_in_jack_descriptor midi2_midi1_in_jack2_desc = { - .bLength = 0x06, - .bDescriptorType = USB_DT_CS_INTERFACE, - .bDescriptorSubtype = USB_MS_MIDI_IN_JACK, - .bJackType = USB_MS_EXTERNAL, - .bJackID = 0x02, - .iJack = 0, -}; - -/* MIDI 1.0 OUT (Embedded) Jack */ -static struct usb_midi_out_jack_descriptor_1 midi2_midi1_out_jack1_desc = { - .bLength = 0x09, - .bDescriptorType = USB_DT_CS_INTERFACE, - .bDescriptorSubtype = USB_MS_MIDI_OUT_JACK, - .bJackType = USB_MS_EMBEDDED, - .bJackID = 0x03, - .bNrInputPins = 1, - .pins = { { 0x02, 0x01 } }, - .iJack = 0, -}; - -/* MIDI 1.0 OUT (External) Jack */ -static struct usb_midi_out_jack_descriptor_1 midi2_midi1_out_jack2_desc = { - .bLength = 0x09, - .bDescriptorType = USB_DT_CS_INTERFACE, - .bDescriptorSubtype = USB_MS_MIDI_OUT_JACK, - .bJackType = USB_MS_EXTERNAL, - .bJackID = 0x04, - .bNrInputPins = 1, - .pins = { { 0x01, 0x01 } }, - .iJack = 0, -}; - /* MIDI 1.0 EP OUT */ static struct usb_endpoint_descriptor midi2_midi1_ep_out_desc = { .bLength = USB_DT_ENDPOINT_AUDIO_SIZE, @@ -257,8 +213,8 @@ static struct usb_ss_ep_comp_descriptor midi2_midi1_ep_out_ss_comp_desc = { .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, }; -static struct usb_ms_endpoint_descriptor_1 midi2_midi1_ep_out_class_desc = { - .bLength = 0x05, +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, @@ -278,8 +234,8 @@ static struct usb_ss_ep_comp_descriptor midi2_midi1_ep_in_ss_comp_desc = { .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, }; -static struct usb_ms_endpoint_descriptor_1 midi2_midi1_ep_in_class_desc = { - .bLength = 0x05, +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, @@ -337,25 +293,29 @@ static void *midi2_audio_descs[] = { static void *midi2_midi1_descs[] = { &midi2_midi1_if_desc, &midi2_midi1_class_desc, - &midi2_midi1_in_jack1_desc, - &midi2_midi1_in_jack2_desc, - &midi2_midi1_out_jack1_desc, - &midi2_midi1_out_jack2_desc, NULL }; -static void *midi2_midi1_ep_descs[] = { +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_ss_descs[] = { +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, @@ -1197,6 +1157,11 @@ 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[16]; + struct usb_midi_out_jack_descriptor_1 jack_outs[16]; }; static int append_config(struct f_midi2_usb_config *config, void *d) @@ -1231,12 +1196,61 @@ static int append_configs(struct f_midi2_usb_config *config, void **d) return 0; } +static int append_midi1_in_jack(struct f_midi2 *midi2, + struct f_midi2_usb_config *config, + 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; + jack->iJack = midi2->strings[STR_GTB1].id; // TODO: better names? + + 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, + 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; + jack->iJack = midi2->strings[STR_GTB1].id; // TODO: better names? + + 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_eps; - int i, err; + struct f_midi2_block *blk = &midi2->midi2_eps[0].blks[0]; + void **midi1_in_eps, **midi1_out_eps; + int i, jack, total; + int err; switch (speed) { default: @@ -1248,7 +1262,8 @@ static int f_midi2_create_usb_configs(struct f_midi2 *midi2, cpu_to_le16(512); fallthrough; case USB_SPEED_FULL: - midi1_eps = midi2_midi1_ep_descs; + midi1_in_eps = midi2_midi1_ep_in_descs; + midi1_out_eps = midi2_midi1_ep_out_descs; break; case USB_SPEED_SUPER: case USB_SPEED_SUPER_PLUS: @@ -1257,19 +1272,85 @@ static int f_midi2_create_usb_configs(struct f_midi2 *midi2, for (i = 0; i < midi2->num_eps; i++) midi2_midi2_ep_out_desc[i].wMaxPacketSize = cpu_to_le16(1024); - midi1_eps = midi2_midi1_ep_ss_descs; + 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; + + switch (blk->info.direction) { + case SNDRV_UMP_DIR_INPUT: + case SNDRV_UMP_DIR_OUTPUT: + midi2_midi1_if_desc.bNumEndpoints = 1; + break; + default: + midi2_midi1_if_desc.bNumEndpoints = 2; + break; + } + err = append_configs(config, midi2_midi1_descs); if (err < 0) return err; - err = append_configs(config, midi1_eps); - if (err < 0) - return err; + + total = USB_DT_MS_HEADER_SIZE; + if (blk->info.direction != SNDRV_UMP_DIR_INPUT) { + midi2_midi1_ep_out_class_desc.bLength = + USB_DT_MS_ENDPOINT_SIZE(blk->info.num_groups); + total += midi2_midi1_ep_out_class_desc.bLength; + midi2_midi1_ep_out_class_desc.bNumEmbMIDIJack = + blk->info.num_groups; + total += blk->info.num_groups * + (USB_DT_MIDI_IN_SIZE + USB_DT_MIDI_OUT_SIZE(1)); + for (i = 0; i < blk->info.num_groups; i++) { + jack = append_midi1_in_jack(midi2, config, + USB_MS_EMBEDDED); + if (jack < 0) + return jack; + midi2_midi1_ep_out_class_desc.baAssocJackID[i] = jack; + jack = append_midi1_out_jack(midi2, config, + USB_MS_EXTERNAL, jack); + if (jack < 0) + return jack; + } + } + + if (blk->info.direction != SNDRV_UMP_DIR_OUTPUT) { + midi2_midi1_ep_in_class_desc.bLength = + USB_DT_MS_ENDPOINT_SIZE(blk->info.num_groups); + total += midi2_midi1_ep_in_class_desc.bLength; + midi2_midi1_ep_in_class_desc.bNumEmbMIDIJack = + blk->info.num_groups; + total += blk->info.num_groups * + (USB_DT_MIDI_IN_SIZE + USB_DT_MIDI_OUT_SIZE(1)); + for (i = 0; i < blk->info.num_groups; i++) { + jack = append_midi1_in_jack(midi2, config, + USB_MS_EXTERNAL); + if (jack < 0) + return jack; + jack = append_midi1_out_jack(midi2, config, + 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 (blk->info.direction != SNDRV_UMP_DIR_INPUT) { + err = append_configs(config, midi1_out_eps); + if (err < 0) + return err; + } + if (blk->info.direction != SNDRV_UMP_DIR_OUTPUT) { + err = append_configs(config, midi1_in_eps); + if (err < 0) + return err; + } + err = append_configs(config, midi2_midi2_descs); if (err < 0) return err; @@ -1421,14 +1502,19 @@ static int f_midi2_bind(struct usb_configuration *c, struct usb_function *f) midi2_audio_class_desc.baInterfaceNr[0] = status; /* allocate instance-specific endpoints */ - status = f_midi2_init_ep(midi2, NULL, &midi2->midi1_ep_in, - &midi2_midi1_ep_in_desc, 0, NULL); - if (status) - goto fail; - status = f_midi2_init_ep(midi2, NULL, &midi2->midi1_ep_out, - &midi2_midi1_ep_out_desc, 0, NULL); - if (status) - goto fail; + 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, 0, NULL); + 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, 0, NULL); + if (status) + goto fail; + } for (i = 0; i < midi2->num_eps; i++) { status = f_midi2_init_midi2_ep_in(midi2, i); From patchwork Tue Jul 25 06:22:03 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Takashi Iwai X-Patchwork-Id: 125367 Return-Path: Delivered-To: ouuuleilei@gmail.com Received: by 2002:a59:9010:0:b0:3e4:2afc:c1 with SMTP id l16csp2296210vqg; Tue, 25 Jul 2023 00:29:31 -0700 (PDT) X-Google-Smtp-Source: APBJJlHYyUBTq6SNmNG1uzc+TXoCGKFAq4C9IHEW9hz3vDLepAcW+NJnPD590pYMhARSbw2OwcZs X-Received: by 2002:a05:6402:b14:b0:51d:8aaf:5adc with SMTP id bm20-20020a0564020b1400b0051d8aaf5adcmr10347041edb.14.1690270170831; Tue, 25 Jul 2023 00:29:30 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1690270170; cv=none; d=google.com; s=arc-20160816; b=B06VdPwROZNNa1UYWB13hrJr2C0ABaEZCQxFhQ8jvR96FwIFX7J1GQDz/zLVQPiRBI KkZWbkn4BL5O7aHSCs36+1RKV3hGcthqNQJRNNdD8b/SR3Hun7SQclEsYQBQoeFtOhL3 uNgeTE6S0AsVXXFmT3L6MkGyZ4lqyJMJ7BwFemtgqDRXIVyiazfbaSheaFe5JPJ/64PR /lopgefNWAw4GA6na0/vzzUwPNr3scsfJq7iMRhYYc1GSGJO1Ll3l7Wref14UuseOR38 xTBhQJ1+GEFJyFqAdzJED69X2HG604KFSMJzrwM8SkROy32ji0hbSeTRQ+I2aYC2IWiW AkXg== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:content-transfer-encoding:mime-version :references:in-reply-to:message-id:date:subject:cc:to:from :dkim-signature:dkim-signature; bh=6fcCJ8Vaj3hw+RLNlHJQVq+19pva/3L/EmE633ig1ro=; fh=BbVOx8xHLmKqd0mM1otOmPdkL6v0ikLC29Avu+/pNMM=; b=YrJsygAVAF3vYdGEB+5amjeY3pt6I062uhhhKC5A3QBKdkJVCFihYXti+31r/FwKyr a00Q8UW4YLHTuLocglWgOsrzdu02o35qa680XsxuTWGYJEym0GGPUW1ETWkMfiWLk32a sLrcCtvwRVfZxrO8vpjuZdasNJqQeNSya4+hx6StUObF5eb5Wxumi135xe0bAKML78e0 JnBmoWMnj160zRR7+bFd//aZT5NGzHM6icgoW1ryYaYuX522uTjGp2q2cd4lY+Hn5D1z nRCua/lM0JhVSuopyy8X9ACO3igLzTI7Xij3bM8bbu39SkopACoS/rwKaMw/QXozdPgG 13zg== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@suse.de header.s=susede2_rsa header.b=RIVHnDBL; dkim=neutral (no key) header.i=@suse.de; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=suse.de Received: from out1.vger.email (out1.vger.email. [2620:137:e000::1:20]) by mx.google.com with ESMTP id l6-20020aa7d946000000b005221da47f59si4272950eds.172.2023.07.25.00.29.06; Tue, 25 Jul 2023 00:29:30 -0700 (PDT) Received-SPF: pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) client-ip=2620:137:e000::1:20; Authentication-Results: mx.google.com; dkim=pass header.i=@suse.de header.s=susede2_rsa header.b=RIVHnDBL; dkim=neutral (no key) header.i=@suse.de; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=suse.de Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S232100AbjGYGWe (ORCPT + 99 others); Tue, 25 Jul 2023 02:22:34 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:43052 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231314AbjGYGWQ (ORCPT ); Tue, 25 Jul 2023 02:22:16 -0400 Received: from smtp-out1.suse.de (smtp-out1.suse.de [IPv6:2001:67c:2178:6::1c]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id B564B10F6; Mon, 24 Jul 2023 23:22:13 -0700 (PDT) Received: from imap2.suse-dmz.suse.de (imap2.suse-dmz.suse.de [192.168.254.74]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (P-521) server-digest SHA512) (No client certificate requested) by smtp-out1.suse.de (Postfix) with ESMTPS id 6CA1D224A7; Tue, 25 Jul 2023 06:22:12 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=suse.de; s=susede2_rsa; t=1690266132; h=from:from:reply-to:date:date:message-id:message-id:to:to:cc:cc: mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=6fcCJ8Vaj3hw+RLNlHJQVq+19pva/3L/EmE633ig1ro=; b=RIVHnDBLwgYOGzjUJImXD776g6/L3dXdkZ9GEO++PzEi6SNOoRxuIMoAikI7MtGI8qcYhU FNNL1MEt0QK6jVQ37AfIqlKr4nKp7CRTF+dwpNQ6wfAHfO/87kI9bHVXK+4VXrfWu1W9AO BXo32OKzvcPHVPte++qAXzs6jmDJ/94= DKIM-Signature: v=1; a=ed25519-sha256; c=relaxed/relaxed; d=suse.de; s=susede2_ed25519; t=1690266132; h=from:from:reply-to:date:date:message-id:message-id:to:to:cc:cc: mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=6fcCJ8Vaj3hw+RLNlHJQVq+19pva/3L/EmE633ig1ro=; b=ge8KE8ofURsow9zmth6QZouNdbwU7CYsQ/otz52uAP80m/MKR8zH4iHeBGafRpirB/1Ul4 f88iIf1Hjm5j4KAQ== Received: from imap2.suse-dmz.suse.de (imap2.suse-dmz.suse.de [192.168.254.74]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (P-521) server-digest SHA512) (No client certificate requested) by imap2.suse-dmz.suse.de (Postfix) with ESMTPS id 3F10E13342; Tue, 25 Jul 2023 06:22:12 +0000 (UTC) Received: from dovecot-director2.suse.de ([192.168.254.65]) by imap2.suse-dmz.suse.de with ESMTPSA id 6FRaDhRqv2S0dQAAMHmgww (envelope-from ); Tue, 25 Jul 2023 06:22:12 +0000 From: Takashi Iwai To: Greg Kroah-Hartman Cc: alsa-devel@alsa-project.org, linux-usb@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH 4/7] usb: gadget: midi2: MIDI 1.0 interface (altset 0) support Date: Tue, 25 Jul 2023 08:22:03 +0200 Message-Id: <20230725062206.9674-5-tiwai@suse.de> X-Mailer: git-send-email 2.35.3 In-Reply-To: <20230725062206.9674-1-tiwai@suse.de> References: <20230725062206.9674-1-tiwai@suse.de> MIME-Version: 1.0 X-Spam-Status: No, score=-2.1 required=5.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,RCVD_IN_DNSWL_BLOCKED, SPF_HELO_NONE,SPF_PASS,T_SCC_BODY_TEXT_LINE,URIBL_BLOCKED autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on lindbergh.monkeyblade.net Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org X-getmail-retrieved-from-mailbox: INBOX X-GMAIL-THRID: 1772376734446891505 X-GMAIL-MSGID: 1772376734446891505 This patch extends MIDI 2.0 function driver to add more proper support for MIDI 1.0 interface. Before this patch, the driver only exposes the USB descriptor of a MIDI 1.0 interface in altset 0 while no actual I/O is running for it. This patch enables the actual I/O for the altset 0; the backend UMP rawmidi is translated from/to the MIDI 1.0 USB commands. For converting to USB MIDI 1.0 data protocol, a helper function is copied from the existing f_midi driver, in addition to a few other UMP Core helper functions. For the MIDI 1.0 OUT (that is, input for gadget), the incoming USB MIDI 1.0 packet is translated to UMP packets via UMP Core helper, and tossed to the attached UMP rawmidi. It's a relatively straightforward. OTOH, for MIDI 1.0 IN (i.e. output for gadget), it's a bit more complex: we need to convert a source UMP packet once to the standard MIDI 1.0 byte stream, and convert it again to USB MIDI 1.0 packets, then send them out. Signed-off-by: Takashi Iwai --- drivers/usb/gadget/function/f_midi2.c | 494 +++++++++++++++++++++++--- 1 file changed, 452 insertions(+), 42 deletions(-) diff --git a/drivers/usb/gadget/function/f_midi2.c b/drivers/usb/gadget/function/f_midi2.c index b15d832ff441..a368ac51d349 100644 --- a/drivers/usb/gadget/function/f_midi2.c +++ b/drivers/usb/gadget/function/f_midi2.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -50,6 +51,27 @@ struct f_midi2_block { 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 */ @@ -89,6 +111,11 @@ struct f_midi2 { struct f_midi2_usb_ep midi1_ep_in; struct f_midi2_usb_ep midi1_ep_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; + int midi_if; /* USB MIDI interface number */ int operation_mode; /* current operation mode */ @@ -707,12 +734,353 @@ static void f_midi2_ep_in_complete(struct usb_ep *usb_ep, 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->midi2_eps[0].blks[0].info.num_groups; + 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; + int len, size, cable; + 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 = group - ep->blks[0].info.first_group; + if (cable < 0 || cable >= ep->blks[0].info.num_groups) + continue; + 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 = &midi2->midi2_eps[0]; + 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, 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) { + group = *buf >> 4; + if (group >= ep->blks[0].info.num_groups) + continue; + group += ep->blks[0].info.first_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) @@ -725,7 +1093,7 @@ static void f_midi2_drop_reqs(struct f_midi2_usb_ep *usb_ep) { int i; - if (!usb_ep->num_reqs) + if (!usb_ep->usb_ep || !usb_ep->num_reqs) return; for (i = 0; i < usb_ep->num_reqs; i++) { @@ -742,6 +1110,8 @@ 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; @@ -774,7 +1144,7 @@ static void f_midi2_free_ep_reqs(struct f_midi2_usb_ep *usb_ep) /* 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, int num_reqs, + void *desc, void (*complete)(struct usb_ep *usb_ep, struct usb_request *req)) { @@ -787,17 +1157,15 @@ static int f_midi2_init_ep(struct f_midi2 *midi2, struct f_midi2_ep *ep, return -ENODEV; usb_ep->complete = complete; - if (num_reqs) { - usb_ep->reqs = kcalloc(num_reqs, sizeof(*usb_ep->reqs), - GFP_KERNEL); - if (!usb_ep->reqs) - return -ENOMEM; - for (i = 0; i < 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++; - } + 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; @@ -821,6 +1189,9 @@ 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; @@ -836,6 +1207,41 @@ static void f_midi2_queue_out_reqs(struct f_midi2_usb_ep *usb_ep) * 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) @@ -859,35 +1265,28 @@ static int f_midi2_set_alt(struct usb_function *fn, unsigned int intf, 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_drop_reqs(&ep->ep_in); - f_midi2_drop_reqs(&ep->ep_out); - f_midi2_free_ep_reqs(&ep->ep_in); - f_midi2_free_ep_reqs(&ep->ep_out); + f_midi2_stop_eps(&ep->ep_in, &ep->ep_out); } - return 0; } - for (i = 0; i < midi2->num_eps; i++) { - ep = &midi2->midi2_eps[i]; + if (op_mode == MIDI_OP_MODE_MIDI1) + return f_midi2_start_eps(&midi2->midi1_ep_in, + &midi2->midi1_ep_out, fn); - err = f_midi2_start_ep(&ep->ep_in, fn); - if (err) - return err; - err = f_midi2_start_ep(&ep->ep_out, fn); - if (err) - return err; + if (op_mode == MIDI_OP_MODE_MIDI2) { + for (i = 0; i < midi2->num_eps; i++) { + ep = &midi2->midi2_eps[i]; - err = f_midi2_alloc_ep_reqs(&ep->ep_in); - if (err) - return err; - err = f_midi2_alloc_ep_reqs(&ep->ep_out); - if (err) - return err; - - f_midi2_queue_out_reqs(&ep->ep_out); + err = f_midi2_start_eps(&ep->ep_in, &ep->ep_out, fn); + if (err) + return err; + } } return 0; @@ -1026,9 +1425,18 @@ 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) - process_ump_transmit(ep); + 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) @@ -1160,8 +1568,8 @@ struct f_midi2_usb_config { /* MIDI 1.0 jacks */ unsigned char jack_in, jack_out, jack_id; - struct usb_midi_in_jack_descriptor jack_ins[16]; - struct usb_midi_out_jack_descriptor_1 jack_outs[16]; + 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) @@ -1422,7 +1830,7 @@ static int f_midi2_init_midi2_ep_in(struct f_midi2 *midi2, int index) fill_midi2_class_desc(ep, &midi2_midi2_ep_in_class_desc[index]); return f_midi2_init_ep(midi2, ep, &ep->ep_in, desc, - midi2->info.num_reqs, f_midi2_ep_in_complete); + f_midi2_ep_in_complete); } /* initialize MIDI2 EP-out */ @@ -1439,7 +1847,7 @@ static int f_midi2_init_midi2_ep_out(struct f_midi2 *midi2, int index) fill_midi2_class_desc(ep, &midi2_midi2_ep_out_class_desc[index]); return f_midi2_init_ep(midi2, ep, &ep->ep_out, desc, - midi2->info.num_reqs, f_midi2_ep_out_complete); + f_midi2_ep_out_complete); } /* gadget function bind callback */ @@ -1504,14 +1912,16 @@ static int f_midi2_bind(struct usb_configuration *c, struct usb_function *f) /* 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, 0, NULL); + &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, 0, NULL); + &midi2_midi1_ep_out_desc, + f_midi2_midi1_ep_out_complete); if (status) goto fail; } From patchwork Tue Jul 25 06:22:04 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Takashi Iwai X-Patchwork-Id: 125391 Return-Path: Delivered-To: ouuuleilei@gmail.com Received: by 2002:a59:9010:0:b0:3e4:2afc:c1 with SMTP id l16csp2303923vqg; Tue, 25 Jul 2023 00:48:51 -0700 (PDT) X-Google-Smtp-Source: APBJJlGLeng+snyHTsr+tKr6DrSvT+iwF+e4hix75FNJynPO2/absaVCP19Z6NWYEXziJ7cR6jrd X-Received: by 2002:a05:6808:bc3:b0:3a1:f0dc:def3 with SMTP id o3-20020a0568080bc300b003a1f0dcdef3mr14558775oik.41.1690271330786; Tue, 25 Jul 2023 00:48:50 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1690271330; cv=none; d=google.com; s=arc-20160816; b=Jku+1CQ3rmP8jdC7Bc0Rk4q/RRjlR1oJNjtDW/hftTTPusz90aFDLV/DCSAWXFDfvZ xQbAnueSTo2T4l7dCe8ZlGSguQsBR9kLSOplQQIlvb1ZMWpJj6227Dfohc5iM6XewPkS a3EK5I3Mln4LtQ/WqjSr8lRMJL7mT2PJV7Zqp09PwfOF3fgt4/vODgQBIqvUvXqTMVFs 1DCvGCbp9PjerpzyEjZybaghj4O1MS5safw7tqm7RfUVUczPvM9h3YbZwWm3iP89gx7h bnuSo0yZAQcF5EGN0C9H3nHo13G4M38O4k9a9fc007lRN0xA7bbNqtBjrocvZeFIVvd6 rxbg== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:content-transfer-encoding:mime-version :references:in-reply-to:message-id:date:subject:cc:to:from :dkim-signature:dkim-signature; bh=38Kf27qLGwtr7mNgdf5byvs73kGBmPsZ0oBHrWVAK5U=; fh=BbVOx8xHLmKqd0mM1otOmPdkL6v0ikLC29Avu+/pNMM=; b=ZLPTSlVCKtGc2e+tqeuYnl33QYX7UCOfvvWPCHY7c1gySkKLZAZ/fkeqlVHV7RmjN+ x81UVJmkFZsHpOLGZsP+mipYK+TcBh990hv5DXEcYqMOrr4IFrwADsXMx1KZAcF/z4CH l4VtpA6IZUmII4HqpJZHw3EkEoK19JXh3FfHMxxGnkmHa0l/OZyn080X03+q8kYRQSbE xa0KEpO2EgFWeZiF3j/MUCTnI3kB+umpH2rMO/ydGSC4QAnZqOtjHZ3roWQGhhHgFHGL 8qnnusnathf//ZNcoTx7uMzbT59qpw3raFNlC12/tGQzv6XiGFHQ5qvtoLbBh5X+N8U+ nSYg== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@suse.de header.s=susede2_rsa header.b=k0QQ3EIa; dkim=neutral (no key) header.i=@suse.de header.s=susede2_ed25519 header.b=igRWm8yi; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=suse.de Received: from out1.vger.email (out1.vger.email. [2620:137:e000::1:20]) by mx.google.com with ESMTP id e17-20020a17090a9a9100b00261266bf8b8si13636080pjp.179.2023.07.25.00.48.37; Tue, 25 Jul 2023 00:48:50 -0700 (PDT) Received-SPF: pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) client-ip=2620:137:e000::1:20; Authentication-Results: mx.google.com; dkim=pass header.i=@suse.de header.s=susede2_rsa header.b=k0QQ3EIa; dkim=neutral (no key) header.i=@suse.de header.s=susede2_ed25519 header.b=igRWm8yi; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=suse.de Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231792AbjGYGWT (ORCPT + 99 others); Tue, 25 Jul 2023 02:22:19 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:43046 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231254AbjGYGWP (ORCPT ); Tue, 25 Jul 2023 02:22:15 -0400 Received: from smtp-out1.suse.de (smtp-out1.suse.de [IPv6:2001:67c:2178:6::1c]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 250BF10FA; Mon, 24 Jul 2023 23:22:13 -0700 (PDT) Received: from imap2.suse-dmz.suse.de (imap2.suse-dmz.suse.de [192.168.254.74]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (P-521) server-digest SHA512) (No client certificate requested) by smtp-out1.suse.de (Postfix) with ESMTPS id 948D7224B1; Tue, 25 Jul 2023 06:22:12 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=suse.de; s=susede2_rsa; t=1690266132; h=from:from:reply-to:date:date:message-id:message-id:to:to:cc:cc: mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=38Kf27qLGwtr7mNgdf5byvs73kGBmPsZ0oBHrWVAK5U=; b=k0QQ3EIaWdzg6N065CdlgUBl13ElrvnYlI/5vgNCVFG2LgzTPigbYzY1l8GUa8ocaJXkxV eWZLLR3/msQFV/oOGy0tid19Yqg37dgfpkzDGfNeA9yo3bwnI5DzwK3gqQnM+bbfm/Moaj pgzBPeAw2aE4Iw2P3CA5WHUxgnSGQQk= DKIM-Signature: v=1; a=ed25519-sha256; c=relaxed/relaxed; d=suse.de; s=susede2_ed25519; t=1690266132; h=from:from:reply-to:date:date:message-id:message-id:to:to:cc:cc: mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=38Kf27qLGwtr7mNgdf5byvs73kGBmPsZ0oBHrWVAK5U=; b=igRWm8yi487PeE6Zy2MbG8e13BHbkKPiD5Sts4RIicjp9/T+3EwwSO8jtgnOIbEQztTtlA N+I4mUp3MGJ+juCw== Received: from imap2.suse-dmz.suse.de (imap2.suse-dmz.suse.de [192.168.254.74]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (P-521) server-digest SHA512) (No client certificate requested) by imap2.suse-dmz.suse.de (Postfix) with ESMTPS id 6F61A1390F; Tue, 25 Jul 2023 06:22:12 +0000 (UTC) Received: from dovecot-director2.suse.de ([192.168.254.65]) by imap2.suse-dmz.suse.de with ESMTPSA id 2Ng0GhRqv2S0dQAAMHmgww (envelope-from ); Tue, 25 Jul 2023 06:22:12 +0000 From: Takashi Iwai To: Greg Kroah-Hartman Cc: alsa-devel@alsa-project.org, linux-usb@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH 5/7] usb: gadget: midi2: Add testing documentation Date: Tue, 25 Jul 2023 08:22:04 +0200 Message-Id: <20230725062206.9674-6-tiwai@suse.de> X-Mailer: git-send-email 2.35.3 In-Reply-To: <20230725062206.9674-1-tiwai@suse.de> References: <20230725062206.9674-1-tiwai@suse.de> MIME-Version: 1.0 X-Spam-Status: No, score=-2.1 required=5.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,RCVD_IN_DNSWL_BLOCKED, SPF_HELO_NONE,SPF_PASS,T_SCC_BODY_TEXT_LINE,URIBL_BLOCKED autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on lindbergh.monkeyblade.net Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org X-getmail-retrieved-from-mailbox: INBOX X-GMAIL-THRID: 1772377950675697779 X-GMAIL-MSGID: 1772377950675697779 Describing brief testing of the new MIDI 2.0 function driver. Signed-off-by: Takashi Iwai --- Documentation/usb/gadget-testing.rst | 141 +++++++++++++++++++++++++++ 1 file changed, 141 insertions(+) diff --git a/Documentation/usb/gadget-testing.rst b/Documentation/usb/gadget-testing.rst index 2fca40443dc9..0f3708ae5bc8 100644 --- a/Documentation/usb/gadget-testing.rst +++ b/Documentation/usb/gadget-testing.rst @@ -27,6 +27,7 @@ provided by gadgets. 18. UVC function 19. PRINTER function 20. UAC1 function (new API) + 21. MIDI2 function 1. ACM function @@ -965,3 +966,143 @@ e.g.:: $ arecord -f dat -t wav -D hw:CARD=UAC1Gadget,DEV=0 | \ aplay -D default:CARD=OdroidU3 + + +21. MIDI2 function +================== + +The function is provided by usb_f_midi2.ko module. +It will create a virtual ALSA card containing a UMP rawmidi device +where the UMP packet is looped back. In addition, a legacy rawmidi +device is created. The UMP rawmidi is bound with ALSA sequencer +clients, too. + +Function-specific configfs interface +------------------------------------ + +The function name to use when creating the function directory is "midi2". +The midi2 function provides these attributes in its function directory +as the card top-level information: + + ============= ================================================= + process_ump Bool flag to process UMP Stream messages (0 or 1) + static_block Bool flag for static blocks (0 or 1) + iface_name Optional interface name string + ============= ================================================= + +The directory contains a subdirectory "ep.0", and this provides the +attributes for a UMP Endpoint (which is a pair of USB MIDI Endpoints): + + ============= ================================================= + protocol_caps MIDI protocol capabilities; + 1: MIDI 1.0, 2: MIDI 2.0, or 3: both protocols + protocol Default MIDI protocol (either 1 or 2) + ep_name UMP Endpoint name string + product_id Product ID string + manufacturer Manufacture ID number (24 bit) + family Device family ID number (16 bit) + model Device model ID number (16 bit) + sw_revision Software revision (32 bit) + ============= ================================================= + +Each Endpoint subdirectory contains a subdirectory "block.0", which +represents the Function Block for Block 0 information. +Its attributes are: + + =============== =============================================== + name Function Block name string + direction Direction of this FB + 1: input, 2: output, or 3: bidirectional + first_group The first UMP Group number (0-15) + num_groups The number of groups in this FB (1-16) + ui_hint UI-hint of this FB + 0: unknown, 1: receiver, 2: sender, 3: both + midi_ci_verison Supported MIDI-CI version number (8 bit) + is_midi1 Legacy MIDI 1.0 device (0-2) + 0: MIDI 2.0 device, + 1: MIDI 1.0 without restriction, or + 2: MIDI 1.0 with low speed + sysex8_streams Max number of SysEx8 streams (8 bit) + active Bool flag for FB activity (0 or 1) + =============== =============================================== + +If multiple Function Blocks are required, you can add more Function +Blocks by creating subdirectories "block." with the corresponding +Function Block number (1, 2, ....). The FB subdirectories can be +dynamically removed, too. Note that the Function Block numbers must be +continuous. + +Similarly, if you multiple UMP Endpoints are required, you can add +more Endpoints by creating subdirectories "ep.". The number must +be continuous. + +For emulating the old MIDI 2.0 device without UMP v1.1 support, pass 0 +to `process_ump` flag. Then the whole UMP v1.1 requests are ignored. + +Testing the MIDI2 function +-------------------------- + +On the device: run the gadget, and running:: + + $ cat /proc/asound/cards + +will show a new sound card containing a MIDI2 device. + +OTOH, on the host:: + + $ cat /proc/asound/cards + +will show a new sound card containing either MIDI1 or MIDI2 device, +depending on the USB audio driver configuration. + +On both, when ALSA sequencer is enabled on the host, you can find the +UMP MIDI client such as "MIDI 2.0 Gadget". + +As the driver simply loops back the data, there is no need for a real +device just for testing. + +For testing a MIDI input from the gadget to the host (e.g. emulating a +MIDI keyboard), you can send a MIDI stream like the following. + +On the gadget:: + + $ aconnect -o + .... + client 20: 'MIDI 2.0 Gadget' [type=kernel,card=1] + 0 'MIDI 2.0 ' + 1 'Group 1 (MIDI 2.0 Gadget I/O)' + $ aplaymidi -p 20:1 to_host.mid + +On the host:: + + $ aconnect -i + .... + client 24: 'MIDI 2.0 Gadget' [type=kernel,card=2] + 0 'MIDI 2.0 ' + 1 'Group 1 (MIDI 2.0 Gadget I/O)' + $ arecordmidi -p 24:1 from_gadget.mid + +If you have a UMP-capable application, you can use the UMP port to +send/receive the raw UMP packets, too. For example, aseqdump program +with UMP support can receive from UMP port. On the host:: + + $ aseqdump -u 2 -p 24:1 + Waiting for data. Press Ctrl+C to end. + Source Group Event Ch Data + 24:1 Group 0, Program change 0, program 0, Bank select 0:0 + 24:1 Group 0, Channel pressure 0, value 0x80000000 + +For testing a MIDI output to the gadget to the host (e.g. emulating a +MIDI synth), it'll be just other way round. + +On the gadget:: + + $ arecordmidi -p 20:1 from_host.mid + +On the host:: + + $ aplaymidi -p 24:1 to_gadget.mid + +The access to MIDI 1.0 on altset 0 on the host is supported, and it's +translated from/to UMP packets on the gadget. It's bound to only +Function Block 0. From patchwork Tue Jul 25 06:22:05 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Takashi Iwai X-Patchwork-Id: 125349 Return-Path: Delivered-To: ouuuleilei@gmail.com Received: by 2002:a59:9010:0:b0:3e4:2afc:c1 with SMTP id l16csp2279004vqg; Mon, 24 Jul 2023 23:49:01 -0700 (PDT) X-Google-Smtp-Source: APBJJlGhJJ0mJNQROT7qCI/MOSAVn4ncBvuhR0eMd/U+onXmS+jlr29zKQJSX+/XTUA3ggvqg4oN X-Received: by 2002:a17:906:6a1b:b0:98e:1c4b:10e2 with SMTP id qw27-20020a1709066a1b00b0098e1c4b10e2mr1640877ejc.20.1690267741190; Mon, 24 Jul 2023 23:49:01 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1690267741; cv=none; d=google.com; s=arc-20160816; b=ME6Ey8RgvZrhPtOHZekgN/5fl2otxYO8bWPhvEcEbGfoPTRpBKI3XyU6dQ1QSHqwU0 Y7gAb7Ty+4KG14HNHUl8ASRk7w1GS5t5ncNRnj9Z74gnX4Qqipo4FUmqf6hmyXHo4EcJ Qi1a5L1cu+EP7BnNxAVbR7d7bsRqVdkWjDNow5UoDA2K6nunShoEWYWBdlx1UtpnVNMn fQlO0d32mIdJWwuHRhuM/LoMltoKWmKR+HlDiFFtBRgElQVtKEvHBfhzmBf5YBAvcyEY 2lG+iOnOAXE+hlj6pyGQFt7gV2jRTgrR+AX2mGaAEf8Ty3NN55RzGGDt35BpK30WL0v8 rncA== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:content-transfer-encoding:mime-version :references:in-reply-to:message-id:date:subject:cc:to:from :dkim-signature:dkim-signature; bh=oArbKxaeZCPtqMVbiTY1Kkik4trkyVoAFJEqdy+srBU=; fh=BbVOx8xHLmKqd0mM1otOmPdkL6v0ikLC29Avu+/pNMM=; b=xvE7Vkn1k+0orAEWgdUfSGQptHsy2gV+pUPaSLFBDCzUdRN/5UgpqoGSxgjHrk1qKq 1M22NlwbNqsEvstx5xKB3YJkuLuiAT7CVvp9OKQ1uYCoQSqX0S3t4On5empeghOqlfHW IyvvrmhoF6xdPqIP2G+PacCgZ0gMfdq3k3WaFQzqgv0+REeAHR73WcLSm2W+JRse8YtP m5dXu8a2kSZGI31gDonTSP+DKjqTkAP8t4POBhor+QZGMy5fhyhT1/P5jhPGbE6boNkD eaz3sO1W3nA01KV9naaenbp4F8L6KtoXLp1rDYmBBfmhun+1XTSjU51t6PrYhQFfbfMV fzbA== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@suse.de header.s=susede2_rsa header.b="2Ig4JsL/"; dkim=neutral (no key) header.i=@suse.de; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=suse.de Received: from out1.vger.email (out1.vger.email. [2620:137:e000::1:20]) by mx.google.com with ESMTP id j24-20020a170906831800b009659573b4e3si7779701ejx.51.2023.07.24.23.48.38; Mon, 24 Jul 2023 23:49:01 -0700 (PDT) Received-SPF: pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) client-ip=2620:137:e000::1:20; Authentication-Results: mx.google.com; dkim=pass header.i=@suse.de header.s=susede2_rsa header.b="2Ig4JsL/"; dkim=neutral (no key) header.i=@suse.de; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=suse.de Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S232058AbjGYGWk (ORCPT + 99 others); Tue, 25 Jul 2023 02:22:40 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:43064 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231640AbjGYGWS (ORCPT ); Tue, 25 Jul 2023 02:22:18 -0400 Received: from smtp-out2.suse.de (smtp-out2.suse.de [195.135.220.29]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 26BB81990; Mon, 24 Jul 2023 23:22:14 -0700 (PDT) Received: from imap2.suse-dmz.suse.de (imap2.suse-dmz.suse.de [192.168.254.74]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (P-521) server-digest SHA512) (No client certificate requested) by smtp-out2.suse.de (Postfix) with ESMTPS id BECB81F8B4; Tue, 25 Jul 2023 06:22:12 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=suse.de; s=susede2_rsa; t=1690266132; h=from:from:reply-to:date:date:message-id:message-id:to:to:cc:cc: mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=oArbKxaeZCPtqMVbiTY1Kkik4trkyVoAFJEqdy+srBU=; b=2Ig4JsL/QSHJg3WDWvvrzuyGDYCR18LMLIt57WwZyLoTCo1GgcY7Xzy7f2AyB986CNYRxs CHwg6Z+t2ZGgJIdZeq/g3bvdkaW48M8CxDDyrkjNL8x3yFTOzGriyPYhKSWri7jfdn7FzI 98m6R867vuat2R/nZI11btxhP7/3dGM= DKIM-Signature: v=1; a=ed25519-sha256; c=relaxed/relaxed; d=suse.de; s=susede2_ed25519; t=1690266132; h=from:from:reply-to:date:date:message-id:message-id:to:to:cc:cc: mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=oArbKxaeZCPtqMVbiTY1Kkik4trkyVoAFJEqdy+srBU=; b=xDjJ4JW01JxO/FJbwOQOcph3kuzi6/7sya8pmT73vBkumw52evG5v2znOBlSqF7R9s38Z5 h/NBePTu1WYBIaAQ== Received: from imap2.suse-dmz.suse.de (imap2.suse-dmz.suse.de [192.168.254.74]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (P-521) server-digest SHA512) (No client certificate requested) by imap2.suse-dmz.suse.de (Postfix) with ESMTPS id 990DF13342; Tue, 25 Jul 2023 06:22:12 +0000 (UTC) Received: from dovecot-director2.suse.de ([192.168.254.65]) by imap2.suse-dmz.suse.de with ESMTPSA id CB5+JBRqv2S0dQAAMHmgww (envelope-from ); Tue, 25 Jul 2023 06:22:12 +0000 From: Takashi Iwai To: Greg Kroah-Hartman Cc: alsa-devel@alsa-project.org, linux-usb@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH 6/7] usb: gadget: midi2: Add "Operation Mode" control Date: Tue, 25 Jul 2023 08:22:05 +0200 Message-Id: <20230725062206.9674-7-tiwai@suse.de> X-Mailer: git-send-email 2.35.3 In-Reply-To: <20230725062206.9674-1-tiwai@suse.de> References: <20230725062206.9674-1-tiwai@suse.de> MIME-Version: 1.0 X-Spam-Status: No, score=-2.1 required=5.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,RCVD_IN_DNSWL_BLOCKED, SPF_HELO_NONE,SPF_PASS,T_SCC_BODY_TEXT_LINE,URIBL_BLOCKED autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on lindbergh.monkeyblade.net Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org X-getmail-retrieved-from-mailbox: INBOX X-GMAIL-THRID: 1772374186451962662 X-GMAIL-MSGID: 1772374186451962662 Add a new ALSA control element to watch the current operation mode (MIDI 1.0 or MIDI 2.0). It's a read-only control that reflects the current value of altsetting, and 0 means unused, 1 for MIDI 1.0 (altset 0) and 2 for MIDI 2.0 (altset 1). Signed-off-by: Takashi Iwai --- Documentation/usb/gadget-testing.rst | 11 +++++++++ drivers/usb/gadget/function/f_midi2.c | 35 +++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/Documentation/usb/gadget-testing.rst b/Documentation/usb/gadget-testing.rst index 0f3708ae5bc8..1fb181d61322 100644 --- a/Documentation/usb/gadget-testing.rst +++ b/Documentation/usb/gadget-testing.rst @@ -1106,3 +1106,14 @@ On the host:: The access to MIDI 1.0 on altset 0 on the host is supported, and it's translated from/to UMP packets on the gadget. It's bound to only Function Block 0. + +The current operation mode can be observed in ALSA control element +"Operation Mode" for SND_CTL_IFACE_RAWMIDI. For example:: + + $ amixer -c1 contents + numid=1,iface=RAWMIDI,name='Operation Mode' + ; type=INTEGER,access=r--v----,values=1,min=0,max=2,step=0 + : values=2 + +where 0 = unused, 1 = MIDI 1.0 (altset 0), 2 = MIDI 2.0 (altset 1). +The example above shows it's running in 2, i.e. MIDI 2.0. diff --git a/drivers/usb/gadget/function/f_midi2.c b/drivers/usb/gadget/function/f_midi2.c index a368ac51d349..ec9ef15abfea 100644 --- a/drivers/usb/gadget/function/f_midi2.c +++ b/drivers/usb/gadget/function/f_midi2.c @@ -9,6 +9,7 @@ #include #include +#include #include #include #include @@ -1450,6 +1451,36 @@ static const struct snd_ump_ops f_midi2_ump_ops = { .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 */ @@ -1547,6 +1578,10 @@ static int f_midi2_create_card(struct f_midi2 *midi2) 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; From patchwork Tue Jul 25 06:22:06 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Takashi Iwai X-Patchwork-Id: 125353 Return-Path: Delivered-To: ouuuleilei@gmail.com Received: by 2002:a59:9010:0:b0:3e4:2afc:c1 with SMTP id l16csp2283565vqg; Tue, 25 Jul 2023 00:00:41 -0700 (PDT) X-Google-Smtp-Source: APBJJlEVpkwOTa2Mq7aWNTg5efZL7FyQpEyWtqL4/cpeZGI6i0cFFpb0IJnKWqj0bwzpDLI8Umet X-Received: by 2002:a05:6a20:8e24:b0:135:bf8d:b758 with SMTP id y36-20020a056a208e2400b00135bf8db758mr2373078pzj.16.1690268441274; Tue, 25 Jul 2023 00:00:41 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1690268441; cv=none; d=google.com; s=arc-20160816; b=oZNt6DqNh0f2r0mnRnG8NKZ0mZ9TcVQxvfKO9MKkNdPkL8VMTHL5vHxSMVGPSZOrEV TE6FuyDO+sC8lOb93FxtTkC1RlzEO564Y0jIbsqCBssYTfG6uLleEUK2ve3XEAZT8OHZ eCZ1+FQ2F0G/+6FvmEL8VMEbi3MydRWgsiITJywoRofbAxFSrGNllL5Mc+JRSAsLpVZC wX1hKBlEdLX+LVpHscxg4ICXUneKTMNiMxbTJmuwJZebcjMb3l51gwRpHNRPQnnIHM6E KhYin5M/FWwm2Q5goH0x+sbFkxRUkLLmXf5j5Pu0PBZZO7WBtRZfkxi7qHb1uqkTUqzS a2Ww== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:content-transfer-encoding:mime-version :references:in-reply-to:message-id:date:subject:cc:to:from :dkim-signature:dkim-signature; bh=8zpF+SUkasNIWZkOfvZPls/jKPiZoHAEyqDm14jWo0I=; fh=BbVOx8xHLmKqd0mM1otOmPdkL6v0ikLC29Avu+/pNMM=; b=g6yIzqdsVrtO5EOAV988N3LcdLLy4HtvMjNgnlNkqHtF976kNVNxJmHWDGp8j5QNly UGoGcL6s8uTH1rydYjq2Yz6hIfD5pXqCLbQcTkqiXQ5VMPKygnY19a5jQwi6ynW2wtL9 q79a6hkqw3gS9a+VsWONlaG3LrE45piKUyfGC7uKvDwE9JETXqNbgXlrACGSFn0PI2v5 GyaspESYS/NTkNSRYDt9UigCVL8uJKIQCJrntTkWSBFk6bwDLp4xAEqvIIDwr99a+5bH 2Dbn9cOg2e+im5RLlH02st5eBgKAtgcrxKp+uBMVzs8FDyzw4Vf1hCQqiVB7lJOA8MjW aQfg== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@suse.de header.s=susede2_rsa header.b=FPbrBhG1; dkim=neutral (no key) header.i=@suse.de header.b=2QK4mNqY; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=suse.de Received: from out1.vger.email (out1.vger.email. [2620:137:e000::1:20]) by mx.google.com with ESMTP id v4-20020a63f204000000b0055798b90375si11577315pgh.709.2023.07.25.00.00.25; Tue, 25 Jul 2023 00:00:41 -0700 (PDT) Received-SPF: pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) client-ip=2620:137:e000::1:20; Authentication-Results: mx.google.com; dkim=pass header.i=@suse.de header.s=susede2_rsa header.b=FPbrBhG1; dkim=neutral (no key) header.i=@suse.de header.b=2QK4mNqY; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=suse.de Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S232128AbjGYGWs (ORCPT + 99 others); Tue, 25 Jul 2023 02:22:48 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:43066 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231641AbjGYGWS (ORCPT ); Tue, 25 Jul 2023 02:22:18 -0400 Received: from smtp-out2.suse.de (smtp-out2.suse.de [195.135.220.29]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 49CAE199F; Mon, 24 Jul 2023 23:22:14 -0700 (PDT) Received: from imap2.suse-dmz.suse.de (imap2.suse-dmz.suse.de [192.168.254.74]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (P-521) server-digest SHA512) (No client certificate requested) by smtp-out2.suse.de (Postfix) with ESMTPS id EDEEB1FD5F; Tue, 25 Jul 2023 06:22:12 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=suse.de; s=susede2_rsa; t=1690266132; h=from:from:reply-to:date:date:message-id:message-id:to:to:cc:cc: mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=8zpF+SUkasNIWZkOfvZPls/jKPiZoHAEyqDm14jWo0I=; b=FPbrBhG1xYDJ0nukBafO+w9XnYaYRRIJP+tHjBzL3ZxiEgOztLNp1BTEgOCnnWqS2kraE3 o+d4p4LIIsIfJo9FKKpe+O/E9r/NlwWrTOUqY8mHYilgrZWTpOkFtNZ3FbHWdmL0uXG8Dx ERwjP6v5kMJ30fmN8hLk3zuztjONolg= DKIM-Signature: v=1; a=ed25519-sha256; c=relaxed/relaxed; d=suse.de; s=susede2_ed25519; t=1690266132; h=from:from:reply-to:date:date:message-id:message-id:to:to:cc:cc: mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=8zpF+SUkasNIWZkOfvZPls/jKPiZoHAEyqDm14jWo0I=; b=2QK4mNqYOktfyFo9ydlm3Y3pDHY9KYqGe51P+fTRPJxQMJbVqm59sSUBBwE8+7Ml8oQ5Im R4Zbme2YdE3JAUAQ== Received: from imap2.suse-dmz.suse.de (imap2.suse-dmz.suse.de [192.168.254.74]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (P-521) server-digest SHA512) (No client certificate requested) by imap2.suse-dmz.suse.de (Postfix) with ESMTPS id C1EF71390F; Tue, 25 Jul 2023 06:22:12 +0000 (UTC) Received: from dovecot-director2.suse.de ([192.168.254.65]) by imap2.suse-dmz.suse.de with ESMTPSA id cIyOLhRqv2S0dQAAMHmgww (envelope-from ); Tue, 25 Jul 2023 06:22:12 +0000 From: Takashi Iwai To: Greg Kroah-Hartman Cc: alsa-devel@alsa-project.org, linux-usb@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH 7/7] usb: gadget: midi2: More flexible MIDI 1.0 configuration Date: Tue, 25 Jul 2023 08:22:06 +0200 Message-Id: <20230725062206.9674-8-tiwai@suse.de> X-Mailer: git-send-email 2.35.3 In-Reply-To: <20230725062206.9674-1-tiwai@suse.de> References: <20230725062206.9674-1-tiwai@suse.de> MIME-Version: 1.0 X-Spam-Status: No, score=-2.1 required=5.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,RCVD_IN_DNSWL_BLOCKED, SPF_HELO_NONE,SPF_PASS,T_SCC_BODY_TEXT_LINE,URIBL_BLOCKED autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on lindbergh.monkeyblade.net Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org X-getmail-retrieved-from-mailbox: INBOX X-GMAIL-THRID: 1772374920919527556 X-GMAIL-MSGID: 1772374920919527556 This patch allows users to set up MIDI 1.0 ports more flexibly. Namely, instead of the fixed mapping only from FB 0, now multiple block definitions are applied to build up the MIDI 1.0 mapping. The each block config has midi1_first_group and midi1_num_groups attributes, and those specify which Groups are used for MIDI 1.0. Those fields must be within the UMP Groups defined in the block itself. Signed-off-by: Takashi Iwai --- .../ABI/testing/configfs-usb-gadget-midi2 | 24 +-- Documentation/usb/gadget-testing.rst | 34 ++-- drivers/usb/gadget/function/f_midi2.c | 154 ++++++++++++++---- drivers/usb/gadget/function/u_midi2.h | 2 + 4 files changed, 153 insertions(+), 61 deletions(-) diff --git a/Documentation/ABI/testing/configfs-usb-gadget-midi2 b/Documentation/ABI/testing/configfs-usb-gadget-midi2 index a3a036d784c7..0eac3aaba137 100644 --- a/Documentation/ABI/testing/configfs-usb-gadget-midi2 +++ b/Documentation/ABI/testing/configfs-usb-gadget-midi2 @@ -39,14 +39,16 @@ Description: The attributes: - =============== =============================================== - name Function Block name string - direction 1: input, 2: output, 3: bidirectional - first_group The first UMP Group number (0-15) - num_groups The number of groups in this FB (1-16) - ui_hint 0: unknown, 1: receiver, 2: sender, 3: both - midi_ci_verison Supported MIDI-CI version number (8 bit) - is_midi1 Legacy MIDI 1.0 device (0, 1 or 2) - sysex8_streams Max number of SysEx8 streams (8 bit) - active Active FB flag (0 or 1) - =============== =============================================== + ================= ============================================== + name Function Block name string + direction 1: input, 2: output, 3: bidirectional + first_group The first UMP Group number (0-15) + num_groups The number of groups in this FB (1-16) + midi1_first_group The first UMP Group number for MIDI 1.0 (0-15) + midi1_num_groups The number of groups for MIDI 1.0 (0-16) + ui_hint 0: unknown, 1: receiver, 2: sender, 3: both + midi_ci_verison Supported MIDI-CI version number (8 bit) + is_midi1 Legacy MIDI 1.0 device (0, 1 or 2) + sysex8_streams Max number of SysEx8 streams (8 bit) + active Active FB flag (0 or 1) + ================= ============================================== diff --git a/Documentation/usb/gadget-testing.rst b/Documentation/usb/gadget-testing.rst index 1fb181d61322..394cd226bfae 100644 --- a/Documentation/usb/gadget-testing.rst +++ b/Documentation/usb/gadget-testing.rst @@ -1009,22 +1009,24 @@ Each Endpoint subdirectory contains a subdirectory "block.0", which represents the Function Block for Block 0 information. Its attributes are: - =============== =============================================== - name Function Block name string - direction Direction of this FB - 1: input, 2: output, or 3: bidirectional - first_group The first UMP Group number (0-15) - num_groups The number of groups in this FB (1-16) - ui_hint UI-hint of this FB - 0: unknown, 1: receiver, 2: sender, 3: both - midi_ci_verison Supported MIDI-CI version number (8 bit) - is_midi1 Legacy MIDI 1.0 device (0-2) - 0: MIDI 2.0 device, - 1: MIDI 1.0 without restriction, or - 2: MIDI 1.0 with low speed - sysex8_streams Max number of SysEx8 streams (8 bit) - active Bool flag for FB activity (0 or 1) - =============== =============================================== + ================= =============================================== + name Function Block name string + direction Direction of this FB + 1: input, 2: output, or 3: bidirectional + first_group The first UMP Group number (0-15) + num_groups The number of groups in this FB (1-16) + midi1_first_group The first UMP Group number for MIDI 1.0 (0-15) + midi1_num_groups The number of groups for MIDI 1.0 (0-16) + ui_hint UI-hint of this FB + 0: unknown, 1: receiver, 2: sender, 3: both + midi_ci_verison Supported MIDI-CI version number (8 bit) + is_midi1 Legacy MIDI 1.0 device (0-2) + 0: MIDI 2.0 device, + 1: MIDI 1.0 without restriction, or + 2: MIDI 1.0 with low speed + sysex8_streams Max number of SysEx8 streams (8 bit) + active Bool flag for FB activity (0 or 1) + ================= =============================================== If multiple Function Blocks are required, you can add more Function Blocks by creating subdirectories "block." with the corresponding diff --git a/drivers/usb/gadget/function/f_midi2.c b/drivers/usb/gadget/function/f_midi2.c index ec9ef15abfea..f1c47753e0c1 100644 --- a/drivers/usb/gadget/function/f_midi2.c +++ b/drivers/usb/gadget/function/f_midi2.c @@ -84,6 +84,8 @@ struct f_midi2_ep { 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 */ @@ -95,6 +97,13 @@ enum { /* 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 */ @@ -112,10 +121,17 @@ struct f_midi2 { 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 */ @@ -917,8 +933,7 @@ static bool process_midi1_pending_buf(struct f_midi2 *midi2, { unsigned int cable, c; - for (cable = 0; cable < midi2->midi2_eps[0].blks[0].info.num_groups; - cable++) { + for (cable = 0; cable < midi2->num_midi1_in; cable++) { struct f_midi2_midi1_port *port = &midi2->midi1_port[cable]; if (!port->pending) @@ -960,8 +975,8 @@ static void process_midi1_transmit(struct f_midi2 *midi2) 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; - int len, size, cable; + unsigned char group, cable; + int len, size; u32 ump; if (!usb_ep->usb_ep || !usb_ep->usb_ep->enabled) @@ -986,9 +1001,10 @@ static void process_midi1_transmit(struct f_midi2 *midi2) &group); if (size <= 0) continue; - cable = group - ep->blks[0].info.first_group; - if (cable < 0 || cable >= ep->blks[0].info.num_groups) + cable = ep->in_group_to_cable[group]; + if (!cable) continue; + cable--; /* to 0-base */ fill_midi1_pending_buf(midi2, cable, outbuf, size); } @@ -1025,12 +1041,12 @@ static void f_midi2_midi1_ep_out_complete(struct usb_ep *usb_ep, { struct f_midi2_req_ctx *ctx = req->context; struct f_midi2 *midi2 = ctx->usb_ep->card; - struct f_midi2_ep *ep = &midi2->midi2_eps[0]; + 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, bytes, c, len; + unsigned int group, cable, bytes, c, len; int status = req->status; const u8 *buf = req->buf; @@ -1042,10 +1058,11 @@ static void f_midi2_midi1_ep_out_complete(struct usb_ep *usb_ep, len = req->actual >> 2; for (; len; len--, buf += 4) { - group = *buf >> 4; - if (group >= ep->blks[0].info.num_groups) + cable = *buf >> 4; + ep = midi2->out_cable_mapping[cable].ep; + if (!ep) continue; - group += ep->blks[0].info.first_group; + 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, @@ -1641,6 +1658,7 @@ static int append_configs(struct f_midi2_usb_config *config, void **d) 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 = @@ -1653,7 +1671,9 @@ static int append_midi1_in_jack(struct f_midi2 *midi2, jack->bDescriptorSubtype = USB_MS_MIDI_IN_JACK; jack->bJackType = type; jack->bJackID = id; - jack->iJack = midi2->strings[STR_GTB1].id; // TODO: better names? + /* 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) @@ -1663,6 +1683,7 @@ static int append_midi1_in_jack(struct f_midi2 *midi2, 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 = @@ -1678,7 +1699,9 @@ static int append_midi1_out_jack(struct f_midi2 *midi2, jack->bNrInputPins = 1; jack->pins[0].baSourceID = source; jack->pins[0].baSourcePin = 0x01; - jack->iJack = midi2->strings[STR_GTB1].id; // TODO: better names? + /* 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) @@ -1690,7 +1713,6 @@ static int f_midi2_create_usb_configs(struct f_midi2 *midi2, struct f_midi2_usb_config *config, int speed) { - struct f_midi2_block *blk = &midi2->midi2_eps[0].blks[0]; void **midi1_in_eps, **midi1_out_eps; int i, jack, total; int err; @@ -1724,56 +1746,55 @@ static int f_midi2_create_usb_configs(struct f_midi2 *midi2, if (err < 0) return err; - switch (blk->info.direction) { - case SNDRV_UMP_DIR_INPUT: - case SNDRV_UMP_DIR_OUTPUT: - midi2_midi1_if_desc.bNumEndpoints = 1; - break; - default: + if (midi2->num_midi1_in && midi2->num_midi1_out) midi2_midi1_if_desc.bNumEndpoints = 2; - break; - } + 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 (blk->info.direction != SNDRV_UMP_DIR_INPUT) { + if (midi2->num_midi1_out) { midi2_midi1_ep_out_class_desc.bLength = - USB_DT_MS_ENDPOINT_SIZE(blk->info.num_groups); + USB_DT_MS_ENDPOINT_SIZE(midi2->num_midi1_out); total += midi2_midi1_ep_out_class_desc.bLength; midi2_midi1_ep_out_class_desc.bNumEmbMIDIJack = - blk->info.num_groups; - total += blk->info.num_groups * + midi2->num_midi1_out; + total += midi2->num_midi1_out * (USB_DT_MIDI_IN_SIZE + USB_DT_MIDI_OUT_SIZE(1)); - for (i = 0; i < blk->info.num_groups; i++) { + 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 (blk->info.direction != SNDRV_UMP_DIR_OUTPUT) { + if (midi2->num_midi1_in) { midi2_midi1_ep_in_class_desc.bLength = - USB_DT_MS_ENDPOINT_SIZE(blk->info.num_groups); + USB_DT_MS_ENDPOINT_SIZE(midi2->num_midi1_in); total += midi2_midi1_ep_in_class_desc.bLength; midi2_midi1_ep_in_class_desc.bNumEmbMIDIJack = - blk->info.num_groups; - total += blk->info.num_groups * + midi2->num_midi1_in; + total += midi2->num_midi1_in * (USB_DT_MIDI_IN_SIZE + USB_DT_MIDI_OUT_SIZE(1)); - for (i = 0; i < blk->info.num_groups; i++) { + 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; @@ -1783,12 +1804,12 @@ static int f_midi2_create_usb_configs(struct f_midi2 *midi2, midi2_midi1_class_desc.wTotalLength = cpu_to_le16(total); - if (blk->info.direction != SNDRV_UMP_DIR_INPUT) { + if (midi2->num_midi1_out) { err = append_configs(config, midi1_out_eps); if (err < 0) return err; } - if (blk->info.direction != SNDRV_UMP_DIR_OUTPUT) { + if (midi2->num_midi1_in) { err = append_configs(config, midi1_in_eps); if (err < 0) return err; @@ -2236,6 +2257,8 @@ 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); @@ -2265,6 +2288,8 @@ 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, @@ -2644,6 +2669,9 @@ static struct usb_function_instance *f_midi2_alloc_inst(void) 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); @@ -2707,6 +2735,16 @@ static int verify_parameters(struct f_midi2_opts *opts) 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) { @@ -2717,6 +2755,46 @@ static int verify_parameters(struct f_midi2_opts *opts) 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) { @@ -2786,9 +2864,17 @@ static struct usb_function *f_midi2_alloc(struct usb_function_instance *fi) 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; } diff --git a/drivers/usb/gadget/function/u_midi2.h b/drivers/usb/gadget/function/u_midi2.h index a68dc2ea035e..4e7adb41dfb7 100644 --- a/drivers/usb/gadget/function/u_midi2.h +++ b/drivers/usb/gadget/function/u_midi2.h @@ -18,6 +18,8 @@ struct f_midi2_block_info { unsigned int direction; /* FB direction: 1-3 */ unsigned int first_group; /* first UMP group: 0-15 */ unsigned int num_groups; /* number of UMP groups: 1-16 */ + unsigned int midi1_first_group; /* first UMP group for MIDI 1.0 */ + unsigned int midi1_num_groups; /* number of UMP groups for MIDI 1.0 */ unsigned int ui_hint; /* UI-hint: 0-3 */ unsigned int midi_ci_version; /* MIDI-CI version: 0-255 */ unsigned int sysex8_streams; /* number of sysex8 streams: 0-255 */