brintos

brintos / linux-shallow public Read only

0
0
Text · 37.0 KiB · 7d59a0a Raw
1308 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * Universal MIDI Packet (UMP) support4 */5 6#include <linux/list.h>7#include <linux/slab.h>8#include <linux/module.h>9#include <linux/export.h>10#include <linux/mm.h>11#include <sound/core.h>12#include <sound/rawmidi.h>13#include <sound/ump.h>14#include <sound/ump_convert.h>15 16#define ump_err(ump, fmt, args...)	dev_err((ump)->core.dev, fmt, ##args)17#define ump_warn(ump, fmt, args...)	dev_warn((ump)->core.dev, fmt, ##args)18#define ump_info(ump, fmt, args...)	dev_info((ump)->core.dev, fmt, ##args)19#define ump_dbg(ump, fmt, args...)	dev_dbg((ump)->core.dev, fmt, ##args)20 21static int snd_ump_dev_register(struct snd_rawmidi *rmidi);22static int snd_ump_dev_unregister(struct snd_rawmidi *rmidi);23static long snd_ump_ioctl(struct snd_rawmidi *rmidi, unsigned int cmd,24			  void __user *argp);25static void snd_ump_proc_read(struct snd_info_entry *entry,26			      struct snd_info_buffer *buffer);27static int snd_ump_rawmidi_open(struct snd_rawmidi_substream *substream);28static int snd_ump_rawmidi_close(struct snd_rawmidi_substream *substream);29static void snd_ump_rawmidi_trigger(struct snd_rawmidi_substream *substream,30				    int up);31static void snd_ump_rawmidi_drain(struct snd_rawmidi_substream *substream);32 33static void ump_handle_stream_msg(struct snd_ump_endpoint *ump,34				  const u32 *buf, int size);35#if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI)36static int process_legacy_output(struct snd_ump_endpoint *ump,37				 u32 *buffer, int count);38static void process_legacy_input(struct snd_ump_endpoint *ump, const u32 *src,39				 int words);40#else41static inline int process_legacy_output(struct snd_ump_endpoint *ump,42					u32 *buffer, int count)43{44	return 0;45}46static inline void process_legacy_input(struct snd_ump_endpoint *ump,47					const u32 *src, int words)48{49}50#endif51 52static const struct snd_rawmidi_global_ops snd_ump_rawmidi_ops = {53	.dev_register = snd_ump_dev_register,54	.dev_unregister = snd_ump_dev_unregister,55	.ioctl = snd_ump_ioctl,56	.proc_read = snd_ump_proc_read,57};58 59static const struct snd_rawmidi_ops snd_ump_rawmidi_input_ops = {60	.open = snd_ump_rawmidi_open,61	.close = snd_ump_rawmidi_close,62	.trigger = snd_ump_rawmidi_trigger,63};64 65static const struct snd_rawmidi_ops snd_ump_rawmidi_output_ops = {66	.open = snd_ump_rawmidi_open,67	.close = snd_ump_rawmidi_close,68	.trigger = snd_ump_rawmidi_trigger,69	.drain = snd_ump_rawmidi_drain,70};71 72static void snd_ump_endpoint_free(struct snd_rawmidi *rmidi)73{74	struct snd_ump_endpoint *ump = rawmidi_to_ump(rmidi);75	struct snd_ump_block *fb;76 77	while (!list_empty(&ump->block_list)) {78		fb = list_first_entry(&ump->block_list, struct snd_ump_block,79				      list);80		list_del(&fb->list);81		if (fb->private_free)82			fb->private_free(fb);83		kfree(fb);84	}85 86	if (ump->private_free)87		ump->private_free(ump);88 89#if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI)90	kfree(ump->out_cvts);91#endif92}93 94/**95 * snd_ump_endpoint_new - create a UMP Endpoint object96 * @card: the card instance97 * @id: the id string for rawmidi98 * @device: the device index for rawmidi99 * @output: 1 for enabling output100 * @input: 1 for enabling input101 * @ump_ret: the pointer to store the new UMP instance102 *103 * Creates a new UMP Endpoint object. A UMP Endpoint is tied with one rawmidi104 * instance with one input and/or one output rawmidi stream (either uni-105 * or bi-directional). A UMP Endpoint may contain one or multiple UMP Blocks106 * that consist of one or multiple UMP Groups.107 *108 * Use snd_rawmidi_set_ops() to set the operators to the new instance.109 * Unlike snd_rawmidi_new(), this function sets up the info_flags by itself110 * depending on the given @output and @input.111 *112 * The device has SNDRV_RAWMIDI_INFO_UMP flag set and a different device113 * file ("umpCxDx") than a standard MIDI 1.x device ("midiCxDx") is114 * created.115 *116 * Return: Zero if successful, or a negative error code on failure.117 */118int snd_ump_endpoint_new(struct snd_card *card, char *id, int device,119			 int output, int input,120			 struct snd_ump_endpoint **ump_ret)121{122	unsigned int info_flags = SNDRV_RAWMIDI_INFO_UMP;123	struct snd_ump_endpoint *ump;124	int err;125 126	if (input)127		info_flags |= SNDRV_RAWMIDI_INFO_INPUT;128	if (output)129		info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT;130	if (input && output)131		info_flags |= SNDRV_RAWMIDI_INFO_DUPLEX;132 133	ump = kzalloc(sizeof(*ump), GFP_KERNEL);134	if (!ump)135		return -ENOMEM;136	INIT_LIST_HEAD(&ump->block_list);137	mutex_init(&ump->open_mutex);138	init_waitqueue_head(&ump->stream_wait);139#if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI)140	spin_lock_init(&ump->legacy_locks[0]);141	spin_lock_init(&ump->legacy_locks[1]);142#endif143	err = snd_rawmidi_init(&ump->core, card, id, device,144			       output, input, info_flags);145	if (err < 0) {146		snd_rawmidi_free(&ump->core);147		return err;148	}149 150	ump->info.card = card->number;151	ump->info.device = device;152 153	ump->core.private_free = snd_ump_endpoint_free;154	ump->core.ops = &snd_ump_rawmidi_ops;155	if (input)156		snd_rawmidi_set_ops(&ump->core, SNDRV_RAWMIDI_STREAM_INPUT,157				    &snd_ump_rawmidi_input_ops);158	if (output)159		snd_rawmidi_set_ops(&ump->core, SNDRV_RAWMIDI_STREAM_OUTPUT,160				    &snd_ump_rawmidi_output_ops);161 162	ump_dbg(ump, "Created a UMP EP #%d (%s)\n", device, id);163	*ump_ret = ump;164	return 0;165}166EXPORT_SYMBOL_GPL(snd_ump_endpoint_new);167 168/*169 * Device register / unregister hooks;170 *  do nothing, placeholders for avoiding the default rawmidi handling171 */172 173#if IS_ENABLED(CONFIG_SND_SEQUENCER)174static void snd_ump_dev_seq_free(struct snd_seq_device *device)175{176	struct snd_ump_endpoint *ump = device->private_data;177 178	ump->seq_dev = NULL;179}180#endif181 182static int snd_ump_dev_register(struct snd_rawmidi *rmidi)183{184#if IS_ENABLED(CONFIG_SND_SEQUENCER)185	struct snd_ump_endpoint *ump = rawmidi_to_ump(rmidi);186	int err;187 188	err = snd_seq_device_new(ump->core.card, ump->core.device,189				 SNDRV_SEQ_DEV_ID_UMP, 0, &ump->seq_dev);190	if (err < 0)191		return err;192	ump->seq_dev->private_data = ump;193	ump->seq_dev->private_free = snd_ump_dev_seq_free;194	snd_device_register(ump->core.card, ump->seq_dev);195#endif196	return 0;197}198 199static int snd_ump_dev_unregister(struct snd_rawmidi *rmidi)200{201	return 0;202}203 204static struct snd_ump_block *205snd_ump_get_block(struct snd_ump_endpoint *ump, unsigned char id)206{207	struct snd_ump_block *fb;208 209	list_for_each_entry(fb, &ump->block_list, list) {210		if (fb->info.block_id == id)211			return fb;212	}213	return NULL;214}215 216/*217 * rawmidi ops for UMP endpoint218 */219static int snd_ump_rawmidi_open(struct snd_rawmidi_substream *substream)220{221	struct snd_ump_endpoint *ump = rawmidi_to_ump(substream->rmidi);222	int dir = substream->stream;223	int err;224 225	if (ump->substreams[dir])226		return -EBUSY;227	err = ump->ops->open(ump, dir);228	if (err < 0)229		return err;230	ump->substreams[dir] = substream;231	return 0;232}233 234static int snd_ump_rawmidi_close(struct snd_rawmidi_substream *substream)235{236	struct snd_ump_endpoint *ump = rawmidi_to_ump(substream->rmidi);237	int dir = substream->stream;238 239	ump->substreams[dir] = NULL;240	ump->ops->close(ump, dir);241	return 0;242}243 244static void snd_ump_rawmidi_trigger(struct snd_rawmidi_substream *substream,245				    int up)246{247	struct snd_ump_endpoint *ump = rawmidi_to_ump(substream->rmidi);248	int dir = substream->stream;249 250	ump->ops->trigger(ump, dir, up);251}252 253static void snd_ump_rawmidi_drain(struct snd_rawmidi_substream *substream)254{255	struct snd_ump_endpoint *ump = rawmidi_to_ump(substream->rmidi);256 257	if (ump->ops->drain)258		ump->ops->drain(ump, SNDRV_RAWMIDI_STREAM_OUTPUT);259}260 261/* number of 32bit words per message type */262static unsigned char ump_packet_words[0x10] = {263	1, 1, 1, 2, 2, 4, 1, 1, 2, 2, 2, 3, 3, 4, 4, 4264};265 266/**267 * snd_ump_receive_ump_val - parse the UMP packet data268 * @ump: UMP endpoint269 * @val: UMP packet data270 *271 * The data is copied onto ump->input_buf[].272 * When a full packet is completed, returns the number of words (from 1 to 4).273 * OTOH, if the packet is incomplete, returns 0.274 */275int snd_ump_receive_ump_val(struct snd_ump_endpoint *ump, u32 val)276{277	int words;278 279	if (!ump->input_pending)280		ump->input_pending = ump_packet_words[ump_message_type(val)];281 282	ump->input_buf[ump->input_buf_head++] = val;283	ump->input_pending--;284	if (!ump->input_pending) {285		words = ump->input_buf_head;286		ump->input_buf_head = 0;287		return words;288	}289	return 0;290}291EXPORT_SYMBOL_GPL(snd_ump_receive_ump_val);292 293/**294 * snd_ump_receive - transfer UMP packets from the device295 * @ump: the UMP endpoint296 * @buffer: the buffer pointer to transfer297 * @count: byte size to transfer298 *299 * Called from the driver to submit the received UMP packets from the device300 * to user-space.  It's essentially a wrapper of rawmidi_receive().301 * The data to receive is in CPU-native endianness.302 */303int snd_ump_receive(struct snd_ump_endpoint *ump, const u32 *buffer, int count)304{305	struct snd_rawmidi_substream *substream;306	const u32 *p = buffer;307	int n, words = count >> 2;308 309	while (words--) {310		n = snd_ump_receive_ump_val(ump, *p++);311		if (!n)312			continue;313		ump_handle_stream_msg(ump, ump->input_buf, n);314#if IS_ENABLED(CONFIG_SND_SEQUENCER)315		if (ump->seq_ops)316			ump->seq_ops->input_receive(ump, ump->input_buf, n);317#endif318		process_legacy_input(ump, ump->input_buf, n);319	}320 321	substream = ump->substreams[SNDRV_RAWMIDI_STREAM_INPUT];322	if (!substream)323		return 0;324	return snd_rawmidi_receive(substream, (const char *)buffer, count);325}326EXPORT_SYMBOL_GPL(snd_ump_receive);327 328/**329 * snd_ump_transmit - transmit UMP packets330 * @ump: the UMP endpoint331 * @buffer: the buffer pointer to transfer332 * @count: byte size to transfer333 *334 * Called from the driver to obtain the UMP packets from user-space to the335 * device.  It's essentially a wrapper of rawmidi_transmit().336 * The data to transmit is in CPU-native endianness.337 */338int snd_ump_transmit(struct snd_ump_endpoint *ump, u32 *buffer, int count)339{340	struct snd_rawmidi_substream *substream =341		ump->substreams[SNDRV_RAWMIDI_STREAM_OUTPUT];342	int err;343 344	if (!substream)345		return -ENODEV;346	err = snd_rawmidi_transmit(substream, (char *)buffer, count);347	/* received either data or an error? */348	if (err)349		return err;350	return process_legacy_output(ump, buffer, count);351}352EXPORT_SYMBOL_GPL(snd_ump_transmit);353 354/**355 * snd_ump_block_new - Create a UMP block356 * @ump: UMP object357 * @blk: block ID number to create358 * @direction: direction (in/out/bidirection)359 * @first_group: the first group ID (0-based)360 * @num_groups: the number of groups in this block361 * @blk_ret: the pointer to store the resultant block object362 */363int snd_ump_block_new(struct snd_ump_endpoint *ump, unsigned int blk,364		      unsigned int direction, unsigned int first_group,365		      unsigned int num_groups, struct snd_ump_block **blk_ret)366{367	struct snd_ump_block *fb, *p;368 369	if (blk < 0 || blk >= SNDRV_UMP_MAX_BLOCKS)370		return -EINVAL;371 372	if (snd_ump_get_block(ump, blk))373		return -EBUSY;374 375	fb = kzalloc(sizeof(*fb), GFP_KERNEL);376	if (!fb)377		return -ENOMEM;378 379	fb->ump = ump;380	fb->info.card = ump->info.card;381	fb->info.device = ump->info.device;382	fb->info.block_id = blk;383	if (blk >= ump->info.num_blocks)384		ump->info.num_blocks = blk + 1;385	fb->info.direction = direction;386	fb->info.active = 1;387	fb->info.first_group = first_group;388	fb->info.num_groups = num_groups;389	/* fill the default name, may be overwritten to a better name */390	snprintf(fb->info.name, sizeof(fb->info.name), "Group %d-%d",391		 first_group + 1, first_group + num_groups);392 393	/* put the entry in the ordered list */394	list_for_each_entry(p, &ump->block_list, list) {395		if (p->info.block_id > blk) {396			list_add_tail(&fb->list, &p->list);397			goto added;398		}399	}400	list_add_tail(&fb->list, &ump->block_list);401 402 added:403	ump_dbg(ump, "Created a UMP Block #%d (%s)\n", blk, fb->info.name);404	*blk_ret = fb;405	return 0;406}407EXPORT_SYMBOL_GPL(snd_ump_block_new);408 409static int snd_ump_ioctl_block(struct snd_ump_endpoint *ump,410			       struct snd_ump_block_info __user *argp)411{412	struct snd_ump_block *fb;413	unsigned char id;414 415	if (get_user(id, &argp->block_id))416		return -EFAULT;417	fb = snd_ump_get_block(ump, id);418	if (!fb)419		return -ENOENT;420	if (copy_to_user(argp, &fb->info, sizeof(fb->info)))421		return -EFAULT;422	return 0;423}424 425/*426 * Handle UMP-specific ioctls; called from snd_rawmidi_ioctl()427 */428static long snd_ump_ioctl(struct snd_rawmidi *rmidi, unsigned int cmd,429			  void __user *argp)430{431	struct snd_ump_endpoint *ump = rawmidi_to_ump(rmidi);432 433	switch (cmd) {434	case SNDRV_UMP_IOCTL_ENDPOINT_INFO:435		if (copy_to_user(argp, &ump->info, sizeof(ump->info)))436			return -EFAULT;437		return 0;438	case SNDRV_UMP_IOCTL_BLOCK_INFO:439		return snd_ump_ioctl_block(ump, argp);440	default:441		ump_dbg(ump, "rawmidi: unknown command = 0x%x\n", cmd);442		return -ENOTTY;443	}444}445 446static const char *ump_direction_string(int dir)447{448	switch (dir) {449	case SNDRV_UMP_DIR_INPUT:450		return "input";451	case SNDRV_UMP_DIR_OUTPUT:452		return "output";453	case SNDRV_UMP_DIR_BIDIRECTION:454		return "bidirection";455	default:456		return "unknown";457	}458}459 460static const char *ump_ui_hint_string(int dir)461{462	switch (dir) {463	case  SNDRV_UMP_BLOCK_UI_HINT_RECEIVER:464		return "receiver";465	case SNDRV_UMP_BLOCK_UI_HINT_SENDER:466		return "sender";467	case SNDRV_UMP_BLOCK_UI_HINT_BOTH:468		return "both";469	default:470		return "unknown";471	}472}473 474/* Additional proc file output */475static void snd_ump_proc_read(struct snd_info_entry *entry,476			      struct snd_info_buffer *buffer)477{478	struct snd_rawmidi *rmidi = entry->private_data;479	struct snd_ump_endpoint *ump = rawmidi_to_ump(rmidi);480	struct snd_ump_block *fb;481 482	snd_iprintf(buffer, "EP Name: %s\n", ump->info.name);483	snd_iprintf(buffer, "EP Product ID: %s\n", ump->info.product_id);484	snd_iprintf(buffer, "UMP Version: 0x%04x\n", ump->info.version);485	snd_iprintf(buffer, "Protocol Caps: 0x%08x\n", ump->info.protocol_caps);486	snd_iprintf(buffer, "Protocol: 0x%08x\n", ump->info.protocol);487	if (ump->info.version) {488		snd_iprintf(buffer, "Manufacturer ID: 0x%08x\n",489			    ump->info.manufacturer_id);490		snd_iprintf(buffer, "Family ID: 0x%04x\n", ump->info.family_id);491		snd_iprintf(buffer, "Model ID: 0x%04x\n", ump->info.model_id);492		snd_iprintf(buffer, "SW Revision: 0x%4phN\n", ump->info.sw_revision);493	}494	snd_iprintf(buffer, "Static Blocks: %s\n",495		    (ump->info.flags & SNDRV_UMP_EP_INFO_STATIC_BLOCKS) ? "Yes" : "No");496	snd_iprintf(buffer, "Num Blocks: %d\n\n", ump->info.num_blocks);497 498	list_for_each_entry(fb, &ump->block_list, list) {499		snd_iprintf(buffer, "Block %d (%s)\n", fb->info.block_id,500			    fb->info.name);501		snd_iprintf(buffer, "  Direction: %s\n",502			    ump_direction_string(fb->info.direction));503		snd_iprintf(buffer, "  Active: %s\n",504			    fb->info.active ? "Yes" : "No");505		snd_iprintf(buffer, "  Groups: %d-%d\n",506			    fb->info.first_group + 1,507			    fb->info.first_group + fb->info.num_groups);508		snd_iprintf(buffer, "  Is MIDI1: %s%s\n",509			    (fb->info.flags & SNDRV_UMP_BLOCK_IS_MIDI1) ? "Yes" : "No",510			    (fb->info.flags & SNDRV_UMP_BLOCK_IS_LOWSPEED) ? " (Low Speed)" : "");511		if (ump->info.version) {512			snd_iprintf(buffer, "  MIDI-CI Version: %d\n",513				    fb->info.midi_ci_version);514			snd_iprintf(buffer, "  Sysex8 Streams: %d\n",515				    fb->info.sysex8_streams);516			snd_iprintf(buffer, "  UI Hint: %s\n",517				    ump_ui_hint_string(fb->info.ui_hint));518		}519		snd_iprintf(buffer, "\n");520	}521}522 523/* update dir_bits and active flag for all groups in the client */524void snd_ump_update_group_attrs(struct snd_ump_endpoint *ump)525{526	struct snd_ump_block *fb;527	struct snd_ump_group *group;528	int i;529 530	for (i = 0; i < SNDRV_UMP_MAX_GROUPS; i++) {531		group = &ump->groups[i];532		*group->name = 0;533		group->dir_bits = 0;534		group->active = 0;535		group->group = i;536		group->valid = false;537		group->is_midi1 = false;538	}539 540	list_for_each_entry(fb, &ump->block_list, list) {541		if (fb->info.first_group + fb->info.num_groups > SNDRV_UMP_MAX_GROUPS)542			break;543		group = &ump->groups[fb->info.first_group];544		for (i = 0; i < fb->info.num_groups; i++, group++) {545			group->valid = true;546			if (fb->info.active)547				group->active = 1;548			if (fb->info.flags & SNDRV_UMP_BLOCK_IS_MIDI1)549				group->is_midi1 = true;550			switch (fb->info.direction) {551			case SNDRV_UMP_DIR_INPUT:552				group->dir_bits |= (1 << SNDRV_RAWMIDI_STREAM_INPUT);553				break;554			case SNDRV_UMP_DIR_OUTPUT:555				group->dir_bits |= (1 << SNDRV_RAWMIDI_STREAM_OUTPUT);556				break;557			case SNDRV_UMP_DIR_BIDIRECTION:558				group->dir_bits |= (1 << SNDRV_RAWMIDI_STREAM_INPUT) |559					(1 << SNDRV_RAWMIDI_STREAM_OUTPUT);560				break;561			}562			if (!*fb->info.name)563				continue;564			if (!*group->name) {565				/* store the first matching name */566				strscpy(group->name, fb->info.name,567					sizeof(group->name));568			} else {569				/* when overlapping, concat names */570				strlcat(group->name, ", ", sizeof(group->name));571				strlcat(group->name, fb->info.name,572					sizeof(group->name));573			}574		}575	}576}577EXPORT_SYMBOL_GPL(snd_ump_update_group_attrs);578 579/*580 * UMP endpoint and function block handling581 */582 583/* open / close UMP streams for the internal stream msg communication */584static int ump_request_open(struct snd_ump_endpoint *ump)585{586	return snd_rawmidi_kernel_open(&ump->core, 0,587				       SNDRV_RAWMIDI_LFLG_OUTPUT,588				       &ump->stream_rfile);589}590 591static void ump_request_close(struct snd_ump_endpoint *ump)592{593	snd_rawmidi_kernel_release(&ump->stream_rfile);594}595 596/* request a command and wait for the given response;597 * @req1 and @req2 are u32 commands598 * @reply is the expected UMP stream status599 */600static int ump_req_msg(struct snd_ump_endpoint *ump, u32 req1, u32 req2,601		       u32 reply)602{603	u32 buf[4];604 605	ump_dbg(ump, "%s: request %08x %08x, wait-for %08x\n",606		__func__, req1, req2, reply);607	memset(buf, 0, sizeof(buf));608	buf[0] = req1;609	buf[1] = req2;610	ump->stream_finished = 0;611	ump->stream_wait_for = reply;612	snd_rawmidi_kernel_write(ump->stream_rfile.output,613				 (unsigned char *)&buf, 16);614	wait_event_timeout(ump->stream_wait, ump->stream_finished,615			   msecs_to_jiffies(500));616	if (!READ_ONCE(ump->stream_finished)) {617		ump_dbg(ump, "%s: request timed out\n", __func__);618		return -ETIMEDOUT;619	}620	ump->stream_finished = 0;621	ump_dbg(ump, "%s: reply: %08x %08x %08x %08x\n",622		__func__, buf[0], buf[1], buf[2], buf[3]);623	return 0;624}625 626/* append the received letters via UMP packet to the given string buffer;627 * return 1 if the full string is received or 0 to continue628 */629static int ump_append_string(struct snd_ump_endpoint *ump, char *dest,630			     int maxsize, const u32 *buf, int offset)631{632	unsigned char format;633	int c;634 635	format = ump_stream_message_format(buf[0]);636	if (format == UMP_STREAM_MSG_FORMAT_SINGLE ||637	    format == UMP_STREAM_MSG_FORMAT_START) {638		c = 0;639	} else {640		c = strlen(dest);641		if (c >= maxsize - 1)642			return 1;643	}644 645	for (; offset < 16; offset++) {646		dest[c] = buf[offset / 4] >> (3 - (offset % 4)) * 8;647		if (!dest[c])648			break;649		if (++c >= maxsize - 1)650			break;651	}652	dest[c] = 0;653	return (format == UMP_STREAM_MSG_FORMAT_SINGLE ||654		format == UMP_STREAM_MSG_FORMAT_END);655}656 657/* Choose the default protocol */658static void choose_default_protocol(struct snd_ump_endpoint *ump)659{660	if (ump->info.protocol & SNDRV_UMP_EP_INFO_PROTO_MIDI_MASK)661		return;662	if (ump->info.protocol_caps & SNDRV_UMP_EP_INFO_PROTO_MIDI2)663		ump->info.protocol |= SNDRV_UMP_EP_INFO_PROTO_MIDI2;664	else665		ump->info.protocol |= SNDRV_UMP_EP_INFO_PROTO_MIDI1;666}667 668/* handle EP info stream message; update the UMP attributes */669static int ump_handle_ep_info_msg(struct snd_ump_endpoint *ump,670				  const union snd_ump_stream_msg *buf)671{672	ump->info.version = (buf->ep_info.ump_version_major << 8) |673		buf->ep_info.ump_version_minor;674	ump->info.num_blocks = buf->ep_info.num_function_blocks;675	if (ump->info.num_blocks > SNDRV_UMP_MAX_BLOCKS) {676		ump_info(ump, "Invalid function blocks %d, fallback to 1\n",677			 ump->info.num_blocks);678		ump->info.num_blocks = 1;679	}680 681	if (buf->ep_info.static_function_block)682		ump->info.flags |= SNDRV_UMP_EP_INFO_STATIC_BLOCKS;683 684	ump->info.protocol_caps = (buf->ep_info.protocol << 8) |685		buf->ep_info.jrts;686 687	ump_dbg(ump, "EP info: version=%x, num_blocks=%x, proto_caps=%x\n",688		ump->info.version, ump->info.num_blocks, ump->info.protocol_caps);689 690	ump->info.protocol &= ump->info.protocol_caps;691	choose_default_protocol(ump);692 693	return 1; /* finished */694}695 696/* handle EP device info stream message; update the UMP attributes */697static int ump_handle_device_info_msg(struct snd_ump_endpoint *ump,698				      const union snd_ump_stream_msg *buf)699{700	ump->info.manufacturer_id = buf->device_info.manufacture_id & 0x7f7f7f;701	ump->info.family_id = (buf->device_info.family_msb << 8) |702		buf->device_info.family_lsb;703	ump->info.model_id = (buf->device_info.model_msb << 8) |704		buf->device_info.model_lsb;705	ump->info.sw_revision[0] = (buf->device_info.sw_revision >> 24) & 0x7f;706	ump->info.sw_revision[1] = (buf->device_info.sw_revision >> 16) & 0x7f;707	ump->info.sw_revision[2] = (buf->device_info.sw_revision >> 8) & 0x7f;708	ump->info.sw_revision[3] = buf->device_info.sw_revision & 0x7f;709	ump_dbg(ump, "EP devinfo: manid=%08x, family=%04x, model=%04x, sw=%4phN\n",710		ump->info.manufacturer_id,711		ump->info.family_id,712		ump->info.model_id,713		ump->info.sw_revision);714	return 1; /* finished */715}716 717/* handle EP name stream message; update the UMP name string */718static int ump_handle_ep_name_msg(struct snd_ump_endpoint *ump,719				  const union snd_ump_stream_msg *buf)720{721	return ump_append_string(ump, ump->info.name, sizeof(ump->info.name),722				 buf->raw, 2);723}724 725/* handle EP product id stream message; update the UMP product_id string */726static int ump_handle_product_id_msg(struct snd_ump_endpoint *ump,727				     const union snd_ump_stream_msg *buf)728{729	return ump_append_string(ump, ump->info.product_id,730				 sizeof(ump->info.product_id),731				 buf->raw, 2);732}733 734/* notify the protocol change to sequencer */735static void seq_notify_protocol(struct snd_ump_endpoint *ump)736{737#if IS_ENABLED(CONFIG_SND_SEQUENCER)738	if (ump->seq_ops && ump->seq_ops->switch_protocol)739		ump->seq_ops->switch_protocol(ump);740#endif /* CONFIG_SND_SEQUENCER */741}742 743/**744 * snd_ump_switch_protocol - switch MIDI protocol745 * @ump: UMP endpoint746 * @protocol: protocol to switch to747 *748 * Returns 1 if the protocol is actually switched, 0 if unchanged749 */750int snd_ump_switch_protocol(struct snd_ump_endpoint *ump, unsigned int protocol)751{752	unsigned int type;753 754	protocol &= ump->info.protocol_caps;755	if (protocol == ump->info.protocol)756		return 0;757 758	type = protocol & SNDRV_UMP_EP_INFO_PROTO_MIDI_MASK;759	if (type != SNDRV_UMP_EP_INFO_PROTO_MIDI1 &&760	    type != SNDRV_UMP_EP_INFO_PROTO_MIDI2)761		return 0;762 763	ump->info.protocol = protocol;764	ump_dbg(ump, "New protocol = %x (caps = %x)\n",765		protocol, ump->info.protocol_caps);766	seq_notify_protocol(ump);767	return 1;768}769EXPORT_SYMBOL_GPL(snd_ump_switch_protocol);770 771/* handle EP stream config message; update the UMP protocol */772static int ump_handle_stream_cfg_msg(struct snd_ump_endpoint *ump,773				     const union snd_ump_stream_msg *buf)774{775	unsigned int protocol =776		(buf->stream_cfg.protocol << 8) | buf->stream_cfg.jrts;777 778	snd_ump_switch_protocol(ump, protocol);779	return 1; /* finished */780}781 782/* Extract Function Block info from UMP packet */783static void fill_fb_info(struct snd_ump_endpoint *ump,784			 struct snd_ump_block_info *info,785			 const union snd_ump_stream_msg *buf)786{787	info->direction = buf->fb_info.direction;788	info->ui_hint = buf->fb_info.ui_hint;789	info->first_group = buf->fb_info.first_group;790	info->num_groups = buf->fb_info.num_groups;791	info->flags = buf->fb_info.midi_10;792	info->active = buf->fb_info.active;793	info->midi_ci_version = buf->fb_info.midi_ci_version;794	info->sysex8_streams = buf->fb_info.sysex8_streams;795 796	ump_dbg(ump, "FB %d: dir=%d, active=%d, first_gp=%d, num_gp=%d, midici=%d, sysex8=%d, flags=0x%x\n",797		info->block_id, info->direction, info->active,798		info->first_group, info->num_groups, info->midi_ci_version,799		info->sysex8_streams, info->flags);800 801	if ((info->flags & SNDRV_UMP_BLOCK_IS_MIDI1) && info->num_groups != 1) {802		info->num_groups = 1;803		ump_dbg(ump, "FB %d: corrected groups to 1 for MIDI1\n",804			info->block_id);805	}806}807 808/* check whether the FB info gets updated by the current message */809static bool is_fb_info_updated(struct snd_ump_endpoint *ump,810			       struct snd_ump_block *fb,811			       const union snd_ump_stream_msg *buf)812{813	char tmpbuf[offsetof(struct snd_ump_block_info, name)];814 815	if (ump->info.flags & SNDRV_UMP_EP_INFO_STATIC_BLOCKS) {816		ump_info(ump, "Skipping static FB info update (blk#%d)\n",817			 fb->info.block_id);818		return 0;819	}820 821	memcpy(tmpbuf, &fb->info, sizeof(tmpbuf));822	fill_fb_info(ump, (struct snd_ump_block_info *)tmpbuf, buf);823	return memcmp(&fb->info, tmpbuf, sizeof(tmpbuf)) != 0;824}825 826/* notify the FB info/name change to sequencer */827static void seq_notify_fb_change(struct snd_ump_endpoint *ump,828				 struct snd_ump_block *fb)829{830#if IS_ENABLED(CONFIG_SND_SEQUENCER)831	if (ump->seq_ops && ump->seq_ops->notify_fb_change)832		ump->seq_ops->notify_fb_change(ump, fb);833#endif834}835 836/* handle FB info message; update FB info if the block is present */837static int ump_handle_fb_info_msg(struct snd_ump_endpoint *ump,838				  const union snd_ump_stream_msg *buf)839{840	unsigned char blk;841	struct snd_ump_block *fb;842 843	blk = buf->fb_info.function_block_id;844	fb = snd_ump_get_block(ump, blk);845 846	/* complain only if updated after parsing */847	if (!fb && ump->parsed) {848		ump_info(ump, "Function Block Info Update for non-existing block %d\n",849			 blk);850		return -ENODEV;851	}852 853	/* When updated after the initial parse, check the FB info update */854	if (ump->parsed && !is_fb_info_updated(ump, fb, buf))855		return 1; /* no content change */856 857	if (fb) {858		fill_fb_info(ump, &fb->info, buf);859		if (ump->parsed) {860			snd_ump_update_group_attrs(ump);861			seq_notify_fb_change(ump, fb);862		}863	}864 865	return 1; /* finished */866}867 868/* handle FB name message; update the FB name string */869static int ump_handle_fb_name_msg(struct snd_ump_endpoint *ump,870				  const union snd_ump_stream_msg *buf)871{872	unsigned char blk;873	struct snd_ump_block *fb;874	int ret;875 876	blk = buf->fb_name.function_block_id;877	fb = snd_ump_get_block(ump, blk);878	if (!fb)879		return -ENODEV;880 881	if (ump->parsed &&882	    (ump->info.flags & SNDRV_UMP_EP_INFO_STATIC_BLOCKS)) {883		ump_dbg(ump, "Skipping static FB name update (blk#%d)\n",884			fb->info.block_id);885		return 0;886	}887 888	ret = ump_append_string(ump, fb->info.name, sizeof(fb->info.name),889				buf->raw, 3);890	/* notify the FB name update to sequencer, too */891	if (ret > 0 && ump->parsed) {892		snd_ump_update_group_attrs(ump);893		seq_notify_fb_change(ump, fb);894	}895	return ret;896}897 898static int create_block_from_fb_info(struct snd_ump_endpoint *ump, int blk)899{900	struct snd_ump_block *fb;901	unsigned char direction, first_group, num_groups;902	const union snd_ump_stream_msg *buf =903		(const union snd_ump_stream_msg *)ump->input_buf;904	u32 msg;905	int err;906 907	/* query the FB info once */908	msg = ump_stream_compose(UMP_STREAM_MSG_STATUS_FB_DISCOVERY, 0) |909		(blk << 8) | UMP_STREAM_MSG_REQUEST_FB_INFO;910	err = ump_req_msg(ump, msg, 0, UMP_STREAM_MSG_STATUS_FB_INFO);911	if (err < 0) {912		ump_dbg(ump, "Unable to get FB info for block %d\n", blk);913		return err;914	}915 916	/* the last input must be the FB info */917	if (buf->fb_info.status != UMP_STREAM_MSG_STATUS_FB_INFO) {918		ump_dbg(ump, "Inconsistent input: 0x%x\n", *buf->raw);919		return -EINVAL;920	}921 922	direction = buf->fb_info.direction;923	first_group = buf->fb_info.first_group;924	num_groups = buf->fb_info.num_groups;925 926	err = snd_ump_block_new(ump, blk, direction, first_group, num_groups,927				&fb);928	if (err < 0)929		return err;930 931	fill_fb_info(ump, &fb->info, buf);932 933	msg = ump_stream_compose(UMP_STREAM_MSG_STATUS_FB_DISCOVERY, 0) |934		(blk << 8) | UMP_STREAM_MSG_REQUEST_FB_NAME;935	err = ump_req_msg(ump, msg, 0, UMP_STREAM_MSG_STATUS_FB_NAME);936	if (err)937		ump_dbg(ump, "Unable to get UMP FB name string #%d\n", blk);938 939	return 0;940}941 942/* handle stream messages, called from snd_ump_receive() */943static void ump_handle_stream_msg(struct snd_ump_endpoint *ump,944				  const u32 *buf, int size)945{946	const union snd_ump_stream_msg *msg;947	unsigned int status;948	int ret;949 950	/* UMP stream message suppressed (for gadget UMP)? */951	if (ump->no_process_stream)952		return;953 954	BUILD_BUG_ON(sizeof(*msg) != 16);955	ump_dbg(ump, "Stream msg: %08x %08x %08x %08x\n",956		buf[0], buf[1], buf[2], buf[3]);957 958	if (size != 4 || ump_message_type(*buf) != UMP_MSG_TYPE_STREAM)959		return;960 961	msg = (const union snd_ump_stream_msg *)buf;962	status = ump_stream_message_status(*buf);963	switch (status) {964	case UMP_STREAM_MSG_STATUS_EP_INFO:965		ret = ump_handle_ep_info_msg(ump, msg);966		break;967	case UMP_STREAM_MSG_STATUS_DEVICE_INFO:968		ret = ump_handle_device_info_msg(ump, msg);969		break;970	case UMP_STREAM_MSG_STATUS_EP_NAME:971		ret = ump_handle_ep_name_msg(ump, msg);972		break;973	case UMP_STREAM_MSG_STATUS_PRODUCT_ID:974		ret = ump_handle_product_id_msg(ump, msg);975		break;976	case UMP_STREAM_MSG_STATUS_STREAM_CFG:977		ret = ump_handle_stream_cfg_msg(ump, msg);978		break;979	case UMP_STREAM_MSG_STATUS_FB_INFO:980		ret = ump_handle_fb_info_msg(ump, msg);981		break;982	case UMP_STREAM_MSG_STATUS_FB_NAME:983		ret = ump_handle_fb_name_msg(ump, msg);984		break;985	default:986		return;987	}988 989	/* when the message has been processed fully, wake up */990	if (ret > 0 && ump->stream_wait_for == status) {991		WRITE_ONCE(ump->stream_finished, 1);992		wake_up(&ump->stream_wait);993	}994}995 996/**997 * snd_ump_parse_endpoint - parse endpoint and create function blocks998 * @ump: UMP object999 *1000 * Returns 0 for successful parse, -ENODEV if device doesn't respond1001 * (or the query is unsupported), or other error code for serious errors.1002 */1003int snd_ump_parse_endpoint(struct snd_ump_endpoint *ump)1004{1005	int blk, err;1006	u32 msg;1007 1008	if (!(ump->core.info_flags & SNDRV_RAWMIDI_INFO_DUPLEX))1009		return -ENODEV;1010 1011	err = ump_request_open(ump);1012	if (err < 0) {1013		ump_dbg(ump, "Unable to open rawmidi device: %d\n", err);1014		return err;1015	}1016 1017	/* Check Endpoint Information */1018	msg = ump_stream_compose(UMP_STREAM_MSG_STATUS_EP_DISCOVERY, 0) |1019		0x0101; /* UMP version 1.1 */1020	err = ump_req_msg(ump, msg, UMP_STREAM_MSG_REQUEST_EP_INFO,1021			  UMP_STREAM_MSG_STATUS_EP_INFO);1022	if (err < 0) {1023		ump_dbg(ump, "Unable to get UMP EP info\n");1024		goto error;1025	}1026 1027	/* Request Endpoint Device Info */1028	err = ump_req_msg(ump, msg, UMP_STREAM_MSG_REQUEST_DEVICE_INFO,1029			  UMP_STREAM_MSG_STATUS_DEVICE_INFO);1030	if (err < 0)1031		ump_dbg(ump, "Unable to get UMP EP device info\n");1032 1033	/* Request Endpoint Name */1034	err = ump_req_msg(ump, msg, UMP_STREAM_MSG_REQUEST_EP_NAME,1035			  UMP_STREAM_MSG_STATUS_EP_NAME);1036	if (err < 0)1037		ump_dbg(ump, "Unable to get UMP EP name string\n");1038 1039	/* Request Endpoint Product ID */1040	err = ump_req_msg(ump, msg, UMP_STREAM_MSG_REQUEST_PRODUCT_ID,1041			  UMP_STREAM_MSG_STATUS_PRODUCT_ID);1042	if (err < 0)1043		ump_dbg(ump, "Unable to get UMP EP product ID string\n");1044 1045	/* Get the current stream configuration */1046	err = ump_req_msg(ump, msg, UMP_STREAM_MSG_REQUEST_STREAM_CFG,1047			  UMP_STREAM_MSG_STATUS_STREAM_CFG);1048	if (err < 0)1049		ump_dbg(ump, "Unable to get UMP EP stream config\n");1050 1051	/* If no protocol is set by some reason, assume the valid one */1052	choose_default_protocol(ump);1053 1054	/* Query and create blocks from Function Blocks */1055	for (blk = 0; blk < ump->info.num_blocks; blk++) {1056		err = create_block_from_fb_info(ump, blk);1057		if (err < 0)1058			continue;1059	}1060 1061	/* initialize group attributions */1062	snd_ump_update_group_attrs(ump);1063 1064 error:1065	ump->parsed = true;1066	ump_request_close(ump);1067	if (err == -ETIMEDOUT)1068		err = -ENODEV;1069	return err;1070}1071EXPORT_SYMBOL_GPL(snd_ump_parse_endpoint);1072 1073#if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI)1074/*1075 * Legacy rawmidi support1076 */1077static int snd_ump_legacy_open(struct snd_rawmidi_substream *substream)1078{1079	struct snd_ump_endpoint *ump = substream->rmidi->private_data;1080	int dir = substream->stream;1081	int group = ump->legacy_mapping[substream->number];1082	int err;1083 1084	guard(mutex)(&ump->open_mutex);1085	if (ump->legacy_substreams[dir][group])1086		return -EBUSY;1087	if (dir == SNDRV_RAWMIDI_STREAM_OUTPUT) {1088		if (!ump->legacy_out_opens) {1089			err = snd_rawmidi_kernel_open(&ump->core, 0,1090						      SNDRV_RAWMIDI_LFLG_OUTPUT |1091						      SNDRV_RAWMIDI_LFLG_APPEND,1092						      &ump->legacy_out_rfile);1093			if (err < 0)1094				return err;1095		}1096		ump->legacy_out_opens++;1097		snd_ump_convert_reset(&ump->out_cvts[group]);1098	}1099	guard(spinlock_irq)(&ump->legacy_locks[dir]);1100	ump->legacy_substreams[dir][group] = substream;1101	return 0;1102}1103 1104static int snd_ump_legacy_close(struct snd_rawmidi_substream *substream)1105{1106	struct snd_ump_endpoint *ump = substream->rmidi->private_data;1107	int dir = substream->stream;1108	int group = ump->legacy_mapping[substream->number];1109 1110	guard(mutex)(&ump->open_mutex);1111	scoped_guard(spinlock_irq, &ump->legacy_locks[dir])1112		ump->legacy_substreams[dir][group] = NULL;1113	if (dir == SNDRV_RAWMIDI_STREAM_OUTPUT) {1114		if (!--ump->legacy_out_opens)1115			snd_rawmidi_kernel_release(&ump->legacy_out_rfile);1116	}1117	return 0;1118}1119 1120static void snd_ump_legacy_trigger(struct snd_rawmidi_substream *substream,1121				   int up)1122{1123	struct snd_ump_endpoint *ump = substream->rmidi->private_data;1124	int dir = substream->stream;1125 1126	ump->ops->trigger(ump, dir, up);1127}1128 1129static void snd_ump_legacy_drain(struct snd_rawmidi_substream *substream)1130{1131	struct snd_ump_endpoint *ump = substream->rmidi->private_data;1132 1133	if (ump->ops->drain)1134		ump->ops->drain(ump, SNDRV_RAWMIDI_STREAM_OUTPUT);1135}1136 1137static int snd_ump_legacy_dev_register(struct snd_rawmidi *rmidi)1138{1139	/* dummy, just for avoiding create superfluous seq clients */1140	return 0;1141}1142 1143static const struct snd_rawmidi_ops snd_ump_legacy_input_ops = {1144	.open = snd_ump_legacy_open,1145	.close = snd_ump_legacy_close,1146	.trigger = snd_ump_legacy_trigger,1147};1148 1149static const struct snd_rawmidi_ops snd_ump_legacy_output_ops = {1150	.open = snd_ump_legacy_open,1151	.close = snd_ump_legacy_close,1152	.trigger = snd_ump_legacy_trigger,1153	.drain = snd_ump_legacy_drain,1154};1155 1156static const struct snd_rawmidi_global_ops snd_ump_legacy_ops = {1157	.dev_register = snd_ump_legacy_dev_register,1158};1159 1160static int process_legacy_output(struct snd_ump_endpoint *ump,1161				 u32 *buffer, int count)1162{1163	struct snd_rawmidi_substream *substream;1164	struct ump_cvt_to_ump *ctx;1165	const int dir = SNDRV_RAWMIDI_STREAM_OUTPUT;1166	unsigned int protocol;1167	unsigned char c;1168	int group, size = 0;1169 1170	if (!ump->out_cvts || !ump->legacy_out_opens)1171		return 0;1172 1173	guard(spinlock_irqsave)(&ump->legacy_locks[dir]);1174	for (group = 0; group < SNDRV_UMP_MAX_GROUPS; group++) {1175		substream = ump->legacy_substreams[dir][group];1176		if (!substream)1177			continue;1178		ctx = &ump->out_cvts[group];1179		protocol = ump->info.protocol;1180		if ((protocol & SNDRV_UMP_EP_INFO_PROTO_MIDI2) &&1181		    ump->groups[group].is_midi1)1182			protocol = SNDRV_UMP_EP_INFO_PROTO_MIDI1;1183		while (!ctx->ump_bytes &&1184		       snd_rawmidi_transmit(substream, &c, 1) > 0)1185			snd_ump_convert_to_ump(ctx, group, protocol, c);1186		if (ctx->ump_bytes && ctx->ump_bytes <= count) {1187			size = ctx->ump_bytes;1188			memcpy(buffer, ctx->ump, size);1189			ctx->ump_bytes = 0;1190			break;1191		}1192	}1193	return size;1194}1195 1196static void process_legacy_input(struct snd_ump_endpoint *ump, const u32 *src,1197				 int words)1198{1199	struct snd_rawmidi_substream *substream;1200	unsigned char buf[16];1201	unsigned char group;1202	const int dir = SNDRV_RAWMIDI_STREAM_INPUT;1203	int size;1204 1205	size = snd_ump_convert_from_ump(src, buf, &group);1206	if (size <= 0)1207		return;1208	guard(spinlock_irqsave)(&ump->legacy_locks[dir]);1209	substream = ump->legacy_substreams[dir][group];1210	if (substream)1211		snd_rawmidi_receive(substream, buf, size);1212}1213 1214/* Fill ump->legacy_mapping[] for groups to be used for legacy rawmidi */1215static int fill_legacy_mapping(struct snd_ump_endpoint *ump)1216{1217	struct snd_ump_block *fb;1218	unsigned int group_maps = 0;1219	int i, num;1220 1221	if (ump->info.flags & SNDRV_UMP_EP_INFO_STATIC_BLOCKS) {1222		list_for_each_entry(fb, &ump->block_list, list) {1223			for (i = 0; i < fb->info.num_groups; i++)1224				group_maps |= 1U << (fb->info.first_group + i);1225		}1226		if (!group_maps)1227			ump_info(ump, "No UMP Group is found in FB\n");1228	}1229 1230	/* use all groups for non-static case */1231	if (!group_maps)1232		group_maps = (1U << SNDRV_UMP_MAX_GROUPS) - 1;1233 1234	num = 0;1235	for (i = 0; i < SNDRV_UMP_MAX_GROUPS; i++)1236		if ((group_maps & (1U << i)) && ump->groups[i].valid)1237			ump->legacy_mapping[num++] = i;1238 1239	return num;1240}1241 1242static void fill_substream_names(struct snd_ump_endpoint *ump,1243				 struct snd_rawmidi *rmidi, int dir)1244{1245	struct snd_rawmidi_substream *s;1246	const char *name;1247	int idx;1248 1249	list_for_each_entry(s, &rmidi->streams[dir].substreams, list) {1250		idx = ump->legacy_mapping[s->number];1251		name = ump->groups[idx].name;1252		if (!*name)1253			name = ump->info.name;1254		snprintf(s->name, sizeof(s->name), "Group %d (%.16s)",1255			 idx + 1, name);1256	}1257}1258 1259int snd_ump_attach_legacy_rawmidi(struct snd_ump_endpoint *ump,1260				  char *id, int device)1261{1262	struct snd_rawmidi *rmidi;1263	bool input, output;1264	int err, num;1265 1266	ump->out_cvts = kcalloc(SNDRV_UMP_MAX_GROUPS,1267				sizeof(*ump->out_cvts), GFP_KERNEL);1268	if (!ump->out_cvts)1269		return -ENOMEM;1270 1271	num = fill_legacy_mapping(ump);1272 1273	input = ump->core.info_flags & SNDRV_RAWMIDI_INFO_INPUT;1274	output = ump->core.info_flags & SNDRV_RAWMIDI_INFO_OUTPUT;1275	err = snd_rawmidi_new(ump->core.card, id, device,1276			      output ? num : 0, input ? num : 0,1277			      &rmidi);1278	if (err < 0) {1279		kfree(ump->out_cvts);1280		return err;1281	}1282 1283	if (input)1284		snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT,1285				    &snd_ump_legacy_input_ops);1286	if (output)1287		snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT,1288				    &snd_ump_legacy_output_ops);1289	snprintf(rmidi->name, sizeof(rmidi->name), "%.68s (MIDI 1.0)",1290		 ump->info.name);1291	rmidi->info_flags = ump->core.info_flags & ~SNDRV_RAWMIDI_INFO_UMP;1292	rmidi->ops = &snd_ump_legacy_ops;1293	rmidi->private_data = ump;1294	ump->legacy_rmidi = rmidi;1295	if (input)1296		fill_substream_names(ump, rmidi, SNDRV_RAWMIDI_STREAM_INPUT);1297	if (output)1298		fill_substream_names(ump, rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT);1299 1300	ump_dbg(ump, "Created a legacy rawmidi #%d (%s)\n", device, id);1301	return 0;1302}1303EXPORT_SYMBOL_GPL(snd_ump_attach_legacy_rawmidi);1304#endif /* CONFIG_SND_UMP_LEGACY_RAWMIDI */1305 1306MODULE_DESCRIPTION("Universal MIDI Packet (UMP) Core Driver");1307MODULE_LICENSE("GPL");1308