ALSA: usb-audio: USB MIDI 2.0 UMP support

This patch provides a basic support for USB MIDI 2.0.  As of this
patch, the driver creates a UMP device per MIDI I/O endpoints, which
serves as a dumb terminal to read/write UMP streams.

A new Kconfig CONFIG_SND_USB_AUDIO_MIDI_V2 manages whether to enable
or disable the MIDI 2.0 support.  Also, the driver provides a new
module option, midi2_enable, to allow disabling the MIDI 2.0 at
runtime, too.  When MIDI 2.0 support is disabled, the driver tries to
fall back to the already existing MIDI 1.0 device (each MIDI 2.0
device is supposed to provide the MIDI 1.0 interface at the altset
0).

For now, the driver doesn't manage any MIDI-CI or other protocol
setups by itself, but relies on the default protocol given via the
group terminal block descriptors.

The MIDI 1.0 messages on MIDI 2.0 device will be automatically
converted in ALSA sequencer in a later patch.  As of this commit, the
driver accepts merely the rawmidi UMP accesses.

The driver builds up the topology in the following way:
- Create an object for each MIDI endpoint belonging to the USB
  interface
- Find MIDI EP "pairs" that share the same GTB;
  note that MIDI EP is unidirectional, while UMP is (normally)
  bidirectional, so two MIDI EPs can form a single UMP EP
- A UMP endpoint object is created for each I/O pair
- For remaining "solo" MIDI EPs, create unidirectional UMP EPs
- Finally, parse GTBs and fill the protocol bits on each UMP

So the driver may support multiple UMP Endpoints in theory, although
most devices are supposed to have a single UMP EP that can contain up
to 16 groups -- which should be large enough.

Reviewed-by: Jaroslav Kysela <perex@perex.cz>
Link: https://lore.kernel.org/r/20230523075358.9672-10-tiwai@suse.de
Signed-off-by: Takashi Iwai <tiwai@suse.de>
This commit is contained in:
Takashi Iwai 2023-05-23 09:53:30 +02:00
parent f8ddb0fb32
commit ff49d1df79
7 changed files with 1109 additions and 5 deletions

View File

@ -15,6 +15,7 @@ config SND_USB_AUDIO
select SND_HWDEP
select SND_RAWMIDI
select SND_PCM
select SND_UMP if SND_USB_AUDIO_MIDI_V2
select BITREVERSE
select SND_USB_AUDIO_USE_MEDIA_CONTROLLER if MEDIA_CONTROLLER && (MEDIA_SUPPORT=y || MEDIA_SUPPORT=SND_USB_AUDIO)
help
@ -24,6 +25,16 @@ config SND_USB_AUDIO
To compile this driver as a module, choose M here: the module
will be called snd-usb-audio.
config SND_USB_AUDIO_MIDI_V2
bool "MIDI 2.0 support by USB Audio driver"
depends on SND_USB_AUDIO
help
Say Y here to include the support for MIDI 2.0 by USB Audio driver.
When the config is set, the driver tries to probe MIDI 2.0 interface
at first, then falls back to MIDI 1.0 interface as default.
The MIDI 2.0 support can be disabled dynamically via midi2_enable
module option, too.
config SND_USB_AUDIO_USE_MEDIA_CONTROLLER
bool

View File

@ -22,6 +22,7 @@ snd-usb-audio-objs := card.o \
stream.o \
validate.o
snd-usb-audio-$(CONFIG_SND_USB_AUDIO_MIDI_V2) += midi2.o
snd-usb-audio-$(CONFIG_SND_USB_AUDIO_USE_MEDIA_CONTROLLER) += media.o
snd-usbmidi-lib-objs := midi.o

View File

@ -44,6 +44,7 @@
#include "usbaudio.h"
#include "card.h"
#include "midi.h"
#include "midi2.h"
#include "mixer.h"
#include "proc.h"
#include "quirks.h"
@ -178,10 +179,8 @@ static int snd_usb_create_stream(struct snd_usb_audio *chip, int ctrlif, int int
if ((altsd->bInterfaceClass == USB_CLASS_AUDIO ||
altsd->bInterfaceClass == USB_CLASS_VENDOR_SPEC) &&
altsd->bInterfaceSubClass == USB_SUBCLASS_MIDISTREAMING) {
int err = __snd_usbmidi_create(chip->card, iface,
&chip->midi_list, NULL,
chip->usb_id,
&chip->num_rawmidis);
int err = snd_usb_midi_v2_create(chip, iface, NULL,
chip->usb_id);
if (err < 0) {
dev_err(&dev->dev,
"%u:%d: cannot create sequencer device\n",
@ -486,6 +485,7 @@ static void snd_usb_audio_free(struct snd_card *card)
struct snd_usb_audio *chip = card->private_data;
snd_usb_endpoint_free_all(chip);
snd_usb_midi_v2_free_all(chip);
mutex_destroy(&chip->mutex);
if (!atomic_read(&chip->shutdown))
@ -645,6 +645,7 @@ static int snd_usb_audio_create(struct usb_interface *intf,
INIT_LIST_HEAD(&chip->iface_ref_list);
INIT_LIST_HEAD(&chip->clock_ref_list);
INIT_LIST_HEAD(&chip->midi_list);
INIT_LIST_HEAD(&chip->midi_v2_list);
INIT_LIST_HEAD(&chip->mixer_list);
if (quirk_flags[idx])
@ -969,6 +970,7 @@ static void usb_audio_disconnect(struct usb_interface *intf)
list_for_each(p, &chip->midi_list) {
snd_usbmidi_disconnect(p);
}
snd_usb_midi_v2_disconnect_all(chip);
/*
* Nice to check quirk && quirk->shares_media_device and
* then call the snd_media_device_delete(). Don't have
@ -1080,6 +1082,7 @@ static int usb_audio_suspend(struct usb_interface *intf, pm_message_t message)
snd_usbmidi_suspend(p);
list_for_each_entry(mixer, &chip->mixer_list, list)
snd_usb_mixer_suspend(mixer);
snd_usb_midi_v2_suspend_all(chip);
}
if (!PMSG_IS_AUTO(message) && !chip->system_suspend) {
@ -1125,6 +1128,8 @@ static int usb_audio_resume(struct usb_interface *intf)
snd_usbmidi_resume(p);
}
snd_usb_midi_v2_resume_all(chip);
out:
if (chip->num_suspended_intf == chip->system_suspend) {
snd_power_change_state(chip->card, SNDRV_CTL_POWER_D0);

1052
sound/usb/midi2.c Normal file

File diff suppressed because it is too large Load Diff

33
sound/usb/midi2.h Normal file
View File

@ -0,0 +1,33 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#ifndef __USB_AUDIO_MIDI2_H
#define __USB_AUDIO_MIDI2_H
#include "midi.h"
#if IS_ENABLED(CONFIG_SND_USB_AUDIO_MIDI_V2)
int snd_usb_midi_v2_create(struct snd_usb_audio *chip,
struct usb_interface *iface,
const struct snd_usb_audio_quirk *quirk,
unsigned int usb_id);
void snd_usb_midi_v2_suspend_all(struct snd_usb_audio *chip);
void snd_usb_midi_v2_resume_all(struct snd_usb_audio *chip);
void snd_usb_midi_v2_disconnect_all(struct snd_usb_audio *chip);
void snd_usb_midi_v2_free_all(struct snd_usb_audio *chip);
#else /* CONFIG_SND_USB_AUDIO_MIDI_V2 */
/* fallback to MIDI 1.0 creation */
static inline int snd_usb_midi_v2_create(struct snd_usb_audio *chip,
struct usb_interface *iface,
const struct snd_usb_audio_quirk *quirk,
unsigned int usb_id)
{
return __snd_usbmidi_create(chip->card, iface, &chip->midi_list,
quirk, usb_id, &chip->num_rawmidis);
}
static inline void snd_usb_midi_v2_suspend_all(struct snd_usb_audio *chip) {}
static inline void snd_usb_midi_v2_resume_all(struct snd_usb_audio *chip) {}
static inline void snd_usb_midi_v2_disconnect_all(struct snd_usb_audio *chip) {}
static inline void snd_usb_midi_v2_free_all(struct snd_usb_audio *chip) {}
#endif /* CONFIG_SND_USB_AUDIO_MIDI_V2 */
#endif /* __USB_AUDIO_MIDI2_H */

View File

@ -19,6 +19,7 @@
#include "mixer.h"
#include "mixer_quirks.h"
#include "midi.h"
#include "midi2.h"
#include "quirks.h"
#include "helper.h"
#include "endpoint.h"
@ -80,7 +81,7 @@ static int create_any_midi_quirk(struct snd_usb_audio *chip,
struct usb_driver *driver,
const struct snd_usb_audio_quirk *quirk)
{
return snd_usbmidi_create(chip->card, intf, &chip->midi_list, quirk);
return snd_usb_midi_v2_create(chip, intf, quirk, 0);
}
/*

View File

@ -51,6 +51,7 @@ struct snd_usb_audio {
unsigned int num_rawmidis; /* number of created rawmidi devices */
struct list_head midi_list; /* list of midi interfaces */
struct list_head midi_v2_list; /* list of MIDI 2 interfaces */
struct list_head mixer_list; /* list of mixer interfaces */