2023-07-25 06:52:14

by Takashi Iwai

[permalink] [raw]
Subject: [PATCH 0/7] usb: Add USB MIDI 2.0 Gadget Function Driver

Hi,

This is a patch set for adding a USB gadget function driver for USB
MIDI 2.0 interface. The MIDI 2.0 support in the host side was already
enabled since 6.5 kernel in snd-usb-audio driver. Now, this provides
a similar gadget functionality using the existing ALSA UMP helper
functions.

The gadget driver provides both MIDI 2.0 and MIDI 1.0 fallback
interfaces with one or more loopback UMP rawmidi devices. The
configfs implementation is flexible enough to allow users to set up
multiple UMP Endpoints and Function Blocks.

The first patch adds the bare f_midi2 driver without configfs support,
followed by the configfs support, MIDI 1.0 interface support and a few
other extensions and documentation.


Takashi

===

Takashi Iwai (7):
usb: gadget: Add support for USB MIDI 2.0 function driver
usb: gadget: midi2: Add configfs support
usb: gadget: midi2: Dynamically create MIDI 1.0 altset descriptors
usb: gadget: midi2: MIDI 1.0 interface (altset 0) support
usb: gadget: midi2: Add testing documentation
usb: gadget: midi2: Add "Operation Mode" control
usb: gadget: midi2: More flexible MIDI 1.0 configuration

.../ABI/testing/configfs-usb-gadget-midi2 | 54 +
Documentation/usb/gadget-testing.rst | 154 +
drivers/usb/gadget/Kconfig | 18 +
drivers/usb/gadget/function/Makefile | 2 +
drivers/usb/gadget/function/f_midi2.c | 2883 +++++++++++++++++
drivers/usb/gadget/function/u_midi2.h | 81 +
6 files changed, 3192 insertions(+)
create mode 100644 Documentation/ABI/testing/configfs-usb-gadget-midi2
create mode 100644 drivers/usb/gadget/function/f_midi2.c
create mode 100644 drivers/usb/gadget/function/u_midi2.h

--
2.35.3



2023-07-25 06:57:53

by Takashi Iwai

[permalink] [raw]
Subject: [PATCH 7/7] usb: gadget: midi2: More flexible MIDI 1.0 configuration

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 <[email protected]>
---
.../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.<num>" 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 */
--
2.35.3


2023-07-25 07:27:55

by Takashi Iwai

[permalink] [raw]
Subject: [PATCH 4/7] usb: gadget: midi2: MIDI 1.0 interface (altset 0) support

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 <[email protected]>
---
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 <sound/core.h>
#include <sound/ump.h>
#include <sound/ump_msg.h>
+#include <sound/ump_convert.h>

#include <linux/usb/ch9.h>
#include <linux/usb/gadget.h>
@@ -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;
}
--
2.35.3


2023-07-25 07:32:25

by Takashi Iwai

[permalink] [raw]
Subject: [PATCH 6/7] usb: gadget: midi2: Add "Operation Mode" control

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 <[email protected]>
---
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 <linux/slab.h>

#include <sound/core.h>
+#include <sound/control.h>
#include <sound/ump.h>
#include <sound/ump_msg.h>
#include <sound/ump_convert.h>
@@ -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;
--
2.35.3


2023-07-25 07:35:39

by Takashi Iwai

[permalink] [raw]
Subject: [PATCH 1/7] usb: gadget: Add support for USB MIDI 2.0 function driver

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 <[email protected]>
---
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 <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+
+#include <sound/core.h>
+#include <sound/ump.h>
+#include <sound/ump_msg.h>
+
+#include <linux/usb/ch9.h>
+#include <linux/usb/gadget.h>
+#include <linux/usb/audio.h>
+#include <linux/usb/midi-v2.h>
+
+#include "u_f.h"
+#include "u_midi2.h"
+
+struct f_midi2;
+struct f_midi2_ep;
+struct f_midi2_usb_ep;
+
+/* Context for each USB request */
+struct f_midi2_req_ctx {
+ struct f_midi2_usb_ep *usb_ep; /* belonging USB EP */
+ unsigned int index; /* array index: 0-31 */
+ struct usb_request *req; /* assigned request */
+};
+
+/* Resources for a USB Endpoint */
+struct f_midi2_usb_ep {
+ struct f_midi2 *card; /* belonging card */
+ struct f_midi2_ep *ep; /* belonging UMP EP (optional) */
+ struct usb_ep *usb_ep; /* assigned USB EP */
+ void (*complete)(struct usb_ep *usb_ep, struct usb_request *req);
+ unsigned long free_reqs; /* bitmap for unused requests */
+ unsigned int num_reqs; /* number of allocated requests */
+ struct f_midi2_req_ctx *reqs; /* request context array */
+};
+
+/* Resources for UMP Function Block (and USB Group Terminal Block) */
+struct f_midi2_block {
+ struct f_midi2_block_info info; /* FB info, copied from configfs */
+ struct snd_ump_block *fb; /* assigned FB */
+ unsigned int gtb_id; /* assigned GTB id */
+ unsigned int string_id; /* assigned string id */
+};
+
+/* 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 <linux/usb/composite.h>
+#include <sound/asound.h>
+
+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 */
--
2.35.3


2023-07-25 08:01:07

by Takashi Iwai

[permalink] [raw]
Subject: [PATCH 3/7] usb: gadget: midi2: Dynamically create MIDI 1.0 altset descriptors

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 <[email protected]>
---
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);
--
2.35.3


2023-07-25 17:25:31

by Greg Kroah-Hartman

[permalink] [raw]
Subject: Re: [PATCH 0/7] usb: Add USB MIDI 2.0 Gadget Function Driver

On Tue, Jul 25, 2023 at 08:21:59AM +0200, Takashi Iwai wrote:
> Hi,
>
> This is a patch set for adding a USB gadget function driver for USB
> MIDI 2.0 interface. The MIDI 2.0 support in the host side was already
> enabled since 6.5 kernel in snd-usb-audio driver. Now, this provides
> a similar gadget functionality using the existing ALSA UMP helper
> functions.
>
> The gadget driver provides both MIDI 2.0 and MIDI 1.0 fallback
> interfaces with one or more loopback UMP rawmidi devices. The
> configfs implementation is flexible enough to allow users to set up
> multiple UMP Endpoints and Function Blocks.
>
> The first patch adds the bare f_midi2 driver without configfs support,
> followed by the configfs support, MIDI 1.0 interface support and a few
> other extensions and documentation.
>

Very cool, thanks for doing this! All now queued up, let's see what the
builders say...

greg k-h