Commit 8ff0d683 authored by Clemens Ladisch's avatar Clemens Ladisch Committed by Luis Henriques

ALSA: usb-audio: work around CH345 input SysEx corruption

commit a91e627e upstream.

One of the many faults of the QinHeng CH345 USB MIDI interface chip is
that it does not handle received SysEx messages correctly -- every second
event packet has a wrong code index number, which is the one from the last
seen message, instead of 4.  For example, the two messages "FE F0 01 02 03
04 05 06 07 08 09 0A 0B 0C 0D 0E F7" result in the following event
packets:

correct:       CH345:
0F FE 00 00    0F FE 00 00
04 F0 01 02    04 F0 01 02
04 03 04 05    0F 03 04 05
04 06 07 08    04 06 07 08
04 09 0A 0B    0F 09 0A 0B
04 0C 0D 0E    04 0C 0D 0E
05 F7 00 00    05 F7 00 00

A class-compliant driver must interpret an event packet with CIN 15 as
having a single data byte, so the other two bytes would be ignored.  The
message received by the host would then be missing two bytes out of six;
in this example, "F0 01 02 03 06 07 08 09 0C 0D 0E F7".

These corrupted SysEx event packages contain only data bytes, while the
CH345 uses event packets with a correct CIN value only for messages with
a status byte, so it is possible to distinguish between these two cases by
checking for the presence of this status byte.

(Other bugs in the CH345's input handling, such as the corruption resulting
from running status, cannot be worked around.)
Signed-off-by: default avatarClemens Ladisch <clemens@ladisch.de>
Signed-off-by: default avatarTakashi Iwai <tiwai@suse.de>
Signed-off-by: default avatarLuis Henriques <luis.henriques@canonical.com>
parent 88a85146
...@@ -174,6 +174,8 @@ struct snd_usb_midi_in_endpoint { ...@@ -174,6 +174,8 @@ struct snd_usb_midi_in_endpoint {
u8 running_status_length; u8 running_status_length;
} ports[0x10]; } ports[0x10];
u8 seen_f5; u8 seen_f5;
bool in_sysex;
u8 last_cin;
u8 error_resubmit; u8 error_resubmit;
int current_port; int current_port;
}; };
...@@ -464,6 +466,39 @@ static void snd_usbmidi_maudio_broken_running_status_input( ...@@ -464,6 +466,39 @@ static void snd_usbmidi_maudio_broken_running_status_input(
} }
} }
/*
* QinHeng CH345 is buggy: every second packet inside a SysEx has not CIN 4
* but the previously seen CIN, but still with three data bytes.
*/
static void ch345_broken_sysex_input(struct snd_usb_midi_in_endpoint *ep,
uint8_t *buffer, int buffer_length)
{
unsigned int i, cin, length;
for (i = 0; i + 3 < buffer_length; i += 4) {
if (buffer[i] == 0 && i > 0)
break;
cin = buffer[i] & 0x0f;
if (ep->in_sysex &&
cin == ep->last_cin &&
(buffer[i + 1 + (cin == 0x6)] & 0x80) == 0)
cin = 0x4;
#if 0
if (buffer[i + 1] == 0x90) {
/*
* Either a corrupted running status or a real note-on
* message; impossible to detect reliably.
*/
}
#endif
length = snd_usbmidi_cin_length[cin];
snd_usbmidi_input_data(ep, 0, &buffer[i + 1], length);
ep->in_sysex = cin == 0x4;
if (!ep->in_sysex)
ep->last_cin = cin;
}
}
/* /*
* CME protocol: like the standard protocol, but SysEx commands are sent as a * CME protocol: like the standard protocol, but SysEx commands are sent as a
* single USB packet preceded by a 0x0F byte. * single USB packet preceded by a 0x0F byte.
...@@ -650,6 +685,12 @@ static struct usb_protocol_ops snd_usbmidi_cme_ops = { ...@@ -650,6 +685,12 @@ static struct usb_protocol_ops snd_usbmidi_cme_ops = {
.output_packet = snd_usbmidi_output_standard_packet, .output_packet = snd_usbmidi_output_standard_packet,
}; };
static struct usb_protocol_ops snd_usbmidi_ch345_broken_sysex_ops = {
.input = ch345_broken_sysex_input,
.output = snd_usbmidi_standard_output,
.output_packet = snd_usbmidi_output_standard_packet,
};
/* /*
* AKAI MPD16 protocol: * AKAI MPD16 protocol:
* *
...@@ -2295,6 +2336,7 @@ int snd_usbmidi_create(struct snd_card *card, ...@@ -2295,6 +2336,7 @@ int snd_usbmidi_create(struct snd_card *card,
err = snd_usbmidi_detect_per_port_endpoints(umidi, endpoints); err = snd_usbmidi_detect_per_port_endpoints(umidi, endpoints);
break; break;
case QUIRK_MIDI_CH345: case QUIRK_MIDI_CH345:
umidi->usb_protocol_ops = &snd_usbmidi_ch345_broken_sysex_ops;
err = snd_usbmidi_detect_per_port_endpoints(umidi, endpoints); err = snd_usbmidi_detect_per_port_endpoints(umidi, endpoints);
break; break;
default: default:
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment