From patchwork Fri May 19 09:30:51 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Takashi Iwai X-Patchwork-Id: 13248093 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from alsa0.perex.cz (alsa0.perex.cz [77.48.224.243]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id 92788C77B75 for ; Fri, 19 May 2023 09:36:45 +0000 (UTC) Received: from alsa1.perex.cz (alsa1.perex.cz [207.180.221.201]) (using TLSv1.2 with cipher ADH-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by alsa0.perex.cz (Postfix) with ESMTPS id 7CE2F839; Fri, 19 May 2023 11:35:53 +0200 (CEST) DKIM-Filter: OpenDKIM Filter v2.11.0 alsa0.perex.cz 7CE2F839 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=alsa-project.org; s=default; t=1684489003; bh=M+aTpwHtBC29dnUVCvYJ+1poAvybRrWV6B8hLOJ2l+Y=; h=From:To:Cc:Subject:Date:In-Reply-To:References:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=ggxGzXCBGn6NDkXLY8ExlJtDvh0WufdDSDm1SqkNngrXIIoz/cx9vKRvjjqOLZMOp MLYT5S+nwo2OtYmLRjER5EWxbkJiJxUGYjwcww670Mh5eJ5Qb3yXWJbtjhN7rtB0Uf iSMPXZyBWpTh6JmXSK/JE/DtauzvT8uqC3fr+Zlg= Received: by alsa1.perex.cz (Postfix, from userid 50401) id 90DA2F80635; Fri, 19 May 2023 11:32:31 +0200 (CEST) Received: from mailman-core.alsa-project.org (mailman-core.alsa-project.org [10.254.200.10]) by alsa1.perex.cz (Postfix) with ESMTP id 6153EF80633; Fri, 19 May 2023 11:32:31 +0200 (CEST) Received: by alsa1.perex.cz (Postfix, from userid 50401) id 969B7F805FF; Fri, 19 May 2023 11:32:11 +0200 (CEST) Received: from smtp-out2.suse.de (smtp-out2.suse.de [195.135.220.29]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by alsa1.perex.cz (Postfix) with ESMTPS id 4542FF80552 for ; Fri, 19 May 2023 11:31:38 +0200 (CEST) DKIM-Filter: OpenDKIM Filter v2.11.0 alsa1.perex.cz 4542FF80552 Authentication-Results: alsa1.perex.cz; dkim=pass (1024-bit key, unprotected) header.d=suse.de header.i=@suse.de header.a=rsa-sha256 header.s=susede2_rsa header.b=k2NsXHe/; dkim=pass header.d=suse.de header.i=@suse.de header.a=ed25519-sha256 header.s=susede2_ed25519 header.b=Ib/lZX0z Received: from imap2.suse-dmz.suse.de (imap2.suse-dmz.suse.de [192.168.254.74]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (P-521) server-digest SHA512) (No client certificate requested) by smtp-out2.suse.de (Postfix) with ESMTPS id 146F11FE32; Fri, 19 May 2023 09:31:38 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=suse.de; s=susede2_rsa; t=1684488698; h=from:from:reply-to:date:date:message-id:message-id:to:to:cc:cc: mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=VYglo15OdV0d17dkMcvfsb7GOMc9b53r0UvZJBMGZoU=; b=k2NsXHe/4T/zA9OnwQK5S505XXn0nOd0lXcBtCTZh44p5y6T7/uo6OI6fLfzktPBUYa1nl tbofpHeqt8hCaUbqjFJ+S44z5RG407DtSqtIm6Nft3hUsIHhLQbVqg+gVYAcPR8/Y3Tm+w +Ig2EtMODHnIKXazcswUVvbarChp/5A= DKIM-Signature: v=1; a=ed25519-sha256; c=relaxed/relaxed; d=suse.de; s=susede2_ed25519; t=1684488698; h=from:from:reply-to:date:date:message-id:message-id:to:to:cc:cc: mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=VYglo15OdV0d17dkMcvfsb7GOMc9b53r0UvZJBMGZoU=; b=Ib/lZX0zdGoitMiaw1+ys2gBVIAeWckeVwvnbb15htZplaT+CkR53nJVXzZde5n4dI5x2T LRc+Wn8AFzh+OOBA== Received: from imap2.suse-dmz.suse.de (imap2.suse-dmz.suse.de [192.168.254.74]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (P-521) server-digest SHA512) (No client certificate requested) by imap2.suse-dmz.suse.de (Postfix) with ESMTPS id DD86813A12; Fri, 19 May 2023 09:31:37 +0000 (UTC) Received: from dovecot-director2.suse.de ([192.168.254.65]) by imap2.suse-dmz.suse.de with ESMTPSA id 0B81NflBZ2RXJAAAMHmgww (envelope-from ); Fri, 19 May 2023 09:31:37 +0000 From: Takashi Iwai To: alsa-devel@alsa-project.org Cc: linux-kernel@vger.kernel.org Subject: [PATCH 13/36] ALSA: ump: Add legacy raw MIDI support Date: Fri, 19 May 2023 11:30:51 +0200 Message-Id: <20230519093114.28813-14-tiwai@suse.de> X-Mailer: git-send-email 2.35.3 In-Reply-To: <20230519093114.28813-1-tiwai@suse.de> References: <20230519093114.28813-1-tiwai@suse.de> MIME-Version: 1.0 Message-ID-Hash: HG6Q6ILMAJS2JWWZF5NE4OTK562KYHXN X-Message-ID-Hash: HG6Q6ILMAJS2JWWZF5NE4OTK562KYHXN X-MailFrom: tiwai@suse.de X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; emergency; loop; banned-address; member-moderation; header-match-alsa-devel.alsa-project.org-0; header-match-alsa-devel.alsa-project.org-1; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header X-Mailman-Version: 3.3.8 Precedence: list List-Id: "Alsa-devel mailing list for ALSA developers - http://www.alsa-project.org" Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: This patch extends the UMP core code to support the legacy MIDI 1.0 rawmidi devices. When the new kconfig CONFIG_SND_UMP_LEGACY_RAWMIDI is set, the UMP core allows to attach an additional rawmidi device for each UMP Endpoint. The rawmidi device contains 16 substreams where each substream corresponds to a UMP Group belonging to the EP. The device reads/writes the legacy MIDI 1.0 byte streams and translates from/to UMP packets. The legacy rawmidi devices are exclusive with the UMP rawmidi devices, hence both of them can't be opened at the same time unless the UMP rawmidi is opened in APPEND mode. Signed-off-by: Takashi Iwai Reviewed-by: Jaroslav Kysela --- include/sound/ump.h | 30 +++ include/sound/ump_msg.h | 540 +++++++++++++++++++++++++++++++++++++++ sound/core/Kconfig | 9 + sound/core/Makefile | 1 + sound/core/ump.c | 266 ++++++++++++++++++- sound/core/ump_convert.c | 520 +++++++++++++++++++++++++++++++++++++ sound/core/ump_convert.h | 43 ++++ 7 files changed, 1406 insertions(+), 3 deletions(-) create mode 100644 include/sound/ump_msg.h create mode 100644 sound/core/ump_convert.c create mode 100644 sound/core/ump_convert.h diff --git a/include/sound/ump.h b/include/sound/ump.h index 6f786b462f16..45f4c9b673b5 100644 --- a/include/sound/ump.h +++ b/include/sound/ump.h @@ -10,6 +10,7 @@ struct snd_ump_endpoint; struct snd_ump_block; struct snd_ump_ops; +struct ump_cvt_to_ump; struct snd_ump_endpoint { struct snd_rawmidi core; /* raw UMP access */ @@ -23,6 +24,24 @@ struct snd_ump_endpoint { void (*private_free)(struct snd_ump_endpoint *ump); struct list_head block_list; /* list of snd_ump_block objects */ + + /* intermediate buffer for UMP input */ + u32 input_buf[4]; + int input_buf_head; + int input_pending; + +#if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI) + struct mutex open_mutex; + + spinlock_t legacy_locks[2]; + struct snd_rawmidi *legacy_rmidi; + struct snd_rawmidi_substream *legacy_substreams[2][SNDRV_UMP_MAX_GROUPS]; + + /* for legacy output; need to open the actual substream unlike input */ + int legacy_out_opens; + struct snd_rawmidi_file legacy_out_rfile; + struct ump_cvt_to_ump *out_cvts; +#endif }; /* ops filled by UMP drivers */ @@ -54,6 +73,17 @@ int snd_ump_block_new(struct snd_ump_endpoint *ump, unsigned int blk, int snd_ump_receive(struct snd_ump_endpoint *ump, const u32 *buffer, int count); int snd_ump_transmit(struct snd_ump_endpoint *ump, u32 *buffer, int count); +#if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI) +int snd_ump_attach_legacy_rawmidi(struct snd_ump_endpoint *ump, + char *id, int device); +#else +static inline int snd_ump_attach_legacy_rawmidi(struct snd_ump_endpoint *ump, + char *id, int device) +{ + return 0; +} +#endif + /* * Some definitions for UMP */ diff --git a/include/sound/ump_msg.h b/include/sound/ump_msg.h new file mode 100644 index 000000000000..c76c39944a5f --- /dev/null +++ b/include/sound/ump_msg.h @@ -0,0 +1,540 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Universal MIDI Packet (UMP): Message Definitions + */ +#ifndef __SOUND_UMP_MSG_H +#define __SOUND_UMP_MSG_H + +/* MIDI 1.0 / 2.0 Status Code (4bit) */ +enum { + UMP_MSG_STATUS_PER_NOTE_RCC = 0x0, + UMP_MSG_STATUS_PER_NOTE_ACC = 0x1, + UMP_MSG_STATUS_RPN = 0x2, + UMP_MSG_STATUS_NRPN = 0x3, + UMP_MSG_STATUS_RELATIVE_RPN = 0x4, + UMP_MSG_STATUS_RELATIVE_NRPN = 0x5, + UMP_MSG_STATUS_PER_NOTE_PITCH_BEND = 0x6, + UMP_MSG_STATUS_NOTE_OFF = 0x8, + UMP_MSG_STATUS_NOTE_ON = 0x9, + UMP_MSG_STATUS_POLY_PRESSURE = 0xa, + UMP_MSG_STATUS_CC = 0xb, + UMP_MSG_STATUS_PROGRAM = 0xc, + UMP_MSG_STATUS_CHANNEL_PRESSURE = 0xd, + UMP_MSG_STATUS_PITCH_BEND = 0xe, + UMP_MSG_STATUS_PER_NOTE_MGMT = 0xf, +}; + +/* MIDI 1.0 Channel Control (7bit) */ +enum { + UMP_CC_BANK_SELECT = 0, + UMP_CC_MODULATION = 1, + UMP_CC_BREATH = 2, + UMP_CC_FOOT = 4, + UMP_CC_PORTAMENTO_TIME = 5, + UMP_CC_DATA = 6, + UMP_CC_VOLUME = 7, + UMP_CC_BALANCE = 8, + UMP_CC_PAN = 10, + UMP_CC_EXPRESSION = 11, + UMP_CC_EFFECT_CONTROL_1 = 12, + UMP_CC_EFFECT_CONTROL_2 = 13, + UMP_CC_GP_1 = 16, + UMP_CC_GP_2 = 17, + UMP_CC_GP_3 = 18, + UMP_CC_GP_4 = 19, + UMP_CC_BANK_SELECT_LSB = 32, + UMP_CC_MODULATION_LSB = 33, + UMP_CC_BREATH_LSB = 34, + UMP_CC_FOOT_LSB = 36, + UMP_CC_PORTAMENTO_TIME_LSB = 37, + UMP_CC_DATA_LSB = 38, + UMP_CC_VOLUME_LSB = 39, + UMP_CC_BALANCE_LSB = 40, + UMP_CC_PAN_LSB = 42, + UMP_CC_EXPRESSION_LSB = 43, + UMP_CC_EFFECT1_LSB = 44, + UMP_CC_EFFECT2_LSB = 45, + UMP_CC_GP_1_LSB = 48, + UMP_CC_GP_2_LSB = 49, + UMP_CC_GP_3_LSB = 50, + UMP_CC_GP_4_LSB = 51, + UMP_CC_SUSTAIN = 64, + UMP_CC_PORTAMENTO_SWITCH = 65, + UMP_CC_SOSTENUTO = 66, + UMP_CC_SOFT_PEDAL = 67, + UMP_CC_LEGATO = 68, + UMP_CC_HOLD_2 = 69, + UMP_CC_SOUND_CONTROLLER_1 = 70, + UMP_CC_SOUND_CONTROLLER_2 = 71, + UMP_CC_SOUND_CONTROLLER_3 = 72, + UMP_CC_SOUND_CONTROLLER_4 = 73, + UMP_CC_SOUND_CONTROLLER_5 = 74, + UMP_CC_SOUND_CONTROLLER_6 = 75, + UMP_CC_SOUND_CONTROLLER_7 = 76, + UMP_CC_SOUND_CONTROLLER_8 = 77, + UMP_CC_SOUND_CONTROLLER_9 = 78, + UMP_CC_SOUND_CONTROLLER_10 = 79, + UMP_CC_GP_5 = 80, + UMP_CC_GP_6 = 81, + UMP_CC_GP_7 = 82, + UMP_CC_GP_8 = 83, + UMP_CC_PORTAMENTO_CONTROL = 84, + UMP_CC_EFFECT_1 = 91, + UMP_CC_EFFECT_2 = 92, + UMP_CC_EFFECT_3 = 93, + UMP_CC_EFFECT_4 = 94, + UMP_CC_EFFECT_5 = 95, + UMP_CC_DATA_INC = 96, + UMP_CC_DATA_DEC = 97, + UMP_CC_NRPN_LSB = 98, + UMP_CC_NRPN_MSB = 99, + UMP_CC_RPN_LSB = 100, + UMP_CC_RPN_MSB = 101, + UMP_CC_ALL_SOUND_OFF = 120, + UMP_CC_RESET_ALL = 121, + UMP_CC_LOCAL_CONTROL = 122, + UMP_CC_ALL_NOTES_OFF = 123, + UMP_CC_OMNI_OFF = 124, + UMP_CC_OMNI_ON = 125, + UMP_CC_POLY_OFF = 126, + UMP_CC_POLY_ON = 127, +}; + +/* MIDI 1.0 / 2.0 System Messages (0xfx) */ +enum { + UMP_SYSTEM_STATUS_MIDI_TIME_CODE = 0xf1, + UMP_SYSTEM_STATUS_SONG_POSITION = 0xf2, + UMP_SYSTEM_STATUS_SONG_SELECT = 0xf3, + UMP_SYSTEM_STATUS_TUNE_REQUEST = 0xf6, + UMP_SYSTEM_STATUS_TIMING_CLOCK = 0xf8, + UMP_SYSTEM_STATUS_START = 0xfa, + UMP_SYSTEM_STATUS_CONTINUE = 0xfb, + UMP_SYSTEM_STATUS_STOP = 0xfc, + UMP_SYSTEM_STATUS_ACTIVE_SENSING = 0xfe, + UMP_SYSTEM_STATUS_RESET = 0xff, +}; + +/* MIDI 1.0 Realtime and SysEx status messages (0xfx) */ +enum { + UMP_MIDI1_MSG_REALTIME = 0xf0, /* mask */ + UMP_MIDI1_MSG_SYSEX_START = 0xf0, + UMP_MIDI1_MSG_SYSEX_END = 0xf7, +}; + +/* + * UMP Message Definitions + */ + +/* MIDI 1.0 Note Off / Note On (32bit) */ +struct snd_ump_midi1_msg_note { +#ifdef __BIG_ENDIAN_BITFIELD + u32 type:4; + u32 group:4; + u32 status:4; + u32 channel:4; + u32 note:8; + u32 velocity:8; +#else + u32 velocity:8; + u32 note:8; + u32 channel:4; + u32 status:4; + u32 group:4; + u32 type:4; +#endif +} __packed; + +/* MIDI 1.0 Poly Pressure (32bit) */ +struct snd_ump_midi1_msg_paf { +#ifdef __BIG_ENDIAN_BITFIELD + u32 type:4; + u32 group:4; + u32 status:4; + u32 channel:4; + u32 note:8; + u32 data:8; +#else + u32 data:8; + u32 note:8; + u32 channel:4; + u32 status:4; + u32 group:4; + u32 type:4; +#endif +} __packed; + +/* MIDI 1.0 Control Change (32bit) */ +struct snd_ump_midi1_msg_cc { +#ifdef __BIG_ENDIAN_BITFIELD + u32 type:4; + u32 group:4; + u32 status:4; + u32 channel:4; + u32 index:8; + u32 data:8; +#else + u32 data:8; + u32 index:8; + u32 channel:4; + u32 status:4; + u32 group:4; + u32 type:4; +#endif +} __packed; + +/* MIDI 1.0 Program Change (32bit) */ +struct snd_ump_midi1_msg_program { +#ifdef __BIG_ENDIAN_BITFIELD + u32 type:4; + u32 group:4; + u32 status:4; + u32 channel:4; + u32 program:8; + u32 reserved:8; +#else +#endif + u32 reserved:8; + u32 program:8; + u32 channel:4; + u32 status:4; + u32 group:4; + u32 type:4; +} __packed; + +/* MIDI 1.0 Channel Pressure (32bit) */ +struct snd_ump_midi1_msg_caf { +#ifdef __BIG_ENDIAN_BITFIELD + u32 type:4; + u32 group:4; + u32 status:4; + u32 channel:4; + u32 data:8; + u32 reserved:8; +#else + u32 reserved:8; + u32 data:8; + u32 channel:4; + u32 status:4; + u32 group:4; + u32 type:4; +#endif +} __packed; + +/* MIDI 1.0 Pitch Bend (32bit) */ +struct snd_ump_midi1_msg_pitchbend { +#ifdef __BIG_ENDIAN_BITFIELD + u32 type:4; + u32 group:4; + u32 status:4; + u32 channel:4; + u32 data_lsb:8; + u32 data_msb:8; +#else + u32 data_msb:8; + u32 data_lsb:8; + u32 channel:4; + u32 status:4; + u32 group:4; + u32 type:4; +#endif +} __packed; + +/* System Common and Real Time messages (32bit); no channel field */ +struct snd_ump_system_msg { +#ifdef __BIG_ENDIAN_BITFIELD + u32 type:4; + u32 group:4; + u32 status:8; + u32 parm1:8; + u32 parm2:8; +#else + u32 parm2:8; + u32 parm1:8; + u32 status:8; + u32 group:4; + u32 type:4; +#endif +} __packed; + +/* MIDI 1.0 UMP CVM (32bit) */ +union snd_ump_midi1_msg { + struct snd_ump_midi1_msg_note note; + struct snd_ump_midi1_msg_paf paf; + struct snd_ump_midi1_msg_cc cc; + struct snd_ump_midi1_msg_program pg; + struct snd_ump_midi1_msg_caf caf; + struct snd_ump_midi1_msg_pitchbend pb; + struct snd_ump_system_msg system; + u32 raw; +}; + +/* MIDI 2.0 Note Off / Note On (64bit) */ +struct snd_ump_midi2_msg_note { +#ifdef __BIG_ENDIAN_BITFIELD + /* 0 */ + u32 type:4; + u32 group:4; + u32 status:4; + u32 channel:4; + u32 note:8; + u32 attribute_type:8; + /* 1 */ + u32 velocity:16; + u32 attribute_data:16; +#else + /* 0 */ + u32 attribute_type:8; + u32 note:8; + u32 channel:4; + u32 status:4; + u32 group:4; + u32 type:4; + /* 1 */ + u32 attribute_data:16; + u32 velocity:16; +#endif +} __packed; + +/* MIDI 2.0 Poly Pressure (64bit) */ +struct snd_ump_midi2_msg_paf { +#ifdef __BIG_ENDIAN_BITFIELD + /* 0 */ + u32 type:4; + u32 group:4; + u32 status:4; + u32 channel:4; + u32 note:8; + u32 reserved:8; + /* 1 */ + u32 data; +#else + /* 0 */ + u32 reserved:8; + u32 note:8; + u32 channel:4; + u32 status:4; + u32 group:4; + u32 type:4; + /* 1 */ + u32 data; +#endif +} __packed; + +/* MIDI 2.0 Per-Note Controller (64bit) */ +struct snd_ump_midi2_msg_pernote_cc { +#ifdef __BIG_ENDIAN_BITFIELD + /* 0 */ + u32 type:4; + u32 group:4; + u32 status:4; + u32 channel:4; + u32 note:8; + u32 index:8; + /* 1 */ + u32 data; +#else + /* 0 */ + u32 index:8; + u32 note:8; + u32 channel:4; + u32 status:4; + u32 group:4; + u32 type:4; + /* 1 */ + u32 data; +#endif +} __packed; + +/* MIDI 2.0 Per-Note Management (64bit) */ +struct snd_ump_midi2_msg_pernote_mgmt { +#ifdef __BIG_ENDIAN_BITFIELD + /* 0 */ + u32 type:4; + u32 group:4; + u32 status:4; + u32 channel:4; + u32 note:8; + u32 flags:8; + /* 1 */ + u32 reserved; +#else + /* 0 */ + u32 flags:8; + u32 note:8; + u32 channel:4; + u32 status:4; + u32 group:4; + u32 type:4; + /* 1 */ + u32 reserved; +#endif +} __packed; + +/* MIDI 2.0 Control Change (64bit) */ +struct snd_ump_midi2_msg_cc { +#ifdef __BIG_ENDIAN_BITFIELD + /* 0 */ + u32 type:4; + u32 group:4; + u32 status:4; + u32 channel:4; + u32 index:8; + u32 reserved:8; + /* 1 */ + u32 data; +#else + /* 0 */ + u32 reserved:8; + u32 index:8; + u32 channel:4; + u32 status:4; + u32 group:4; + u32 type:4; + /* 1 */ + u32 data; +#endif +} __packed; + +/* MIDI 2.0 Registered Controller (RPN) / Assignable Controller (NRPN) (64bit) */ +struct snd_ump_midi2_msg_rpn { +#ifdef __BIG_ENDIAN_BITFIELD + /* 0 */ + u32 type:4; + u32 group:4; + u32 status:4; + u32 channel:4; + u32 bank:8; + u32 index:8; + /* 1 */ + u32 data; +#else + /* 0 */ + u32 index:8; + u32 bank:8; + u32 channel:4; + u32 status:4; + u32 group:4; + u32 type:4; + /* 1 */ + u32 data; +#endif +} __packed; + +/* MIDI 2.0 Program Change (64bit) */ +struct snd_ump_midi2_msg_program { +#ifdef __BIG_ENDIAN_BITFIELD + /* 0 */ + u32 type:4; + u32 group:4; + u32 status:4; + u32 channel:4; + u32 reserved:15; + u32 bank_valid:1; + /* 1 */ + u32 program:8; + u32 reserved2:8; + u32 bank_msb:8; + u32 bank_lsb:8; +#else + /* 0 */ + u32 bank_valid:1; + u32 reserved:15; + u32 channel:4; + u32 status:4; + u32 group:4; + u32 type:4; + /* 1 */ + u32 bank_lsb:8; + u32 bank_msb:8; + u32 reserved2:8; + u32 program:8; +#endif +} __packed; + +/* MIDI 2.0 Channel Pressure (64bit) */ +struct snd_ump_midi2_msg_caf { +#ifdef __BIG_ENDIAN_BITFIELD + /* 0 */ + u32 type:4; + u32 group:4; + u32 status:4; + u32 channel:4; + u32 reserved:16; + /* 1 */ + u32 data; +#else + /* 0 */ + u32 reserved:16; + u32 channel:4; + u32 status:4; + u32 group:4; + u32 type:4; + /* 1 */ + u32 data; +#endif +} __packed; + +/* MIDI 2.0 Pitch Bend (64bit) */ +struct snd_ump_midi2_msg_pitchbend { +#ifdef __BIG_ENDIAN_BITFIELD + /* 0 */ + u32 type:4; + u32 group:4; + u32 status:4; + u32 channel:4; + u32 reserved:16; + /* 1 */ + u32 data; +#else + /* 0 */ + u32 reserved:16; + u32 channel:4; + u32 status:4; + u32 group:4; + u32 type:4; + /* 1 */ + u32 data; +#endif +} __packed; + +/* MIDI 2.0 Per-Note Pitch Bend (64bit) */ +struct snd_ump_midi2_msg_pernote_pitchbend { +#ifdef __BIG_ENDIAN_BITFIELD + /* 0 */ + u32 type:4; + u32 group:4; + u32 status:4; + u32 channel:4; + u32 note:8; + u32 reserved:8; + /* 1 */ + u32 data; +#else + /* 0 */ + u32 reserved:8; + u32 note:8; + u32 channel:4; + u32 status:4; + u32 group:4; + u32 type:4; + /* 1 */ + u32 data; +#endif +} __packed; + +/* MIDI 2.0 UMP CVM (64bit) */ +union snd_ump_midi2_msg { + struct snd_ump_midi2_msg_note note; + struct snd_ump_midi2_msg_paf paf; + struct snd_ump_midi2_msg_pernote_cc pernote_cc; + struct snd_ump_midi2_msg_pernote_mgmt pernote_mgmt; + struct snd_ump_midi2_msg_cc cc; + struct snd_ump_midi2_msg_rpn rpn; + struct snd_ump_midi2_msg_program pg; + struct snd_ump_midi2_msg_caf caf; + struct snd_ump_midi2_msg_pitchbend pb; + struct snd_ump_midi2_msg_pernote_pitchbend pernote_pb; + u32 raw[2]; +}; + +#endif /* __SOUND_UMP_MSG_H */ diff --git a/sound/core/Kconfig b/sound/core/Kconfig index eb1c6c930de9..e41818e59a15 100644 --- a/sound/core/Kconfig +++ b/sound/core/Kconfig @@ -30,6 +30,15 @@ config SND_UMP tristate select SND_RAWMIDI +config SND_UMP_LEGACY_RAWMIDI + bool "Legacy raw MIDI support for UMP streams" + depends on SND_UMP + help + This option enables the legacy raw MIDI support for UMP streams. + When this option is set, an additional rawmidi device for the + legacy MIDI 1.0 byte streams is created for each UMP Endpoint. + The device contains 16 substreams corresponding to UMP groups. + config SND_COMPRESS_OFFLOAD tristate diff --git a/sound/core/Makefile b/sound/core/Makefile index 562a05edbc50..a6b444ee2832 100644 --- a/sound/core/Makefile +++ b/sound/core/Makefile @@ -29,6 +29,7 @@ snd-pcm-dmaengine-objs := pcm_dmaengine.o snd-ctl-led-objs := control_led.o snd-rawmidi-objs := rawmidi.o snd-ump-objs := ump.o +snd-ump-$(CONFIG_SND_UMP_LEGACY_RAWMIDI) += ump_convert.o snd-timer-objs := timer.o snd-hrtimer-objs := hrtimer.o snd-rtctimer-objs := rtctimer.o diff --git a/sound/core/ump.c b/sound/core/ump.c index 0d5ca95cd007..176789090896 100644 --- a/sound/core/ump.c +++ b/sound/core/ump.c @@ -11,6 +11,7 @@ #include #include #include +#include "ump_convert.h" #define ump_err(ump, fmt, args...) dev_err(&(ump)->core.dev, fmt, ##args) #define ump_warn(ump, fmt, args...) dev_warn(&(ump)->core.dev, fmt, ##args) @@ -29,6 +30,23 @@ static void snd_ump_rawmidi_trigger(struct snd_rawmidi_substream *substream, int up); static void snd_ump_rawmidi_drain(struct snd_rawmidi_substream *substream); +#if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI) +static int process_legacy_output(struct snd_ump_endpoint *ump, + u32 *buffer, int count); +static void process_legacy_input(struct snd_ump_endpoint *ump, const u32 *src, + int words); +#else +static inline int process_legacy_output(struct snd_ump_endpoint *ump, + u32 *buffer, int count) +{ + return 0; +} +static inline void process_legacy_input(struct snd_ump_endpoint *ump, + const u32 *src, int words) +{ +} +#endif + static const struct snd_rawmidi_global_ops snd_ump_rawmidi_ops = { .dev_register = snd_ump_dev_register, .dev_unregister = snd_ump_dev_unregister, @@ -65,6 +83,10 @@ static void snd_ump_endpoint_free(struct snd_rawmidi *rmidi) if (ump->private_free) ump->private_free(ump); + +#if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI) + snd_ump_convert_free(ump); +#endif } /** @@ -110,6 +132,11 @@ int snd_ump_endpoint_new(struct snd_card *card, char *id, int device, if (!ump) return -ENOMEM; INIT_LIST_HEAD(&ump->block_list); +#if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI) + mutex_init(&ump->open_mutex); + spin_lock_init(&ump->legacy_locks[0]); + spin_lock_init(&ump->legacy_locks[1]); +#endif err = snd_rawmidi_init(&ump->core, card, id, device, output, input, info_flags); if (err < 0) { @@ -206,6 +233,33 @@ static void snd_ump_rawmidi_drain(struct snd_rawmidi_substream *substream) ump->ops->drain(ump, SNDRV_RAWMIDI_STREAM_OUTPUT); } +/* number of 32bit words per message type */ +static unsigned char ump_packet_words[0x10] = { + 1, 1, 1, 2, 2, 4, 1, 1, 2, 2, 2, 3, 3, 4, 4, 4 +}; + +/* parse the UMP packet data; + * the data is copied onto ump->input_buf[]. + * When a full packet is completed, returns the number of words (from 1 to 4). + * OTOH, if the packet is incomplete, returns 0. + */ +static int snd_ump_receive_ump_val(struct snd_ump_endpoint *ump, u32 val) +{ + int words; + + if (!ump->input_pending) + ump->input_pending = ump_packet_words[ump_message_type(val)]; + + ump->input_buf[ump->input_buf_head++] = val; + ump->input_pending--; + if (!ump->input_pending) { + words = ump->input_buf_head; + ump->input_buf_head = 0; + return words; + } + return 0; +} + /** * snd_ump_receive - transfer UMP packets from the device * @ump: the UMP endpoint @@ -218,9 +272,18 @@ static void snd_ump_rawmidi_drain(struct snd_rawmidi_substream *substream) */ int snd_ump_receive(struct snd_ump_endpoint *ump, const u32 *buffer, int count) { - struct snd_rawmidi_substream *substream = - ump->substreams[SNDRV_RAWMIDI_STREAM_INPUT]; + struct snd_rawmidi_substream *substream; + const u32 *p = buffer; + int n, words = count >> 2; + while (words--) { + n = snd_ump_receive_ump_val(ump, *p++); + if (!n) + continue; + process_legacy_input(ump, ump->input_buf, n); + } + + substream = ump->substreams[SNDRV_RAWMIDI_STREAM_INPUT]; if (!substream) return 0; return snd_rawmidi_receive(substream, (const char *)buffer, count); @@ -241,10 +304,15 @@ int snd_ump_transmit(struct snd_ump_endpoint *ump, u32 *buffer, int count) { struct snd_rawmidi_substream *substream = ump->substreams[SNDRV_RAWMIDI_STREAM_OUTPUT]; + int err; if (!substream) return -ENODEV; - return snd_rawmidi_transmit(substream, (char *)buffer, count); + err = snd_rawmidi_transmit(substream, (char *)buffer, count); + /* received either data or an error? */ + if (err) + return err; + return process_legacy_output(ump, buffer, count); } EXPORT_SYMBOL_GPL(snd_ump_transmit); @@ -387,5 +455,197 @@ static void snd_ump_proc_read(struct snd_info_entry *entry, } } +#if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI) +/* + * Legacy rawmidi support + */ +static int snd_ump_legacy_open(struct snd_rawmidi_substream *substream) +{ + struct snd_ump_endpoint *ump = substream->rmidi->private_data; + int dir = substream->stream; + int group = substream->number; + int err; + + mutex_lock(&ump->open_mutex); + if (ump->legacy_substreams[dir][group]) { + err = -EBUSY; + goto unlock; + } + if (dir == SNDRV_RAWMIDI_STREAM_OUTPUT) { + if (!ump->legacy_out_opens) { + err = snd_rawmidi_kernel_open(&ump->core, 0, + SNDRV_RAWMIDI_LFLG_OUTPUT | + SNDRV_RAWMIDI_LFLG_APPEND, + &ump->legacy_out_rfile); + if (err < 0) + goto unlock; + } + ump->legacy_out_opens++; + snd_ump_reset_convert_to_ump(ump, group); + } + spin_lock_irq(&ump->legacy_locks[dir]); + ump->legacy_substreams[dir][group] = substream; + spin_unlock_irq(&ump->legacy_locks[dir]); + unlock: + mutex_unlock(&ump->open_mutex); + return 0; +} + +static int snd_ump_legacy_close(struct snd_rawmidi_substream *substream) +{ + struct snd_ump_endpoint *ump = substream->rmidi->private_data; + int dir = substream->stream; + int group = substream->number; + + mutex_lock(&ump->open_mutex); + spin_lock_irq(&ump->legacy_locks[dir]); + ump->legacy_substreams[dir][group] = NULL; + spin_unlock_irq(&ump->legacy_locks[dir]); + if (dir == SNDRV_RAWMIDI_STREAM_OUTPUT) { + if (!--ump->legacy_out_opens) + snd_rawmidi_kernel_release(&ump->legacy_out_rfile); + } + mutex_unlock(&ump->open_mutex); + return 0; +} + +static void snd_ump_legacy_trigger(struct snd_rawmidi_substream *substream, + int up) +{ + struct snd_ump_endpoint *ump = substream->rmidi->private_data; + int dir = substream->stream; + + ump->ops->trigger(ump, dir, up); +} + +static void snd_ump_legacy_drain(struct snd_rawmidi_substream *substream) +{ + struct snd_ump_endpoint *ump = substream->rmidi->private_data; + + if (ump->ops->drain) + ump->ops->drain(ump, SNDRV_RAWMIDI_STREAM_OUTPUT); +} + +static int snd_ump_legacy_dev_register(struct snd_rawmidi *rmidi) +{ + /* dummy, just for avoiding create superfluous seq clients */ + return 0; +} + +static const struct snd_rawmidi_ops snd_ump_legacy_input_ops = { + .open = snd_ump_legacy_open, + .close = snd_ump_legacy_close, + .trigger = snd_ump_legacy_trigger, +}; + +static const struct snd_rawmidi_ops snd_ump_legacy_output_ops = { + .open = snd_ump_legacy_open, + .close = snd_ump_legacy_close, + .trigger = snd_ump_legacy_trigger, + .drain = snd_ump_legacy_drain, +}; + +static const struct snd_rawmidi_global_ops snd_ump_legacy_ops = { + .dev_register = snd_ump_legacy_dev_register, +}; + +static int process_legacy_output(struct snd_ump_endpoint *ump, + u32 *buffer, int count) +{ + struct snd_rawmidi_substream *substream; + struct ump_cvt_to_ump *ctx; + const int dir = SNDRV_RAWMIDI_STREAM_OUTPUT; + unsigned char c; + int group, size = 0; + unsigned long flags; + + if (!ump->out_cvts || !ump->legacy_out_opens) + return 0; + + spin_lock_irqsave(&ump->legacy_locks[dir], flags); + for (group = 0; group < SNDRV_UMP_MAX_GROUPS; group++) { + substream = ump->legacy_substreams[dir][group]; + if (!substream) + continue; + ctx = &ump->out_cvts[group]; + while (!ctx->ump_bytes && + snd_rawmidi_transmit(substream, &c, 1) > 0) + snd_ump_convert_to_ump(ump, group, c); + if (ctx->ump_bytes && ctx->ump_bytes <= count) { + size = ctx->ump_bytes; + memcpy(buffer, ctx->ump, size); + ctx->ump_bytes = 0; + break; + } + } + spin_unlock_irqrestore(&ump->legacy_locks[dir], flags); + return size; +} + +static void process_legacy_input(struct snd_ump_endpoint *ump, const u32 *src, + int words) +{ + struct snd_rawmidi_substream *substream; + unsigned char buf[16]; + unsigned char group; + unsigned long flags; + const int dir = SNDRV_RAWMIDI_STREAM_INPUT; + int size; + + size = snd_ump_convert_from_ump(ump, src, buf, &group); + if (size <= 0) + return; + spin_lock_irqsave(&ump->legacy_locks[dir], flags); + substream = ump->legacy_substreams[dir][group]; + if (substream) + snd_rawmidi_receive(substream, buf, size); + spin_unlock_irqrestore(&ump->legacy_locks[dir], flags); +} + +int snd_ump_attach_legacy_rawmidi(struct snd_ump_endpoint *ump, + char *id, int device) +{ + unsigned int info_flags = SNDRV_RAWMIDI_INFO_UMP; + struct snd_rawmidi *rmidi; + bool input, output; + int err; + + err = snd_ump_convert_init(ump); + if (err < 0) + return err; + + input = ump->core.info_flags & SNDRV_RAWMIDI_INFO_INPUT; + output = ump->core.info_flags & SNDRV_RAWMIDI_INFO_OUTPUT; + if (input) + info_flags |= SNDRV_RAWMIDI_INFO_INPUT; + if (output) + info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT; + if (input && output) + info_flags |= SNDRV_RAWMIDI_INFO_DUPLEX; + + err = snd_rawmidi_new(ump->core.card, id, device, + output ? 16 : 0, input ? 16 : 0, + &rmidi); + if (err < 0) { + snd_ump_convert_free(ump); + return err; + } + + if (input) + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, + &snd_ump_legacy_input_ops); + if (output) + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, + &snd_ump_legacy_output_ops); + rmidi->info_flags = ump->core.info_flags & ~SNDRV_RAWMIDI_INFO_UMP; + rmidi->ops = &snd_ump_legacy_ops; + rmidi->private_data = ump; + ump->legacy_rmidi = rmidi; + ump_dbg(ump, "Created a legacy rawmidi #%d (%s)\n", device, id); + return 0; +} +EXPORT_SYMBOL_GPL(snd_ump_attach_legacy_rawmidi); +#endif /* CONFIG_SND_UMP_LEGACY_RAWMIDI */ + MODULE_DESCRIPTION("Universal MIDI Packet (UMP) Core Driver"); MODULE_LICENSE("GPL"); diff --git a/sound/core/ump_convert.c b/sound/core/ump_convert.c new file mode 100644 index 000000000000..cb7c2f959a27 --- /dev/null +++ b/sound/core/ump_convert.c @@ -0,0 +1,520 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Helpers for UMP <-> MIDI 1.0 byte stream conversion + */ + +#include +#include +#include +#include +#include +#include "ump_convert.h" + +/* + * Upgrade / downgrade value bits + */ +static u8 downscale_32_to_7bit(u32 src) +{ + return src >> 25; +} + +static u16 downscale_32_to_14bit(u32 src) +{ + return src >> 18; +} + +static u8 downscale_16_to_7bit(u16 src) +{ + return src >> 9; +} + +static u16 upscale_7_to_16bit(u8 src) +{ + u16 val, repeat; + + val = (u16)src << 9; + if (src <= 0x40) + return val; + repeat = src & 0x3f; + return val | (repeat << 3) | (repeat >> 3); +} + +static u32 upscale_7_to_32bit(u8 src) +{ + u32 val, repeat; + + val = src << 25; + if (src <= 0x40) + return val; + repeat = src & 0x3f; + return val | (repeat << 19) | (repeat << 13) | + (repeat << 7) | (repeat << 1) | (repeat >> 5); +} + +static u32 upscale_14_to_32bit(u16 src) +{ + u32 val, repeat; + + val = src << 18; + if (src <= 0x2000) + return val; + repeat = src & 0x1fff; + return val | (repeat << 5) | (repeat >> 8); +} + +/* + * UMP -> MIDI 1 byte stream conversion + */ +/* convert a UMP System message to MIDI 1.0 byte stream */ +static int cvt_ump_system_to_legacy(u32 data, unsigned char *buf) +{ + buf[0] = ump_message_status_channel(data); + switch (ump_message_status_code(data)) { + case UMP_SYSTEM_STATUS_MIDI_TIME_CODE: + case UMP_SYSTEM_STATUS_SONG_SELECT: + buf[1] = (data >> 8) & 0x7f; + return 1; + case UMP_SYSTEM_STATUS_SONG_POSITION: + buf[1] = (data >> 8) & 0x7f; + buf[2] = data & 0x7f; + return 3; + default: + return 1; + } +} + +/* convert a UMP MIDI 1.0 Channel Voice message to MIDI 1.0 byte stream */ +static int cvt_ump_midi1_to_legacy(u32 data, unsigned char *buf) +{ + buf[0] = ump_message_status_channel(data); + buf[1] = (data >> 8) & 0xff; + switch (ump_message_status_code(data)) { + case UMP_MSG_STATUS_PROGRAM: + case UMP_MSG_STATUS_CHANNEL_PRESSURE: + return 2; + default: + buf[2] = data & 0xff; + return 3; + } +} + +/* convert a UMP MIDI 2.0 Channel Voice message to MIDI 1.0 byte stream */ +static int cvt_ump_midi2_to_legacy(const union snd_ump_midi2_msg *midi2, + unsigned char *buf) +{ + unsigned char status = midi2->note.status; + unsigned char channel = midi2->note.channel; + u16 v; + + buf[0] = (status << 4) | channel; + switch (status) { + case UMP_MSG_STATUS_NOTE_OFF: + case UMP_MSG_STATUS_NOTE_ON: + buf[1] = midi2->note.note; + buf[2] = downscale_16_to_7bit(midi2->note.velocity); + if (status == UMP_MSG_STATUS_NOTE_ON && !buf[2]) + buf[2] = 1; + return 3; + case UMP_MSG_STATUS_POLY_PRESSURE: + buf[1] = midi2->paf.note; + buf[2] = downscale_32_to_7bit(midi2->paf.data); + return 3; + case UMP_MSG_STATUS_CC: + buf[1] = midi2->cc.index; + buf[2] = downscale_32_to_7bit(midi2->cc.data); + return 3; + case UMP_MSG_STATUS_CHANNEL_PRESSURE: + buf[1] = downscale_32_to_7bit(midi2->caf.data); + return 2; + case UMP_MSG_STATUS_PROGRAM: + if (midi2->pg.bank_valid) { + buf[0] = channel | (UMP_MSG_STATUS_CC << 4); + buf[1] = UMP_CC_BANK_SELECT; + buf[2] = midi2->pg.bank_msb; + buf[3] = channel | (UMP_MSG_STATUS_CC << 4); + buf[4] = UMP_CC_BANK_SELECT_LSB; + buf[5] = midi2->pg.bank_lsb; + buf[6] = channel | (UMP_MSG_STATUS_PROGRAM << 4); + buf[7] = midi2->pg.program; + return 8; + } + buf[1] = midi2->pg.program; + return 2; + case UMP_MSG_STATUS_PITCH_BEND: + v = downscale_32_to_14bit(midi2->pb.data); + buf[1] = v & 0x7f; + buf[2] = v >> 7; + return 3; + case UMP_MSG_STATUS_RPN: + case UMP_MSG_STATUS_NRPN: + buf[0] = channel | (UMP_MSG_STATUS_CC << 4); + buf[1] = status == UMP_MSG_STATUS_RPN ? UMP_CC_RPN_MSB : UMP_CC_NRPN_MSB; + buf[2] = midi2->rpn.bank; + buf[3] = buf[0]; + buf[4] = status == UMP_MSG_STATUS_RPN ? UMP_CC_RPN_LSB : UMP_CC_NRPN_LSB; + buf[5] = midi2->rpn.index; + buf[6] = buf[0]; + buf[7] = UMP_CC_DATA; + v = downscale_32_to_14bit(midi2->rpn.data); + buf[8] = v >> 7; + buf[9] = buf[0]; + buf[10] = UMP_CC_DATA_LSB; + buf[11] = v & 0x7f; + return 12; + default: + return 0; + } +} + +/* convert a UMP 7-bit SysEx message to MIDI 1.0 byte stream */ +static int cvt_ump_sysex7_to_legacy(const u32 *data, unsigned char *buf) +{ + unsigned char status; + unsigned char bytes; + int size, offset; + + status = ump_sysex_message_status(*data); + if (status > UMP_SYSEX_STATUS_END) + return 0; // unsupported, skip + bytes = ump_sysex_message_length(*data); + if (bytes > 6) + return 0; // skip + + size = 0; + if (status == UMP_SYSEX_STATUS_SINGLE || + status == UMP_SYSEX_STATUS_START) { + buf[0] = UMP_MIDI1_MSG_SYSEX_START; + size = 1; + } + + offset = 8; + for (; bytes; bytes--, size++) { + buf[size] = (*data >> offset) & 0x7f; + if (!offset) { + offset = 24; + data++; + } else { + offset -= 8; + } + } + + if (status == UMP_SYSEX_STATUS_SINGLE || + status == UMP_SYSEX_STATUS_END) + buf[size++] = UMP_MIDI1_MSG_SYSEX_END; + + return size; +} + +/* convert from a UMP packet @data to MIDI 1.0 bytes at @buf; + * the target group is stored at @group_ret, + * returns the number of bytes of MIDI 1.0 stream + */ +int snd_ump_convert_from_ump(struct snd_ump_endpoint *ump, + const u32 *data, + unsigned char *buf, + unsigned char *group_ret) +{ + *group_ret = ump_message_group(*data); + + switch (ump_message_type(*data)) { + case UMP_MSG_TYPE_SYSTEM: + return cvt_ump_system_to_legacy(*data, buf); + case UMP_MSG_TYPE_MIDI1_CHANNEL_VOICE: + return cvt_ump_midi1_to_legacy(*data, buf); + case UMP_MSG_TYPE_MIDI2_CHANNEL_VOICE: + return cvt_ump_midi2_to_legacy((const union snd_ump_midi2_msg *)data, + buf); + case UMP_MSG_TYPE_DATA: + return cvt_ump_sysex7_to_legacy(data, buf); + } + + return 0; +} + +/* + * MIDI 1 byte stream -> UMP conversion + */ +/* convert MIDI 1.0 SysEx to a UMP packet */ +static int cvt_legacy_sysex_to_ump(struct ump_cvt_to_ump *cvt, + unsigned char group, u32 *data, bool finish) +{ + unsigned char status; + bool start = cvt->in_sysex == 1; + int i, offset; + + if (start && finish) + status = UMP_SYSEX_STATUS_SINGLE; + else if (start) + status = UMP_SYSEX_STATUS_START; + else if (finish) + status = UMP_SYSEX_STATUS_END; + else + status = UMP_SYSEX_STATUS_CONTINUE; + *data = ump_compose(UMP_MSG_TYPE_DATA, group, status, cvt->len); + offset = 8; + for (i = 0; i < cvt->len; i++) { + *data |= cvt->buf[i] << offset; + if (!offset) { + offset = 24; + data++; + } else + offset -= 8; + } + cvt->len = 0; + if (finish) + cvt->in_sysex = 0; + else + cvt->in_sysex++; + return 8; +} + +/* convert to a UMP System message */ +static int cvt_legacy_system_to_ump(struct ump_cvt_to_ump *cvt, + unsigned char group, u32 *data) +{ + data[0] = ump_compose(UMP_MSG_TYPE_SYSTEM, group, 0, cvt->buf[0]); + if (cvt->cmd_bytes > 1) + data[0] |= cvt->buf[1] << 8; + if (cvt->cmd_bytes > 2) + data[0] |= cvt->buf[2]; + return 4; +} + +static void fill_rpn(struct ump_cvt_to_ump_bank *cc, + union snd_ump_midi2_msg *midi2) +{ + if (cc->rpn_set) { + midi2->rpn.status = UMP_MSG_STATUS_RPN; + midi2->rpn.bank = cc->cc_rpn_msb; + midi2->rpn.index = cc->cc_rpn_lsb; + cc->rpn_set = 0; + cc->cc_rpn_msb = cc->cc_rpn_lsb = 0; + } else { + midi2->rpn.status = UMP_MSG_STATUS_NRPN; + midi2->rpn.bank = cc->cc_nrpn_msb; + midi2->rpn.index = cc->cc_nrpn_lsb; + cc->nrpn_set = 0; + cc->cc_nrpn_msb = cc->cc_nrpn_lsb = 0; + } + midi2->rpn.data = upscale_14_to_32bit((cc->cc_data_msb << 7) | + cc->cc_data_lsb); + cc->cc_data_msb = cc->cc_data_lsb = 0; +} + +/* convert to a MIDI 1.0 Channel Voice message */ +static int cvt_legacy_cmd_to_ump(struct snd_ump_endpoint *ump, + struct ump_cvt_to_ump *cvt, + unsigned char group, u32 *data, + unsigned char bytes) +{ + const unsigned char *buf = cvt->buf; + struct ump_cvt_to_ump_bank *cc; + union snd_ump_midi2_msg *midi2 = (union snd_ump_midi2_msg *)data; + unsigned char status, channel; + + BUILD_BUG_ON(sizeof(union snd_ump_midi1_msg) != 4); + BUILD_BUG_ON(sizeof(union snd_ump_midi2_msg) != 8); + + /* for MIDI 1.0 UMP, it's easy, just pack it into UMP */ + if (ump->info.protocol & SNDRV_UMP_EP_INFO_PROTO_MIDI1) { + data[0] = ump_compose(UMP_MSG_TYPE_MIDI1_CHANNEL_VOICE, + group, 0, buf[0]); + data[0] |= buf[1] << 8; + if (bytes > 2) + data[0] |= buf[2]; + return 4; + } + + status = *buf >> 4; + channel = *buf & 0x0f; + cc = &cvt->bank[channel]; + + /* special handling: treat note-on with 0 velocity as note-off */ + if (status == UMP_MSG_STATUS_NOTE_ON && !buf[2]) + status = UMP_MSG_STATUS_NOTE_OFF; + + /* initialize the packet */ + data[0] = ump_compose(UMP_MSG_TYPE_MIDI2_CHANNEL_VOICE, + group, status, channel); + data[1] = 0; + + switch (status) { + case UMP_MSG_STATUS_NOTE_ON: + if (!buf[2]) + status = UMP_MSG_STATUS_NOTE_OFF; + fallthrough; + case UMP_MSG_STATUS_NOTE_OFF: + midi2->note.note = buf[1]; + midi2->note.velocity = upscale_7_to_16bit(buf[2]); + break; + case UMP_MSG_STATUS_POLY_PRESSURE: + midi2->paf.note = buf[1]; + midi2->paf.data = upscale_7_to_32bit(buf[2]); + break; + case UMP_MSG_STATUS_CC: + switch (buf[1]) { + case UMP_CC_RPN_MSB: + cc->rpn_set = 1; + cc->cc_rpn_msb = buf[2]; + return 0; // skip + case UMP_CC_RPN_LSB: + cc->rpn_set = 1; + cc->cc_rpn_lsb = buf[2]; + return 0; // skip + case UMP_CC_NRPN_MSB: + cc->nrpn_set = 1; + cc->cc_nrpn_msb = buf[2]; + return 0; // skip + case UMP_CC_NRPN_LSB: + cc->nrpn_set = 1; + cc->cc_nrpn_lsb = buf[2]; + return 0; // skip + case UMP_CC_DATA: + cc->cc_data_msb = buf[2]; + return 0; // skip + case UMP_CC_BANK_SELECT: + cc->bank_set = 1; + cc->cc_bank_msb = buf[2]; + return 0; // skip + case UMP_CC_BANK_SELECT_LSB: + cc->bank_set = 1; + cc->cc_bank_lsb = buf[2]; + return 0; // skip + case UMP_CC_DATA_LSB: + cc->cc_data_lsb = buf[2]; + if (cc->rpn_set || cc->nrpn_set) + fill_rpn(cc, midi2); + else + return 0; // skip + break; + default: + midi2->cc.index = buf[1]; + midi2->cc.data = upscale_7_to_32bit(buf[2]); + break; + } + break; + case UMP_MSG_STATUS_PROGRAM: + midi2->pg.program = buf[1]; + if (cc->bank_set) { + midi2->pg.bank_valid = 1; + midi2->pg.bank_msb = cc->cc_bank_msb; + midi2->pg.bank_lsb = cc->cc_bank_lsb; + cc->bank_set = 0; + cc->cc_bank_msb = cc->cc_bank_lsb = 0; + } + break; + case UMP_MSG_STATUS_CHANNEL_PRESSURE: + midi2->caf.data = upscale_7_to_32bit(buf[1]); + break; + case UMP_MSG_STATUS_PITCH_BEND: + midi2->pb.data = upscale_14_to_32bit(buf[1] | (buf[2] << 7)); + break; + default: + return 0; + } + + return 8; +} + +static int do_convert_to_ump(struct snd_ump_endpoint *ump, + unsigned char group, unsigned char c, u32 *data) +{ + /* bytes for 0x80-0xf0 */ + static unsigned char cmd_bytes[8] = { + 3, 3, 3, 3, 2, 2, 3, 0 + }; + /* bytes for 0xf0-0xff */ + static unsigned char system_bytes[16] = { + 0, 2, 3, 2, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1 + }; + struct ump_cvt_to_ump *cvt = &ump->out_cvts[group]; + unsigned char bytes; + + if (c == UMP_MIDI1_MSG_SYSEX_START) { + cvt->in_sysex = 1; + cvt->len = 0; + return 0; + } + if (c == UMP_MIDI1_MSG_SYSEX_END) { + if (!cvt->in_sysex) + return 0; /* skip */ + return cvt_legacy_sysex_to_ump(cvt, group, data, true); + } + + if ((c & 0xf0) == UMP_MIDI1_MSG_REALTIME) { + bytes = system_bytes[c & 0x0f]; + if (!bytes) + return 0; /* skip */ + if (bytes == 1) { + data[0] = ump_compose(UMP_MSG_TYPE_SYSTEM, group, 0, c); + return 4; + } + cvt->buf[0] = c; + cvt->len = 1; + cvt->cmd_bytes = bytes; + cvt->in_sysex = 0; /* abort SysEx */ + return 0; + } + + if (c & 0x80) { + bytes = cmd_bytes[(c >> 8) & 7]; + cvt->buf[0] = c; + cvt->len = 1; + cvt->cmd_bytes = bytes; + cvt->in_sysex = 0; /* abort SysEx */ + return 0; + } + + if (cvt->in_sysex) { + cvt->buf[cvt->len++] = c; + if (cvt->len == 6) + return cvt_legacy_sysex_to_ump(cvt, group, data, false); + return 0; + } + + if (!cvt->len) + return 0; + + cvt->buf[cvt->len++] = c; + if (cvt->len < cvt->cmd_bytes) + return 0; + cvt->len = 1; + if ((cvt->buf[0] & 0xf0) == UMP_MIDI1_MSG_REALTIME) + return cvt_legacy_system_to_ump(cvt, group, data); + return cvt_legacy_cmd_to_ump(ump, cvt, group, data, cvt->cmd_bytes); +} + +/* feed a MIDI 1.0 byte @c and convert to a UMP packet; + * the target group is @group, + * the result is stored in out_cvts[group].ump[] and out_cvts[group].ump_bytes + */ +void snd_ump_convert_to_ump(struct snd_ump_endpoint *ump, + unsigned char group, unsigned char c) +{ + struct ump_cvt_to_ump *cvt = &ump->out_cvts[group]; + + cvt->ump_bytes = do_convert_to_ump(ump, group, c, cvt->ump); +} + +/* reset the converter context, called at each open */ +void snd_ump_reset_convert_to_ump(struct snd_ump_endpoint *ump, + unsigned char group) +{ + memset(&ump->out_cvts[group], 0, sizeof(*ump->out_cvts)); +} + +/* initialize converters */ +int snd_ump_convert_init(struct snd_ump_endpoint *ump) +{ + ump->out_cvts = kcalloc(16, sizeof(*ump->out_cvts), GFP_KERNEL); + if (!ump->out_cvts) + return -ENOMEM; + return 0; +} + +/* release resources */ +void snd_ump_convert_free(struct snd_ump_endpoint *ump) +{ + kfree(ump->out_cvts); + ump->out_cvts = NULL; +} diff --git a/sound/core/ump_convert.h b/sound/core/ump_convert.h new file mode 100644 index 000000000000..bbfe96084779 --- /dev/null +++ b/sound/core/ump_convert.h @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#ifndef __UMP_CONVERT_H +#define __UMP_CONVERT_H + +#include + +/* context for converting from legacy control messages to UMP packet */ +struct ump_cvt_to_ump_bank { + bool rpn_set; + bool nrpn_set; + bool bank_set; + unsigned char cc_rpn_msb, cc_rpn_lsb; + unsigned char cc_nrpn_msb, cc_nrpn_lsb; + unsigned char cc_data_msb, cc_data_lsb; + unsigned char cc_bank_msb, cc_bank_lsb; +}; + +/* context for converting from MIDI1 byte stream to UMP packet */ +struct ump_cvt_to_ump { + /* MIDI1 intermediate buffer */ + unsigned char buf[4]; + int len; + int cmd_bytes; + + /* UMP output packet */ + u32 ump[4]; + int ump_bytes; + + /* various status */ + unsigned int in_sysex; + struct ump_cvt_to_ump_bank bank[16]; /* per channel */ +}; + +int snd_ump_convert_init(struct snd_ump_endpoint *ump); +void snd_ump_convert_free(struct snd_ump_endpoint *ump); +int snd_ump_convert_from_ump(struct snd_ump_endpoint *ump, + const u32 *data, unsigned char *dst, + unsigned char *group_ret); +void snd_ump_convert_to_ump(struct snd_ump_endpoint *ump, + unsigned char group, unsigned char c); +void snd_ump_reset_convert_to_ump(struct snd_ump_endpoint *ump, + unsigned char group); +#endif /* __UMP_CONVERT_H */