Merge branch 'topic/midi20' into for-next

As the updated MIDI 2.0 spec has been published freshly, this is a
catch up to add the support for new specs, especially UMP v1.1
features, on Linux kernel.

The new UMP v1.1 introduced the concept of Function Blocks (FB), which
is a kind of superset of USB MIDI 2.0 Group Terminal Blocks (GTB).
The patch set adds the support for FB as the primary information
source while keeping the parse of GTB as fallback.  Also UMP v1.1
supports the groupless messages, the protocol switch, static FBs, and
other new fundamental features, and those are supported as well.

Link: https://www.midi.org/midi-articles/details-about-midi-2-0-midi-ci-profiles-and-property-exchange
Link: https://lore.kernel.org/r/20230612081054.17200-1-tiwai@suse.de
Signed-off-by: Takashi Iwai <tiwai@suse.de>
This commit is contained in:
Takashi Iwai 2023-06-13 07:36:39 +02:00
commit 508b662b69
10 changed files with 962 additions and 12 deletions

View File

@ -68,6 +68,15 @@ default instead of the MIDI 1.0 interface (at altset 0). You can
switch back to the binding with the old MIDI 1.0 interface by passing switch back to the binding with the old MIDI 1.0 interface by passing
`midi2_enable=0` option to snd-usb-audio driver module, too. `midi2_enable=0` option to snd-usb-audio driver module, too.
The USB audio driver tries to query the UMP Endpoint and UMP Function
Block information that are provided since UMP v1.1, and builds up the
topology based on those information. When the device is older and
doesn't respond to the new UMP inquiries, the driver falls back and
builds the topology based on Group Terminal Block (GTB) information
from the USB descriptor. Some device might be screwed up by the
unexpected UMP command; in such a case, pass `midi2_probe=0` option to
snd-usb-audio driver for skipping the UMP v1.1 inquiries.
When the MIDI 2.0 device is probed, the kernel creates a rawmidi When the MIDI 2.0 device is probed, the kernel creates a rawmidi
device for each UMP Endpoint of the device. Its device name is device for each UMP Endpoint of the device. Its device name is
`/dev/snd/umpC*D*` and different from the standard rawmidi device name `/dev/snd/umpC*D*` and different from the standard rawmidi device name
@ -101,11 +110,15 @@ opening `/dev/snd/midiC*D*` will end up with opening the first
substream. substream.
Each UMP Endpoint can provide the additional information, constructed Each UMP Endpoint can provide the additional information, constructed
from USB MIDI 2.0 descriptors. And a UMP Endpoint may contain one or from the information inquired via UMP 1.1 Stream messages or USB MIDI
more UMP Blocks, where UMP Block is an abstraction introduced in the 2.0 descriptors. And a UMP Endpoint may contain one or more UMP
ALSA UMP implementations to represent the associations among UMP Blocks, where UMP Block is an abstraction introduced in the ALSA UMP
Groups. UMP Block corresponds to Group Terminal Block (GTB) in USB implementations to represent the associations among UMP Groups. UMP
MIDI 2.0 specifications but provide a few more generic information. Block corresponds to Function Block in UMP 1.1 specification. When
UMP 1.1 Function Block information isn't available, it's filled
partially from Group Terminal Block (GTB) as defined in USB MIDI 2.0
specifications.
The information of UMP Endpoints and UMP Blocks are found in the proc The information of UMP Endpoints and UMP Blocks are found in the proc
file `/proc/asound/card*/midi*`. For example:: file `/proc/asound/card*/midi*`. For example::
@ -207,6 +220,8 @@ The "MIDI 2.0" port is for a UMP Endpoint, and its difference from
other UMP Group ports is that UMP Endpoint port sends the events from other UMP Group ports is that UMP Endpoint port sends the events from
the all ports on the device ("catch-all"), while each UMP Group port the all ports on the device ("catch-all"), while each UMP Group port
sends only the events from the given UMP Group. sends only the events from the given UMP Group.
Also, UMP groupless messages (such as the UMP message type 0x0f) are
sent only to the UMP Endpoint port.
Note that, although each UMP sequencer client usually creates 16 Note that, although each UMP sequencer client usually creates 16
ports, those ports that don't belong to any UMP Blocks (or belonging ports, those ports that don't belong to any UMP Blocks (or belonging
@ -273,6 +288,11 @@ Rawmidi API Extensions
The direction is either `SNDRV_UMP_DIR_INPUT`, The direction is either `SNDRV_UMP_DIR_INPUT`,
`SNDRV_UMP_DIR_OUTPUT` or `SNDRV_UMP_DIR_BIDIRECTION`. `SNDRV_UMP_DIR_OUTPUT` or `SNDRV_UMP_DIR_BIDIRECTION`.
* For the device supports UMP v1.1, the UMP MIDI protocol can be
switched via "Stream Configuration Request" message (UMP type 0x0f,
status 0x05). When UMP core receives such a message, it updates the
UMP EP info and the corresponding sequencer clients as well.
Control API Extensions Control API Extensions
====================== ======================
@ -337,7 +357,7 @@ Sequencer API Extensions
`group_filter` bitmap. The filter consists of bitmap from 1-based `group_filter` bitmap. The filter consists of bitmap from 1-based
Group numbers. For example, when the bit 1 is set, messages from Group numbers. For example, when the bit 1 is set, messages from
Group 1 (i.e. the very first group) are filtered and not delivered. Group 1 (i.e. the very first group) are filtered and not delivered.
The bit 0 is reserved for future use. The bit 0 is used for filtering UMP groupless messages.
* Two new ioctls are added for UMP-capable clients: * Two new ioctls are added for UMP-capable clients:
`SNDRV_SEQ_IOCTL_GET_CLIENT_UMP_INFO` and `SNDRV_SEQ_IOCTL_GET_CLIENT_UMP_INFO` and
@ -349,3 +369,10 @@ Sequencer API Extensions
For an Endpoint data, pass 0 to the `type` field, while for a Block For an Endpoint data, pass 0 to the `type` field, while for a Block
data, pass the block number + 1 to the `type` field. data, pass the block number + 1 to the `type` field.
Setting the data for a kernel client shall result in an error. Setting the data for a kernel client shall result in an error.
* With UMP 1.1, Function Block information may be changed
dynamically. When the update of Function Block is received from the
device, ALSA sequencer core changes the corresponding sequencer port
name and attributes accordingly, and notifies the changes via the
announcement to the ALSA sequencer system port, similarly like the
normal port change notification.

View File

@ -24,6 +24,13 @@ struct snd_ump_endpoint {
void *private_data; void *private_data;
void (*private_free)(struct snd_ump_endpoint *ump); void (*private_free)(struct snd_ump_endpoint *ump);
/* UMP Stream message processing */
u32 stream_wait_for; /* expected stream message status */
bool stream_finished; /* set when message has been processed */
bool parsed; /* UMP / FB parse finished? */
wait_queue_head_t stream_wait;
struct snd_rawmidi_file stream_rfile;
struct list_head block_list; /* list of snd_ump_block objects */ struct list_head block_list; /* list of snd_ump_block objects */
/* intermediate buffer for UMP input */ /* intermediate buffer for UMP input */
@ -63,6 +70,9 @@ struct snd_ump_ops {
struct snd_seq_ump_ops { struct snd_seq_ump_ops {
void (*input_receive)(struct snd_ump_endpoint *ump, void (*input_receive)(struct snd_ump_endpoint *ump,
const u32 *data, int words); const u32 *data, int words);
int (*notify_fb_change)(struct snd_ump_endpoint *ump,
struct snd_ump_block *fb);
int (*switch_protocol)(struct snd_ump_endpoint *ump);
}; };
struct snd_ump_block { struct snd_ump_block {
@ -80,6 +90,7 @@ struct snd_ump_block {
int snd_ump_endpoint_new(struct snd_card *card, char *id, int device, int snd_ump_endpoint_new(struct snd_card *card, char *id, int device,
int output, int input, int output, int input,
struct snd_ump_endpoint **ump_ret); struct snd_ump_endpoint **ump_ret);
int snd_ump_parse_endpoint(struct snd_ump_endpoint *ump);
int snd_ump_block_new(struct snd_ump_endpoint *ump, unsigned int blk, int snd_ump_block_new(struct snd_ump_endpoint *ump, unsigned int blk,
unsigned int direction, unsigned int first_group, unsigned int direction, unsigned int first_group,
unsigned int num_groups, struct snd_ump_block **blk_ret); unsigned int num_groups, struct snd_ump_block **blk_ret);
@ -109,6 +120,8 @@ enum {
UMP_MSG_TYPE_DATA = 0x03, UMP_MSG_TYPE_DATA = 0x03,
UMP_MSG_TYPE_MIDI2_CHANNEL_VOICE = 0x04, UMP_MSG_TYPE_MIDI2_CHANNEL_VOICE = 0x04,
UMP_MSG_TYPE_EXTENDED_DATA = 0x05, UMP_MSG_TYPE_EXTENDED_DATA = 0x05,
UMP_MSG_TYPE_FLEX_DATA = 0x0d,
UMP_MSG_TYPE_STREAM = 0x0f,
}; };
/* MIDI 2.0 SysEx / Data Status; same values for both 7-bit and 8-bit SysEx */ /* MIDI 2.0 SysEx / Data Status; same values for both 7-bit and 8-bit SysEx */
@ -119,6 +132,62 @@ enum {
UMP_SYSEX_STATUS_END = 3, UMP_SYSEX_STATUS_END = 3,
}; };
/* UMP Utility Type Status (type 0x0) */
enum {
UMP_UTILITY_MSG_STATUS_NOOP = 0x00,
UMP_UTILITY_MSG_STATUS_JR_CLOCK = 0x01,
UMP_UTILITY_MSG_STATUS_JR_TSTAMP = 0x02,
UMP_UTILITY_MSG_STATUS_DCTPQ = 0x03,
UMP_UTILITY_MSG_STATUS_DC = 0x04,
};
/* UMP Stream Message Status (type 0xf) */
enum {
UMP_STREAM_MSG_STATUS_EP_DISCOVERY = 0x00,
UMP_STREAM_MSG_STATUS_EP_INFO = 0x01,
UMP_STREAM_MSG_STATUS_DEVICE_INFO = 0x02,
UMP_STREAM_MSG_STATUS_EP_NAME = 0x03,
UMP_STREAM_MSG_STATUS_PRODUCT_ID = 0x04,
UMP_STREAM_MSG_STATUS_STREAM_CFG_REQUEST = 0x05,
UMP_STREAM_MSG_STATUS_STREAM_CFG = 0x06,
UMP_STREAM_MSG_STATUS_FB_DISCOVERY = 0x10,
UMP_STREAM_MSG_STATUS_FB_INFO = 0x11,
UMP_STREAM_MSG_STATUS_FB_NAME = 0x12,
UMP_STREAM_MSG_STATUS_START_CLIP = 0x20,
UMP_STREAM_MSG_STATUS_END_CLIP = 0x21,
};
/* UMP Endpoint Discovery filter bitmap */
enum {
UMP_STREAM_MSG_REQUEST_EP_INFO = (1U << 0),
UMP_STREAM_MSG_REQUEST_DEVICE_INFO = (1U << 1),
UMP_STREAM_MSG_REQUEST_EP_NAME = (1U << 2),
UMP_STREAM_MSG_REQUEST_PRODUCT_ID = (1U << 3),
UMP_STREAM_MSG_REQUEST_STREAM_CFG = (1U << 4),
};
/* UMP Function Block Discovery filter bitmap */
enum {
UMP_STREAM_MSG_REQUEST_FB_INFO = (1U << 0),
UMP_STREAM_MSG_REQUEST_FB_NAME = (1U << 1),
};
/* UMP Endpoint Info capability bits (used for protocol request/notify, too) */
enum {
UMP_STREAM_MSG_EP_INFO_CAP_TXJR = (1U << 0), /* Sending JRTS */
UMP_STREAM_MSG_EP_INFO_CAP_RXJR = (1U << 1), /* Receiving JRTS */
UMP_STREAM_MSG_EP_INFO_CAP_MIDI1 = (1U << 8), /* MIDI 1.0 */
UMP_STREAM_MSG_EP_INFO_CAP_MIDI2 = (1U << 9), /* MIDI 2.0 */
};
/* UMP EP / FB name string format; same as SysEx string handling */
enum {
UMP_STREAM_MSG_FORMAT_SINGLE = 0,
UMP_STREAM_MSG_FORMAT_START = 1,
UMP_STREAM_MSG_FORMAT_CONTINUE = 2,
UMP_STREAM_MSG_FORMAT_END = 3,
};
/* /*
* Helpers for retrieving / filling bits from UMP * Helpers for retrieving / filling bits from UMP
*/ */
@ -172,4 +241,24 @@ static inline unsigned char ump_sysex_message_length(u32 data)
return (data >> 16) & 0xf; return (data >> 16) & 0xf;
} }
/* For Stream Messages */
static inline unsigned char ump_stream_message_format(u32 data)
{
return (data >> 26) & 0x03;
}
static inline unsigned int ump_stream_message_status(u32 data)
{
return (data >> 16) & 0x3ff;
}
static inline u32 ump_stream_compose(unsigned char status, unsigned short form)
{
return (UMP_MSG_TYPE_STREAM << 28) | ((u32)form << 26) |
((u32)status << 16);
}
#define ump_is_groupless_msg(type) \
((type) == UMP_MSG_TYPE_UTILITY || (type) == UMP_MSG_TYPE_STREAM)
#endif /* __SOUND_UMP_H */ #endif /* __SOUND_UMP_H */

View File

@ -537,4 +537,229 @@ union snd_ump_midi2_msg {
u32 raw[2]; u32 raw[2];
}; };
/* UMP Stream Message: Endpoint Discovery (128bit) */
struct snd_ump_stream_msg_ep_discovery {
#ifdef __BIG_ENDIAN_BITFIELD
/* 0 */
u32 type:4;
u32 format:2;
u32 status:10;
u32 ump_version_major:8;
u32 ump_version_minor:8;
/* 1 */
u32 reserved:24;
u32 filter_bitmap:8;
/* 2-3 */
u32 reserved2[2];
#else
/* 0 */
u32 ump_version_minor:8;
u32 ump_version_major:8;
u32 status:10;
u32 format:2;
u32 type:4;
/* 1 */
u32 filter_bitmap:8;
u32 reserved:24;
/* 2-3 */
u32 reserved2[2];
#endif
} __packed;
/* UMP Stream Message: Endpoint Info Notification (128bit) */
struct snd_ump_stream_msg_ep_info {
#ifdef __BIG_ENDIAN_BITFIELD
/* 0 */
u32 type:4;
u32 format:2;
u32 status:10;
u32 ump_version_major:8;
u32 ump_version_minor:8;
/* 1 */
u32 static_function_block:1;
u32 num_function_blocks:7;
u32 reserved:8;
u32 protocol:8;
u32 reserved2:6;
u32 jrts:2;
/* 2-3 */
u32 reserved3[2];
#else
/* 0 */
u32 ump_version_minor:8;
u32 ump_version_major:8;
u32 status:10;
u32 format:2;
u32 type:4;
/* 1 */
u32 jrts:2;
u32 reserved2:6;
u32 protocol:8;
u32 reserved:8;
u32 num_function_blocks:7;
u32 static_function_block:1;
/* 2-3 */
u32 reserved3[2];
#endif
} __packed;
/* UMP Stream Message: Device Info Notification (128bit) */
struct snd_ump_stream_msg_devince_info {
#ifdef __BIG_ENDIAN_BITFIELD
/* 0 */
u32 type:4;
u32 format:2;
u32 status:10;
u32 reserved:16;
/* 1 */
u32 manufacture_id;
/* 2 */
u8 family_lsb;
u8 family_msb;
u8 model_lsb;
u8 model_msb;
/* 3 */
u32 sw_revision;
#else
/* 0 */
u32 reserved:16;
u32 status:10;
u32 format:2;
u32 type:4;
/* 1 */
u32 manufacture_id;
/* 2 */
u8 model_msb;
u8 model_lsb;
u8 family_msb;
u8 family_lsb;
/* 3 */
u32 sw_revision;
#endif
} __packed;
/* UMP Stream Message: Stream Config Request / Notification (128bit) */
struct snd_ump_stream_msg_stream_cfg {
#ifdef __BIG_ENDIAN_BITFIELD
/* 0 */
u32 type:4;
u32 format:2;
u32 status:10;
u32 protocol:8;
u32 reserved:6;
u32 jrts:2;
/* 1-3 */
u32 reserved2[3];
#else
/* 0 */
u32 jrts:2;
u32 reserved:6;
u32 protocol:8;
u32 status:10;
u32 format:2;
u32 type:4;
/* 1-3 */
u32 reserved2[3];
#endif
} __packed;
/* UMP Stream Message: Function Block Discovery (128bit) */
struct snd_ump_stream_msg_fb_discovery {
#ifdef __BIG_ENDIAN_BITFIELD
/* 0 */
u32 type:4;
u32 format:2;
u32 status:10;
u32 function_block_id:8;
u32 filter:8;
/* 1-3 */
u32 reserved[3];
#else
/* 0 */
u32 filter:8;
u32 function_block_id:8;
u32 status:10;
u32 format:2;
u32 type:4;
/* 1-3 */
u32 reserved[3];
#endif
} __packed;
/* UMP Stream Message: Function Block Info Notification (128bit) */
struct snd_ump_stream_msg_fb_info {
#ifdef __BIG_ENDIAN_BITFIELD
/* 0 */
u32 type:4;
u32 format:2;
u32 status:10;
u32 active:1;
u32 function_block_id:7;
u32 reserved:2;
u32 ui_hint:2;
u32 midi_10:2;
u32 direction:2;
/* 1 */
u32 first_group:8;
u32 num_groups:8;
u32 midi_ci_version:8;
u32 sysex8_streams:8;
/* 2-3 */
u32 reserved2[2];
#else
/* 0 */
u32 direction:2;
u32 midi_10:2;
u32 ui_hint:2;
u32 reserved:2;
u32 function_block_id:7;
u32 active:1;
u32 status:10;
u32 format:2;
u32 type:4;
/* 1 */
u32 sysex8_streams:8;
u32 midi_ci_version:8;
u32 num_groups:8;
u32 first_group:8;
/* 2-3 */
u32 reserved2[2];
#endif
} __packed;
/* UMP Stream Message: Function Block Name Notification (128bit) */
struct snd_ump_stream_msg_fb_name {
#ifdef __BIG_ENDIAN_BITFIELD
/* 0 */
u16 type:4;
u16 format:2;
u16 status:10;
u8 function_block_id;
u8 name0;
/* 1-3 */
u8 name[12];
#else
/* 0 */
u8 name0;
u8 function_block_id;
u16 status:10;
u16 format:2;
u16 type:4;
/* 1-3 */
u8 name[12]; // FIXME: byte order
#endif
} __packed;
/* MIDI 2.0 Stream Messages (128bit) */
union snd_ump_stream_msg {
struct snd_ump_stream_msg_ep_discovery ep_discovery;
struct snd_ump_stream_msg_ep_info ep_info;
struct snd_ump_stream_msg_devince_info device_info;
struct snd_ump_stream_msg_stream_cfg stream_cfg;
struct snd_ump_stream_msg_fb_discovery fb_discovery;
struct snd_ump_stream_msg_fb_info fb_info;
struct snd_ump_stream_msg_fb_name fb_name;
u32 raw[4];
};
#endif /* __SOUND_UMP_MSG_H */ #endif /* __SOUND_UMP_MSG_H */

View File

@ -362,7 +362,10 @@ struct snd_seq_client_info {
int card; /* RO: card number[kernel] */ int card; /* RO: card number[kernel] */
int pid; /* RO: pid[user] */ int pid; /* RO: pid[user] */
unsigned int midi_version; /* MIDI version */ unsigned int midi_version; /* MIDI version */
unsigned int group_filter; /* UMP group filter bitmap (for 1-based Group indices) */ unsigned int group_filter; /* UMP group filter bitmap
* (bit 0 = groupless messages,
* bit 1-16 = messages for groups 1-16)
*/
char reserved[48]; /* for future use */ char reserved[48]; /* for future use */
}; };

View File

@ -712,7 +712,7 @@ enum {
* Raw MIDI section - /dev/snd/midi?? * Raw MIDI section - /dev/snd/midi??
*/ */
#define SNDRV_RAWMIDI_VERSION SNDRV_PROTOCOL_VERSION(2, 0, 3) #define SNDRV_RAWMIDI_VERSION SNDRV_PROTOCOL_VERSION(2, 0, 4)
enum { enum {
SNDRV_RAWMIDI_STREAM_OUTPUT = 0, SNDRV_RAWMIDI_STREAM_OUTPUT = 0,
@ -784,6 +784,9 @@ struct snd_rawmidi_status {
}; };
#endif #endif
/* UMP EP info flags */
#define SNDRV_UMP_EP_INFO_STATIC_BLOCKS 0x01
/* UMP EP Protocol / JRTS capability bits */ /* UMP EP Protocol / JRTS capability bits */
#define SNDRV_UMP_EP_INFO_PROTO_MIDI_MASK 0x0300 #define SNDRV_UMP_EP_INFO_PROTO_MIDI_MASK 0x0300
#define SNDRV_UMP_EP_INFO_PROTO_MIDI1 0x0100 /* MIDI 1.0 */ #define SNDRV_UMP_EP_INFO_PROTO_MIDI1 0x0100 /* MIDI 1.0 */
@ -801,7 +804,11 @@ struct snd_ump_endpoint_info {
unsigned int protocol; /* current protocol */ unsigned int protocol; /* current protocol */
unsigned int num_blocks; /* # of function blocks */ unsigned int num_blocks; /* # of function blocks */
unsigned short version; /* UMP major/minor version */ unsigned short version; /* UMP major/minor version */
unsigned short padding[7]; unsigned short family_id; /* MIDI device family ID */
unsigned short model_id; /* MIDI family model ID */
unsigned int manufacturer_id; /* MIDI manufacturer ID */
unsigned char sw_revision[4]; /* software revision */
unsigned short padding;
unsigned char name[128]; /* endpoint name string */ unsigned char name[128]; /* endpoint name string */
unsigned char product_id[128]; /* unique product id string */ unsigned char product_id[128]; /* unique product id string */
unsigned char reserved[32]; unsigned char reserved[32];
@ -816,6 +823,12 @@ struct snd_ump_endpoint_info {
#define SNDRV_UMP_BLOCK_IS_MIDI1 (1U << 0) /* MIDI 1.0 port w/o restrict */ #define SNDRV_UMP_BLOCK_IS_MIDI1 (1U << 0) /* MIDI 1.0 port w/o restrict */
#define SNDRV_UMP_BLOCK_IS_LOWSPEED (1U << 1) /* 31.25Kbps B/W MIDI1 port */ #define SNDRV_UMP_BLOCK_IS_LOWSPEED (1U << 1) /* 31.25Kbps B/W MIDI1 port */
/* UMP block user-interface hint */
#define SNDRV_UMP_BLOCK_UI_HINT_UNKNOWN 0x00
#define SNDRV_UMP_BLOCK_UI_HINT_RECEIVER 0x01
#define SNDRV_UMP_BLOCK_UI_HINT_SENDER 0x02
#define SNDRV_UMP_BLOCK_UI_HINT_BOTH 0x03
/* UMP groups and blocks */ /* UMP groups and blocks */
#define SNDRV_UMP_MAX_GROUPS 16 #define SNDRV_UMP_MAX_GROUPS 16
#define SNDRV_UMP_MAX_BLOCKS 32 #define SNDRV_UMP_MAX_BLOCKS 32
@ -829,7 +842,9 @@ struct snd_ump_block_info {
unsigned char active; /* Activeness */ unsigned char active; /* Activeness */
unsigned char first_group; /* first group ID */ unsigned char first_group; /* first group ID */
unsigned char num_groups; /* number of groups */ unsigned char num_groups; /* number of groups */
unsigned char padding[3]; unsigned char midi_ci_version; /* MIDI-CI support version */
unsigned char sysex8_streams; /* max number of sysex8 streams */
unsigned char ui_hint; /* user interface hint */
unsigned int flags; /* various info flags */ unsigned int flags; /* various info flags */
unsigned char name[128]; /* block name string */ unsigned char name[128]; /* block name string */
unsigned char reserved[32]; unsigned char reserved[32];

View File

@ -85,6 +85,7 @@ void snd_seq_system_broadcast(int client, int port, int type)
ev.type = type; ev.type = type;
snd_seq_kernel_client_dispatch(sysclient, &ev, 0, 0); snd_seq_kernel_client_dispatch(sysclient, &ev, 0, 0);
} }
EXPORT_SYMBOL_GPL(snd_seq_system_broadcast);
/* entry points for broadcasting system events */ /* entry points for broadcasting system events */
int snd_seq_system_notify(int client, int port, struct snd_seq_event *ev) int snd_seq_system_notify(int client, int port, struct snd_seq_event *ev)

View File

@ -13,6 +13,7 @@
#include <sound/seq_kernel.h> #include <sound/seq_kernel.h>
#include <sound/seq_device.h> #include <sound/seq_device.h>
#include "seq_clientmgr.h" #include "seq_clientmgr.h"
#include "seq_system.h"
struct seq_ump_client; struct seq_ump_client;
struct seq_ump_group; struct seq_ump_group;
@ -48,6 +49,7 @@ struct seq_ump_client {
struct seq_ump_input_buffer input; /* input parser context */ struct seq_ump_input_buffer input; /* input parser context */
struct seq_ump_group groups[SNDRV_UMP_MAX_GROUPS]; /* table of groups */ struct seq_ump_group groups[SNDRV_UMP_MAX_GROUPS]; /* table of groups */
void *ump_info[SNDRV_UMP_MAX_BLOCKS + 1]; /* shadow of seq client ump_info */ void *ump_info[SNDRV_UMP_MAX_BLOCKS + 1]; /* shadow of seq client ump_info */
struct work_struct group_notify_work; /* FB change notification */
}; };
/* number of 32bit words for each UMP message type */ /* number of 32bit words for each UMP message type */
@ -73,6 +75,9 @@ static void seq_ump_input_receive(struct snd_ump_endpoint *ump,
if (!client->opened[STR_IN]) if (!client->opened[STR_IN])
return; return;
if (ump_is_groupless_msg(ump_message_type(*val)))
ev.source.port = 0; /* UMP EP port */
else
ev.source.port = ump_group_to_seq_port(ump_message_group(*val)); ev.source.port = ump_group_to_seq_port(ump_message_group(*val));
ev.dest.client = SNDRV_SEQ_ADDRESS_SUBSCRIBERS; ev.dest.client = SNDRV_SEQ_ADDRESS_SUBSCRIBERS;
ev.flags = SNDRV_SEQ_EVENT_UMP; ev.flags = SNDRV_SEQ_EVENT_UMP;
@ -241,6 +246,42 @@ static int seq_ump_group_init(struct seq_ump_client *client, int group_index)
return err; return err;
} }
/* update the sequencer ports; called from notify_fb_change callback */
static void update_port_infos(struct seq_ump_client *client)
{
struct snd_seq_port_info *old, *new;
int i, err;
old = kzalloc(sizeof(*old), GFP_KERNEL);
new = kzalloc(sizeof(*new), GFP_KERNEL);
if (!old || !new)
goto error;
for (i = 0; i < SNDRV_UMP_MAX_GROUPS; i++) {
old->addr.client = client->seq_client;
old->addr.port = i;
err = snd_seq_kernel_client_ctl(client->seq_client,
SNDRV_SEQ_IOCTL_GET_PORT_INFO,
old);
if (err < 0)
goto error;
fill_port_info(new, client, &client->groups[i]);
if (old->capability == new->capability &&
!strcmp(old->name, new->name))
continue;
err = snd_seq_kernel_client_ctl(client->seq_client,
SNDRV_SEQ_IOCTL_SET_PORT_INFO,
new);
if (err < 0)
goto error;
/* notify to system port */
snd_seq_system_client_ev_port_change(client->seq_client, i);
}
error:
kfree(new);
kfree(old);
}
/* update dir_bits and active flag for all groups in the client */ /* update dir_bits and active flag for all groups in the client */
static void update_group_attrs(struct seq_ump_client *client) static void update_group_attrs(struct seq_ump_client *client)
{ {
@ -350,6 +391,8 @@ static int create_ump_endpoint_port(struct seq_ump_client *client)
/* release the client resources */ /* release the client resources */
static void seq_ump_client_free(struct seq_ump_client *client) static void seq_ump_client_free(struct seq_ump_client *client)
{ {
cancel_work_sync(&client->group_notify_work);
if (client->seq_client >= 0) if (client->seq_client >= 0)
snd_seq_delete_kernel_client(client->seq_client); snd_seq_delete_kernel_client(client->seq_client);
@ -374,8 +417,41 @@ static void setup_client_midi_version(struct seq_ump_client *client)
snd_seq_kernel_client_put(cptr); snd_seq_kernel_client_put(cptr);
} }
/* UMP group change notification */
static void handle_group_notify(struct work_struct *work)
{
struct seq_ump_client *client =
container_of(work, struct seq_ump_client, group_notify_work);
update_group_attrs(client);
update_port_infos(client);
}
/* UMP FB change notification */
static int seq_ump_notify_fb_change(struct snd_ump_endpoint *ump,
struct snd_ump_block *fb)
{
struct seq_ump_client *client = ump->seq_client;
if (!client)
return -ENODEV;
schedule_work(&client->group_notify_work);
return 0;
}
/* UMP protocol change notification; just update the midi_version field */
static int seq_ump_switch_protocol(struct snd_ump_endpoint *ump)
{
if (!ump->seq_client)
return -ENODEV;
setup_client_midi_version(ump->seq_client);
return 0;
}
static const struct snd_seq_ump_ops seq_ump_ops = { static const struct snd_seq_ump_ops seq_ump_ops = {
.input_receive = seq_ump_input_receive, .input_receive = seq_ump_input_receive,
.notify_fb_change = seq_ump_notify_fb_change,
.switch_protocol = seq_ump_switch_protocol,
}; };
/* create a sequencer client and ports for the given UMP endpoint */ /* create a sequencer client and ports for the given UMP endpoint */
@ -393,6 +469,7 @@ static int snd_seq_ump_probe(struct device *_dev)
if (!client) if (!client)
return -ENOMEM; return -ENOMEM;
INIT_WORK(&client->group_notify_work, handle_group_notify);
client->ump = ump; client->ump = ump;
client->seq_client = client->seq_client =

View File

@ -534,6 +534,8 @@ static bool ump_event_filtered(struct snd_seq_client *dest,
unsigned char group; unsigned char group;
group = ump_message_group(ev->ump[0]); group = ump_message_group(ev->ump[0]);
if (ump_is_groupless_msg(ump_message_type(ev->ump[0])))
return dest->group_filter & (1U << 0);
/* check the bitmap for 1-based group number */ /* check the bitmap for 1-based group number */
return dest->group_filter & (1U << (group + 1)); return dest->group_filter & (1U << (group + 1));
} }
@ -565,6 +567,7 @@ int snd_seq_deliver_from_ump(struct snd_seq_client *source,
event, atomic, hop); event, atomic, hop);
/* non-EP port and different group is set? */ /* non-EP port and different group is set? */
if (dest_port->ump_group && if (dest_port->ump_group &&
!ump_is_groupless_msg(type) &&
ump_message_group(*ump_ev->ump) + 1 != dest_port->ump_group) ump_message_group(*ump_ev->ump) + 1 != dest_port->ump_group)
return deliver_with_group_convert(dest, dest_port, return deliver_with_group_convert(dest, dest_port,
ump_ev, atomic, hop); ump_ev, atomic, hop);

View File

@ -30,6 +30,8 @@ static void snd_ump_rawmidi_trigger(struct snd_rawmidi_substream *substream,
int up); int up);
static void snd_ump_rawmidi_drain(struct snd_rawmidi_substream *substream); static void snd_ump_rawmidi_drain(struct snd_rawmidi_substream *substream);
static void ump_handle_stream_msg(struct snd_ump_endpoint *ump,
const u32 *buf, int size);
#if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI) #if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI)
static int process_legacy_output(struct snd_ump_endpoint *ump, static int process_legacy_output(struct snd_ump_endpoint *ump,
u32 *buffer, int count); u32 *buffer, int count);
@ -133,6 +135,7 @@ int snd_ump_endpoint_new(struct snd_card *card, char *id, int device,
return -ENOMEM; return -ENOMEM;
INIT_LIST_HEAD(&ump->block_list); INIT_LIST_HEAD(&ump->block_list);
mutex_init(&ump->open_mutex); mutex_init(&ump->open_mutex);
init_waitqueue_head(&ump->stream_wait);
#if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI) #if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI)
spin_lock_init(&ump->legacy_locks[0]); spin_lock_init(&ump->legacy_locks[0]);
spin_lock_init(&ump->legacy_locks[1]); spin_lock_init(&ump->legacy_locks[1]);
@ -302,6 +305,7 @@ int snd_ump_receive(struct snd_ump_endpoint *ump, const u32 *buffer, int count)
n = snd_ump_receive_ump_val(ump, *p++); n = snd_ump_receive_ump_val(ump, *p++);
if (!n) if (!n)
continue; continue;
ump_handle_stream_msg(ump, ump->input_buf, n);
#if IS_ENABLED(CONFIG_SND_SEQUENCER) #if IS_ENABLED(CONFIG_SND_SEQUENCER)
if (ump->seq_ops) if (ump->seq_ops)
ump->seq_ops->input_receive(ump, ump->input_buf, n); ump->seq_ops->input_receive(ump, ump->input_buf, n);
@ -448,6 +452,20 @@ static const char *ump_direction_string(int dir)
} }
} }
static const char *ump_ui_hint_string(int dir)
{
switch (dir) {
case SNDRV_UMP_BLOCK_UI_HINT_RECEIVER:
return "receiver";
case SNDRV_UMP_BLOCK_UI_HINT_SENDER:
return "sender";
case SNDRV_UMP_BLOCK_UI_HINT_BOTH:
return "both";
default:
return "unknown";
}
}
/* Additional proc file output */ /* Additional proc file output */
static void snd_ump_proc_read(struct snd_info_entry *entry, static void snd_ump_proc_read(struct snd_info_entry *entry,
struct snd_info_buffer *buffer) struct snd_info_buffer *buffer)
@ -461,6 +479,19 @@ static void snd_ump_proc_read(struct snd_info_entry *entry,
snd_iprintf(buffer, "UMP Version: 0x%04x\n", ump->info.version); snd_iprintf(buffer, "UMP Version: 0x%04x\n", ump->info.version);
snd_iprintf(buffer, "Protocol Caps: 0x%08x\n", ump->info.protocol_caps); snd_iprintf(buffer, "Protocol Caps: 0x%08x\n", ump->info.protocol_caps);
snd_iprintf(buffer, "Protocol: 0x%08x\n", ump->info.protocol); snd_iprintf(buffer, "Protocol: 0x%08x\n", ump->info.protocol);
if (ump->info.version) {
snd_iprintf(buffer, "Manufacturer ID: 0x%08x\n",
ump->info.manufacturer_id);
snd_iprintf(buffer, "Family ID: 0x%04x\n", ump->info.family_id);
snd_iprintf(buffer, "Model ID: 0x%04x\n", ump->info.model_id);
snd_iprintf(buffer, "SW Revision: 0x%02x%02x%02x%02x\n",
ump->info.sw_revision[0],
ump->info.sw_revision[1],
ump->info.sw_revision[2],
ump->info.sw_revision[3]);
}
snd_iprintf(buffer, "Static Blocks: %s\n",
(ump->info.flags & SNDRV_UMP_EP_INFO_STATIC_BLOCKS) ? "Yes" : "No");
snd_iprintf(buffer, "Num Blocks: %d\n\n", ump->info.num_blocks); snd_iprintf(buffer, "Num Blocks: %d\n\n", ump->info.num_blocks);
list_for_each_entry(fb, &ump->block_list, list) { list_for_each_entry(fb, &ump->block_list, list) {
@ -476,10 +507,449 @@ static void snd_ump_proc_read(struct snd_info_entry *entry,
snd_iprintf(buffer, " Is MIDI1: %s%s\n", snd_iprintf(buffer, " Is MIDI1: %s%s\n",
(fb->info.flags & SNDRV_UMP_BLOCK_IS_MIDI1) ? "Yes" : "No", (fb->info.flags & SNDRV_UMP_BLOCK_IS_MIDI1) ? "Yes" : "No",
(fb->info.flags & SNDRV_UMP_BLOCK_IS_LOWSPEED) ? " (Low Speed)" : ""); (fb->info.flags & SNDRV_UMP_BLOCK_IS_LOWSPEED) ? " (Low Speed)" : "");
if (ump->info.version) {
snd_iprintf(buffer, " MIDI-CI Version: %d\n",
fb->info.midi_ci_version);
snd_iprintf(buffer, " Sysex8 Streams: %d\n",
fb->info.sysex8_streams);
snd_iprintf(buffer, " UI Hint: %s\n",
ump_ui_hint_string(fb->info.ui_hint));
}
snd_iprintf(buffer, "\n"); snd_iprintf(buffer, "\n");
} }
} }
/*
* UMP endpoint and function block handling
*/
/* open / close UMP streams for the internal stream msg communication */
static int ump_request_open(struct snd_ump_endpoint *ump)
{
return snd_rawmidi_kernel_open(&ump->core, 0,
SNDRV_RAWMIDI_LFLG_OUTPUT,
&ump->stream_rfile);
}
static void ump_request_close(struct snd_ump_endpoint *ump)
{
snd_rawmidi_kernel_release(&ump->stream_rfile);
}
/* request a command and wait for the given response;
* @req1 and @req2 are u32 commands
* @reply is the expected UMP stream status
*/
static int ump_req_msg(struct snd_ump_endpoint *ump, u32 req1, u32 req2,
u32 reply)
{
u32 buf[4];
ump_dbg(ump, "%s: request %08x %08x, wait-for %08x\n",
__func__, req1, req2, reply);
memset(buf, 0, sizeof(buf));
buf[0] = req1;
buf[1] = req2;
ump->stream_finished = 0;
ump->stream_wait_for = reply;
snd_rawmidi_kernel_write(ump->stream_rfile.output,
(unsigned char *)&buf, 16);
wait_event_timeout(ump->stream_wait, ump->stream_finished,
msecs_to_jiffies(500));
if (!READ_ONCE(ump->stream_finished)) {
ump_dbg(ump, "%s: request timed out\n", __func__);
return -ETIMEDOUT;
}
ump->stream_finished = 0;
ump_dbg(ump, "%s: reply: %08x %08x %08x %08x\n",
__func__, buf[0], buf[1], buf[2], buf[3]);
return 0;
}
/* append the received letters via UMP packet to the given string buffer;
* return 1 if the full string is received or 0 to continue
*/
static int ump_append_string(struct snd_ump_endpoint *ump, char *dest,
int maxsize, const u32 *buf, int offset)
{
unsigned char format;
int c;
format = ump_stream_message_format(buf[0]);
if (format == UMP_STREAM_MSG_FORMAT_SINGLE ||
format == UMP_STREAM_MSG_FORMAT_START) {
c = 0;
} else {
c = strlen(dest);
if (c >= maxsize - 1)
return 1;
}
for (; offset < 16; offset++) {
dest[c] = buf[offset / 4] >> (3 - (offset % 4)) * 8;
if (!dest[c])
break;
if (++c >= maxsize - 1)
break;
}
dest[c] = 0;
return (format == UMP_STREAM_MSG_FORMAT_SINGLE ||
format == UMP_STREAM_MSG_FORMAT_END);
}
/* handle EP info stream message; update the UMP attributes */
static int ump_handle_ep_info_msg(struct snd_ump_endpoint *ump,
const union snd_ump_stream_msg *buf)
{
ump->info.version = (buf->ep_info.ump_version_major << 8) |
buf->ep_info.ump_version_minor;
ump->info.num_blocks = buf->ep_info.num_function_blocks;
if (ump->info.num_blocks > SNDRV_UMP_MAX_BLOCKS) {
ump_info(ump, "Invalid function blocks %d, fallback to 1\n",
ump->info.num_blocks);
ump->info.num_blocks = 1;
}
if (buf->ep_info.static_function_block)
ump->info.flags |= SNDRV_UMP_EP_INFO_STATIC_BLOCKS;
ump->info.protocol_caps = (buf->ep_info.protocol << 8) |
buf->ep_info.jrts;
ump_dbg(ump, "EP info: version=%x, num_blocks=%x, proto_caps=%x\n",
ump->info.version, ump->info.num_blocks, ump->info.protocol_caps);
return 1; /* finished */
}
/* handle EP device info stream message; update the UMP attributes */
static int ump_handle_device_info_msg(struct snd_ump_endpoint *ump,
const union snd_ump_stream_msg *buf)
{
ump->info.manufacturer_id = buf->device_info.manufacture_id & 0x7f7f7f;
ump->info.family_id = (buf->device_info.family_msb << 8) |
buf->device_info.family_lsb;
ump->info.model_id = (buf->device_info.model_msb << 8) |
buf->device_info.model_lsb;
ump->info.sw_revision[0] = (buf->device_info.sw_revision >> 24) & 0x7f;
ump->info.sw_revision[1] = (buf->device_info.sw_revision >> 16) & 0x7f;
ump->info.sw_revision[2] = (buf->device_info.sw_revision >> 8) & 0x7f;
ump->info.sw_revision[3] = buf->device_info.sw_revision & 0x7f;
ump_dbg(ump, "EP devinfo: manid=%08x, family=%04x, model=%04x, sw=%02x%02x%02x%02x\n",
ump->info.manufacturer_id,
ump->info.family_id,
ump->info.model_id,
ump->info.sw_revision[0],
ump->info.sw_revision[1],
ump->info.sw_revision[2],
ump->info.sw_revision[3]);
return 1; /* finished */
}
/* handle EP name stream message; update the UMP name string */
static int ump_handle_ep_name_msg(struct snd_ump_endpoint *ump,
const union snd_ump_stream_msg *buf)
{
return ump_append_string(ump, ump->info.name, sizeof(ump->info.name),
buf->raw, 2);
}
/* handle EP product id stream message; update the UMP product_id string */
static int ump_handle_product_id_msg(struct snd_ump_endpoint *ump,
const union snd_ump_stream_msg *buf)
{
return ump_append_string(ump, ump->info.product_id,
sizeof(ump->info.product_id),
buf->raw, 2);
}
/* notify the protocol change to sequencer */
static void seq_notify_protocol(struct snd_ump_endpoint *ump)
{
#if IS_ENABLED(CONFIG_SND_SEQUENCER)
if (ump->seq_ops && ump->seq_ops->switch_protocol)
ump->seq_ops->switch_protocol(ump);
#endif /* CONFIG_SND_SEQUENCER */
}
/* handle EP stream config message; update the UMP protocol */
static int ump_handle_stream_cfg_msg(struct snd_ump_endpoint *ump,
const union snd_ump_stream_msg *buf)
{
unsigned int old_protocol = ump->info.protocol;
ump->info.protocol =
(buf->stream_cfg.protocol << 8) | buf->stream_cfg.jrts;
ump_dbg(ump, "Current protocol = %x (caps = %x)\n",
ump->info.protocol, ump->info.protocol_caps);
if (ump->parsed && ump->info.protocol != old_protocol)
seq_notify_protocol(ump);
return 1; /* finished */
}
/* Extract Function Block info from UMP packet */
static void fill_fb_info(struct snd_ump_endpoint *ump,
struct snd_ump_block_info *info,
const union snd_ump_stream_msg *buf)
{
info->direction = buf->fb_info.direction;
info->ui_hint = buf->fb_info.ui_hint;
info->first_group = buf->fb_info.first_group;
info->num_groups = buf->fb_info.num_groups;
info->flags = buf->fb_info.midi_10;
info->active = buf->fb_info.active;
info->midi_ci_version = buf->fb_info.midi_ci_version;
info->sysex8_streams = buf->fb_info.sysex8_streams;
ump_dbg(ump, "FB %d: dir=%d, active=%d, first_gp=%d, num_gp=%d, midici=%d, sysex8=%d, flags=0x%x\n",
info->block_id, info->direction, info->active,
info->first_group, info->num_groups, info->midi_ci_version,
info->sysex8_streams, info->flags);
}
/* check whether the FB info gets updated by the current message */
static bool is_fb_info_updated(struct snd_ump_endpoint *ump,
struct snd_ump_block *fb,
const union snd_ump_stream_msg *buf)
{
char tmpbuf[offsetof(struct snd_ump_block_info, name)];
if (ump->info.flags & SNDRV_UMP_EP_INFO_STATIC_BLOCKS) {
ump_info(ump, "Skipping static FB info update (blk#%d)\n",
fb->info.block_id);
return 0;
}
memcpy(tmpbuf, &fb->info, sizeof(tmpbuf));
fill_fb_info(ump, (struct snd_ump_block_info *)tmpbuf, buf);
return memcmp(&fb->info, tmpbuf, sizeof(tmpbuf)) != 0;
}
/* notify the FB info/name change to sequencer */
static void seq_notify_fb_change(struct snd_ump_endpoint *ump,
struct snd_ump_block *fb)
{
#if IS_ENABLED(CONFIG_SND_SEQUENCER)
if (ump->seq_ops && ump->seq_ops->notify_fb_change)
ump->seq_ops->notify_fb_change(ump, fb);
#endif
}
/* handle FB info message; update FB info if the block is present */
static int ump_handle_fb_info_msg(struct snd_ump_endpoint *ump,
const union snd_ump_stream_msg *buf)
{
unsigned char blk;
struct snd_ump_block *fb;
blk = buf->fb_info.function_block_id;
fb = snd_ump_get_block(ump, blk);
/* complain only if updated after parsing */
if (!fb && ump->parsed) {
ump_info(ump, "Function Block Info Update for non-existing block %d\n",
blk);
return -ENODEV;
}
/* When updated after the initial parse, check the FB info update */
if (ump->parsed && !is_fb_info_updated(ump, fb, buf))
return 1; /* no content change */
if (fb) {
fill_fb_info(ump, &fb->info, buf);
if (ump->parsed)
seq_notify_fb_change(ump, fb);
}
return 1; /* finished */
}
/* handle FB name message; update the FB name string */
static int ump_handle_fb_name_msg(struct snd_ump_endpoint *ump,
const union snd_ump_stream_msg *buf)
{
unsigned char blk;
struct snd_ump_block *fb;
int ret;
blk = buf->fb_name.function_block_id;
fb = snd_ump_get_block(ump, blk);
if (!fb)
return -ENODEV;
ret = ump_append_string(ump, fb->info.name, sizeof(fb->info.name),
buf->raw, 3);
/* notify the FB name update to sequencer, too */
if (ret > 0 && ump->parsed)
seq_notify_fb_change(ump, fb);
return ret;
}
static int create_block_from_fb_info(struct snd_ump_endpoint *ump, int blk)
{
struct snd_ump_block *fb;
unsigned char direction, first_group, num_groups;
const union snd_ump_stream_msg *buf =
(const union snd_ump_stream_msg *)ump->input_buf;
u32 msg;
int err;
/* query the FB info once */
msg = ump_stream_compose(UMP_STREAM_MSG_STATUS_FB_DISCOVERY, 0) |
(blk << 8) | UMP_STREAM_MSG_REQUEST_FB_INFO;
err = ump_req_msg(ump, msg, 0, UMP_STREAM_MSG_STATUS_FB_INFO);
if (err < 0) {
ump_dbg(ump, "Unable to get FB info for block %d\n", blk);
return err;
}
/* the last input must be the FB info */
if (buf->fb_info.status != UMP_STREAM_MSG_STATUS_FB_INFO) {
ump_dbg(ump, "Inconsistent input: 0x%x\n", *buf->raw);
return -EINVAL;
}
direction = buf->fb_info.direction;
first_group = buf->fb_info.first_group;
num_groups = buf->fb_info.num_groups;
err = snd_ump_block_new(ump, blk, direction, first_group, num_groups,
&fb);
if (err < 0)
return err;
fill_fb_info(ump, &fb->info, buf);
msg = ump_stream_compose(UMP_STREAM_MSG_STATUS_FB_DISCOVERY, 0) |
(blk << 8) | UMP_STREAM_MSG_REQUEST_FB_NAME;
err = ump_req_msg(ump, msg, 0, UMP_STREAM_MSG_STATUS_FB_NAME);
if (err)
ump_dbg(ump, "Unable to get UMP FB name string #%d\n", blk);
return 0;
}
/* handle stream messages, called from snd_ump_receive() */
static void ump_handle_stream_msg(struct snd_ump_endpoint *ump,
const u32 *buf, int size)
{
const union snd_ump_stream_msg *msg;
unsigned int status;
int ret;
BUILD_BUG_ON(sizeof(*msg) != 16);
ump_dbg(ump, "Stream msg: %08x %08x %08x %08x\n",
buf[0], buf[1], buf[2], buf[3]);
if (size != 4 || ump_message_type(*buf) != UMP_MSG_TYPE_STREAM)
return;
msg = (const union snd_ump_stream_msg *)buf;
status = ump_stream_message_status(*buf);
switch (status) {
case UMP_STREAM_MSG_STATUS_EP_INFO:
ret = ump_handle_ep_info_msg(ump, msg);
break;
case UMP_STREAM_MSG_STATUS_DEVICE_INFO:
ret = ump_handle_device_info_msg(ump, msg);
break;
case UMP_STREAM_MSG_STATUS_EP_NAME:
ret = ump_handle_ep_name_msg(ump, msg);
break;
case UMP_STREAM_MSG_STATUS_PRODUCT_ID:
ret = ump_handle_product_id_msg(ump, msg);
break;
case UMP_STREAM_MSG_STATUS_STREAM_CFG:
ret = ump_handle_stream_cfg_msg(ump, msg);
break;
case UMP_STREAM_MSG_STATUS_FB_INFO:
ret = ump_handle_fb_info_msg(ump, msg);
break;
case UMP_STREAM_MSG_STATUS_FB_NAME:
ret = ump_handle_fb_name_msg(ump, msg);
break;
default:
return;
}
/* when the message has been processed fully, wake up */
if (ret > 0 && ump->stream_wait_for == status) {
WRITE_ONCE(ump->stream_finished, 1);
wake_up(&ump->stream_wait);
}
}
/**
* snd_ump_parse_endpoint - parse endpoint and create function blocks
* @ump: UMP object
*
* Returns 0 for successful parse, -ENODEV if device doesn't respond
* (or the query is unsupported), or other error code for serious errors.
*/
int snd_ump_parse_endpoint(struct snd_ump_endpoint *ump)
{
int blk, err;
u32 msg;
if (!(ump->core.info_flags & SNDRV_RAWMIDI_INFO_DUPLEX))
return -ENODEV;
err = ump_request_open(ump);
if (err < 0) {
ump_dbg(ump, "Unable to open rawmidi device: %d\n", err);
return err;
}
/* Check Endpoint Information */
msg = ump_stream_compose(UMP_STREAM_MSG_STATUS_EP_DISCOVERY, 0) |
0x0101; /* UMP version 1.1 */
err = ump_req_msg(ump, msg, UMP_STREAM_MSG_REQUEST_EP_INFO,
UMP_STREAM_MSG_STATUS_EP_INFO);
if (err < 0) {
ump_dbg(ump, "Unable to get UMP EP info\n");
goto error;
}
/* Request Endpoint Device Info */
err = ump_req_msg(ump, msg, UMP_STREAM_MSG_REQUEST_DEVICE_INFO,
UMP_STREAM_MSG_STATUS_DEVICE_INFO);
if (err < 0)
ump_dbg(ump, "Unable to get UMP EP device info\n");
/* Request Endpoint Name */
err = ump_req_msg(ump, msg, UMP_STREAM_MSG_REQUEST_EP_NAME,
UMP_STREAM_MSG_STATUS_EP_NAME);
if (err < 0)
ump_dbg(ump, "Unable to get UMP EP name string\n");
/* Request Endpoint Product ID */
err = ump_req_msg(ump, msg, UMP_STREAM_MSG_REQUEST_PRODUCT_ID,
UMP_STREAM_MSG_STATUS_PRODUCT_ID);
if (err < 0)
ump_dbg(ump, "Unable to get UMP EP product ID string\n");
/* Get the current stream configuration */
err = ump_req_msg(ump, msg, UMP_STREAM_MSG_REQUEST_STREAM_CFG,
UMP_STREAM_MSG_STATUS_STREAM_CFG);
if (err < 0)
ump_dbg(ump, "Unable to get UMP EP stream config\n");
/* Query and create blocks from Function Blocks */
for (blk = 0; blk < ump->info.num_blocks; blk++) {
err = create_block_from_fb_info(ump, blk);
if (err < 0)
continue;
}
error:
ump->parsed = true;
ump_request_close(ump);
if (err == -ETIMEDOUT)
err = -ENODEV;
return err;
}
EXPORT_SYMBOL_GPL(snd_ump_parse_endpoint);
#if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI) #if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI)
/* /*
* Legacy rawmidi support * Legacy rawmidi support

View File

@ -27,6 +27,10 @@ static bool midi2_enable = true;
module_param(midi2_enable, bool, 0444); module_param(midi2_enable, bool, 0444);
MODULE_PARM_DESC(midi2_enable, "Enable MIDI 2.0 support."); MODULE_PARM_DESC(midi2_enable, "Enable MIDI 2.0 support.");
static bool midi2_ump_probe = true;
module_param(midi2_ump_probe, bool, 0444);
MODULE_PARM_DESC(midi2_ump_probe, "Probe UMP v1.1 support at first.");
/* stream direction; just shorter names */ /* stream direction; just shorter names */
enum { enum {
STR_OUT = SNDRV_RAWMIDI_STREAM_OUTPUT, STR_OUT = SNDRV_RAWMIDI_STREAM_OUTPUT,
@ -80,6 +84,7 @@ struct snd_usb_midi2_ump {
struct snd_usb_midi2_endpoint *eps[2]; /* USB MIDI endpoints */ struct snd_usb_midi2_endpoint *eps[2]; /* USB MIDI endpoints */
int index; /* rawmidi device index */ int index; /* rawmidi device index */
unsigned char usb_block_id; /* USB GTB id used for finding a pair */ unsigned char usb_block_id; /* USB GTB id used for finding a pair */
bool ump_parsed; /* Parsed UMP 1.1 EP/FB info*/
struct list_head list; /* list to umidi->rawmidi_list */ struct list_head list; /* list to umidi->rawmidi_list */
}; };
@ -786,6 +791,31 @@ static int find_matching_ep_partner(struct snd_usb_midi2_interface *umidi,
return 0; return 0;
} }
/* Call UMP helper to parse UMP endpoints;
* this needs to be called after starting the input streams for bi-directional
* communications
*/
static int parse_ump_endpoints(struct snd_usb_midi2_interface *umidi)
{
struct snd_usb_midi2_ump *rmidi;
int err;
list_for_each_entry(rmidi, &umidi->rawmidi_list, list) {
if (!rmidi->ump ||
!(rmidi->ump->core.info_flags & SNDRV_RAWMIDI_INFO_DUPLEX))
continue;
err = snd_ump_parse_endpoint(rmidi->ump);
if (!err) {
rmidi->ump_parsed = true;
} else {
if (err == -ENOMEM)
return err;
/* fall back to GTB later */
}
}
return 0;
}
/* create a UMP block from a GTB entry */ /* create a UMP block from a GTB entry */
static int create_gtb_block(struct snd_usb_midi2_ump *rmidi, int dir, int blk) static int create_gtb_block(struct snd_usb_midi2_ump *rmidi, int dir, int blk)
{ {
@ -856,8 +886,10 @@ static int create_blocks_from_gtb(struct snd_usb_midi2_interface *umidi)
if (!rmidi->ump) if (!rmidi->ump)
continue; continue;
/* Blocks have been already created? */ /* Blocks have been already created? */
if (rmidi->ump->info.num_blocks) if (rmidi->ump_parsed || rmidi->ump->info.num_blocks)
continue; continue;
/* GTB is static-only */
rmidi->ump->info.flags |= SNDRV_UMP_EP_INFO_STATIC_BLOCKS;
/* loop over GTBs */ /* loop over GTBs */
for (dir = 0; dir < 2; dir++) { for (dir = 0; dir < 2; dir++) {
if (!rmidi->eps[dir]) if (!rmidi->eps[dir])
@ -1110,6 +1142,14 @@ int snd_usb_midi_v2_create(struct snd_usb_audio *chip,
goto error; goto error;
} }
if (midi2_ump_probe) {
err = parse_ump_endpoints(umidi);
if (err < 0) {
usb_audio_err(chip, "Failed to parse UMP endpoint\n");
goto error;
}
}
err = create_blocks_from_gtb(umidi); err = create_blocks_from_gtb(umidi);
if (err < 0) { if (err < 0) {
usb_audio_err(chip, "Failed to create GTB blocks\n"); usb_audio_err(chip, "Failed to create GTB blocks\n");