Linux Audio

Check our new training course

Loading...
Note: File does not exist in v5.4.
  1// SPDX-License-Identifier: GPL-2.0-or-later
  2/* ALSA sequencer binding for UMP device */
  3
  4#include <linux/init.h>
  5#include <linux/slab.h>
  6#include <linux/errno.h>
  7#include <linux/mutex.h>
  8#include <linux/string.h>
  9#include <linux/module.h>
 10#include <asm/byteorder.h>
 11#include <sound/core.h>
 12#include <sound/ump.h>
 13#include <sound/seq_kernel.h>
 14#include <sound/seq_device.h>
 15#include "seq_clientmgr.h"
 16#include "seq_system.h"
 17
 18struct seq_ump_client;
 19struct seq_ump_group;
 20
 21enum {
 22	STR_IN = SNDRV_RAWMIDI_STREAM_INPUT,
 23	STR_OUT = SNDRV_RAWMIDI_STREAM_OUTPUT
 24};
 25
 26/* object per UMP group; corresponding to a sequencer port */
 27struct seq_ump_group {
 28	int group;			/* group index (0-based) */
 29	unsigned int dir_bits;		/* directions */
 30	bool active;			/* activeness */
 31	char name[64];			/* seq port name */
 32};
 33
 34/* context for UMP input parsing, per EP */
 35struct seq_ump_input_buffer {
 36	unsigned char len;		/* total length in words */
 37	unsigned char pending;		/* pending words */
 38	unsigned char type;		/* parsed UMP packet type */
 39	unsigned char group;		/* parsed UMP packet group */
 40	u32 buf[4];			/* incoming UMP packet */
 41};
 42
 43/* sequencer client, per UMP EP (rawmidi) */
 44struct seq_ump_client {
 45	struct snd_ump_endpoint *ump;	/* assigned endpoint */
 46	int seq_client;			/* sequencer client id */
 47	int opened[2];			/* current opens for each direction */
 48	struct snd_rawmidi_file out_rfile; /* rawmidi for output */
 49	struct seq_ump_input_buffer input; /* input parser context */
 50	struct seq_ump_group groups[SNDRV_UMP_MAX_GROUPS]; /* table of groups */
 51	void *ump_info[SNDRV_UMP_MAX_BLOCKS + 1]; /* shadow of seq client ump_info */
 52	struct work_struct group_notify_work; /* FB change notification */
 53};
 54
 55/* number of 32bit words for each UMP message type */
 56static unsigned char ump_packet_words[0x10] = {
 57	1, 1, 1, 2, 2, 4, 1, 1, 2, 2, 2, 3, 3, 4, 4, 4
 58};
 59
 60/* conversion between UMP group and seq port;
 61 * assume the port number is equal with UMP group number (1-based)
 62 */
 63static unsigned char ump_group_to_seq_port(unsigned char group)
 64{
 65	return group + 1;
 66}
 67
 68/* process the incoming rawmidi stream */
 69static void seq_ump_input_receive(struct snd_ump_endpoint *ump,
 70				  const u32 *val, int words)
 71{
 72	struct seq_ump_client *client = ump->seq_client;
 73	struct snd_seq_ump_event ev = {};
 74
 75	if (!client->opened[STR_IN])
 76		return;
 77
 78	if (ump_is_groupless_msg(ump_message_type(*val)))
 79		ev.source.port = 0; /* UMP EP port */
 80	else
 81		ev.source.port = ump_group_to_seq_port(ump_message_group(*val));
 82	ev.dest.client = SNDRV_SEQ_ADDRESS_SUBSCRIBERS;
 83	ev.flags = SNDRV_SEQ_EVENT_UMP;
 84	memcpy(ev.ump, val, words << 2);
 85	snd_seq_kernel_client_dispatch(client->seq_client,
 86				       (struct snd_seq_event *)&ev,
 87				       true, 0);
 88}
 89
 90/* process an input sequencer event; only deal with UMP types */
 91static int seq_ump_process_event(struct snd_seq_event *ev, int direct,
 92				 void *private_data, int atomic, int hop)
 93{
 94	struct seq_ump_client *client = private_data;
 95	struct snd_rawmidi_substream *substream;
 96	struct snd_seq_ump_event *ump_ev;
 97	unsigned char type;
 98	int len;
 99
100	substream = client->out_rfile.output;
101	if (!substream)
102		return -ENODEV;
103	if (!snd_seq_ev_is_ump(ev))
104		return 0; /* invalid event, skip */
105	ump_ev = (struct snd_seq_ump_event *)ev;
106	type = ump_message_type(ump_ev->ump[0]);
107	len = ump_packet_words[type];
108	if (len > 4)
109		return 0; // invalid - skip
110	snd_rawmidi_kernel_write(substream, ev->data.raw8.d, len << 2);
111	return 0;
112}
113
114/* open the rawmidi */
115static int seq_ump_client_open(struct seq_ump_client *client, int dir)
116{
117	struct snd_ump_endpoint *ump = client->ump;
118	int err = 0;
119
120	mutex_lock(&ump->open_mutex);
121	if (dir == STR_OUT && !client->opened[dir]) {
122		err = snd_rawmidi_kernel_open(&ump->core, 0,
123					      SNDRV_RAWMIDI_LFLG_OUTPUT |
124					      SNDRV_RAWMIDI_LFLG_APPEND,
125					      &client->out_rfile);
126		if (err < 0)
127			goto unlock;
128	}
129	client->opened[dir]++;
130 unlock:
131	mutex_unlock(&ump->open_mutex);
132	return err;
133}
134
135/* close the rawmidi */
136static int seq_ump_client_close(struct seq_ump_client *client, int dir)
137{
138	struct snd_ump_endpoint *ump = client->ump;
139
140	mutex_lock(&ump->open_mutex);
141	if (!--client->opened[dir])
142		if (dir == STR_OUT)
143			snd_rawmidi_kernel_release(&client->out_rfile);
144	mutex_unlock(&ump->open_mutex);
145	return 0;
146}
147
148/* sequencer subscription ops for each client */
149static int seq_ump_subscribe(void *pdata, struct snd_seq_port_subscribe *info)
150{
151	struct seq_ump_client *client = pdata;
152
153	return seq_ump_client_open(client, STR_IN);
154}
155
156static int seq_ump_unsubscribe(void *pdata, struct snd_seq_port_subscribe *info)
157{
158	struct seq_ump_client *client = pdata;
159
160	return seq_ump_client_close(client, STR_IN);
161}
162
163static int seq_ump_use(void *pdata, struct snd_seq_port_subscribe *info)
164{
165	struct seq_ump_client *client = pdata;
166
167	return seq_ump_client_open(client, STR_OUT);
168}
169
170static int seq_ump_unuse(void *pdata, struct snd_seq_port_subscribe *info)
171{
172	struct seq_ump_client *client = pdata;
173
174	return seq_ump_client_close(client, STR_OUT);
175}
176
177/* fill port_info from the given UMP EP and group info */
178static void fill_port_info(struct snd_seq_port_info *port,
179			   struct seq_ump_client *client,
180			   struct seq_ump_group *group)
181{
182	unsigned int rawmidi_info = client->ump->core.info_flags;
183
184	port->addr.client = client->seq_client;
185	port->addr.port = ump_group_to_seq_port(group->group);
186	port->capability = 0;
187	if (rawmidi_info & SNDRV_RAWMIDI_INFO_OUTPUT)
188		port->capability |= SNDRV_SEQ_PORT_CAP_WRITE |
189			SNDRV_SEQ_PORT_CAP_SYNC_WRITE |
190			SNDRV_SEQ_PORT_CAP_SUBS_WRITE;
191	if (rawmidi_info & SNDRV_RAWMIDI_INFO_INPUT)
192		port->capability |= SNDRV_SEQ_PORT_CAP_READ |
193			SNDRV_SEQ_PORT_CAP_SYNC_READ |
194			SNDRV_SEQ_PORT_CAP_SUBS_READ;
195	if (rawmidi_info & SNDRV_RAWMIDI_INFO_DUPLEX)
196		port->capability |= SNDRV_SEQ_PORT_CAP_DUPLEX;
197	if (group->dir_bits & (1 << STR_IN))
198		port->direction |= SNDRV_SEQ_PORT_DIR_INPUT;
199	if (group->dir_bits & (1 << STR_OUT))
200		port->direction |= SNDRV_SEQ_PORT_DIR_OUTPUT;
201	port->ump_group = group->group + 1;
202	if (!group->active)
203		port->capability |= SNDRV_SEQ_PORT_CAP_INACTIVE;
204	port->type = SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC |
205		SNDRV_SEQ_PORT_TYPE_MIDI_UMP |
206		SNDRV_SEQ_PORT_TYPE_HARDWARE |
207		SNDRV_SEQ_PORT_TYPE_PORT;
208	port->midi_channels = 16;
209	if (*group->name)
210		snprintf(port->name, sizeof(port->name), "Group %d (%.53s)",
211			 group->group + 1, group->name);
212	else
213		sprintf(port->name, "Group %d", group->group + 1);
214}
215
216/* create a new sequencer port per UMP group */
217static int seq_ump_group_init(struct seq_ump_client *client, int group_index)
218{
219	struct seq_ump_group *group = &client->groups[group_index];
220	struct snd_seq_port_info *port;
221	struct snd_seq_port_callback pcallbacks;
222	int err;
223
224	port = kzalloc(sizeof(*port), GFP_KERNEL);
225	if (!port) {
226		err = -ENOMEM;
227		goto error;
228	}
229
230	fill_port_info(port, client, group);
231	port->flags = SNDRV_SEQ_PORT_FLG_GIVEN_PORT;
232	memset(&pcallbacks, 0, sizeof(pcallbacks));
233	pcallbacks.owner = THIS_MODULE;
234	pcallbacks.private_data = client;
235	pcallbacks.subscribe = seq_ump_subscribe;
236	pcallbacks.unsubscribe = seq_ump_unsubscribe;
237	pcallbacks.use = seq_ump_use;
238	pcallbacks.unuse = seq_ump_unuse;
239	pcallbacks.event_input = seq_ump_process_event;
240	port->kernel = &pcallbacks;
241	err = snd_seq_kernel_client_ctl(client->seq_client,
242					SNDRV_SEQ_IOCTL_CREATE_PORT,
243					port);
244 error:
245	kfree(port);
246	return err;
247}
248
249/* update the sequencer ports; called from notify_fb_change callback */
250static void update_port_infos(struct seq_ump_client *client)
251{
252	struct snd_seq_port_info *old, *new;
253	int i, err;
254
255	old = kzalloc(sizeof(*old), GFP_KERNEL);
256	new = kzalloc(sizeof(*new), GFP_KERNEL);
257	if (!old || !new)
258		goto error;
259
260	for (i = 0; i < SNDRV_UMP_MAX_GROUPS; i++) {
261		old->addr.client = client->seq_client;
262		old->addr.port = i;
263		err = snd_seq_kernel_client_ctl(client->seq_client,
264						SNDRV_SEQ_IOCTL_GET_PORT_INFO,
265						old);
266		if (err < 0)
267			goto error;
268		fill_port_info(new, client, &client->groups[i]);
269		if (old->capability == new->capability &&
270		    !strcmp(old->name, new->name))
271			continue;
272		err = snd_seq_kernel_client_ctl(client->seq_client,
273						SNDRV_SEQ_IOCTL_SET_PORT_INFO,
274						new);
275		if (err < 0)
276			goto error;
277		/* notify to system port */
278		snd_seq_system_client_ev_port_change(client->seq_client, i);
279	}
280 error:
281	kfree(new);
282	kfree(old);
283}
284
285/* update dir_bits and active flag for all groups in the client */
286static void update_group_attrs(struct seq_ump_client *client)
287{
288	struct snd_ump_block *fb;
289	struct seq_ump_group *group;
290	int i;
291
292	for (i = 0; i < SNDRV_UMP_MAX_GROUPS; i++) {
293		group = &client->groups[i];
294		*group->name = 0;
295		group->dir_bits = 0;
296		group->active = 0;
297		group->group = i;
298	}
299
300	list_for_each_entry(fb, &client->ump->block_list, list) {
301		if (fb->info.first_group + fb->info.num_groups > SNDRV_UMP_MAX_GROUPS)
302			break;
303		group = &client->groups[fb->info.first_group];
304		for (i = 0; i < fb->info.num_groups; i++, group++) {
305			if (fb->info.active)
306				group->active = 1;
307			switch (fb->info.direction) {
308			case SNDRV_UMP_DIR_INPUT:
309				group->dir_bits |= (1 << STR_IN);
310				break;
311			case SNDRV_UMP_DIR_OUTPUT:
312				group->dir_bits |= (1 << STR_OUT);
313				break;
314			case SNDRV_UMP_DIR_BIDIRECTION:
315				group->dir_bits |= (1 << STR_OUT) | (1 << STR_IN);
316				break;
317			}
318			if (!*fb->info.name)
319				continue;
320			if (!*group->name) {
321				/* store the first matching name */
322				strscpy(group->name, fb->info.name,
323					sizeof(group->name));
324			} else {
325				/* when overlapping, concat names */
326				strlcat(group->name, ", ", sizeof(group->name));
327				strlcat(group->name, fb->info.name,
328					sizeof(group->name));
329			}
330		}
331	}
332}
333
334/* create a UMP Endpoint port */
335static int create_ump_endpoint_port(struct seq_ump_client *client)
336{
337	struct snd_seq_port_info *port;
338	struct snd_seq_port_callback pcallbacks;
339	unsigned int rawmidi_info = client->ump->core.info_flags;
340	int err;
341
342	port = kzalloc(sizeof(*port), GFP_KERNEL);
343	if (!port)
344		return -ENOMEM;
345
346	port->addr.client = client->seq_client;
347	port->addr.port = 0; /* fixed */
348	port->flags = SNDRV_SEQ_PORT_FLG_GIVEN_PORT;
349	port->capability = SNDRV_SEQ_PORT_CAP_UMP_ENDPOINT;
350	if (rawmidi_info & SNDRV_RAWMIDI_INFO_INPUT) {
351		port->capability |= SNDRV_SEQ_PORT_CAP_READ |
352			SNDRV_SEQ_PORT_CAP_SYNC_READ |
353			SNDRV_SEQ_PORT_CAP_SUBS_READ;
354		port->direction |= SNDRV_SEQ_PORT_DIR_INPUT;
355	}
356	if (rawmidi_info & SNDRV_RAWMIDI_INFO_OUTPUT) {
357		port->capability |= SNDRV_SEQ_PORT_CAP_WRITE |
358			SNDRV_SEQ_PORT_CAP_SYNC_WRITE |
359			SNDRV_SEQ_PORT_CAP_SUBS_WRITE;
360		port->direction |= SNDRV_SEQ_PORT_DIR_OUTPUT;
361	}
362	if (rawmidi_info & SNDRV_RAWMIDI_INFO_DUPLEX)
363		port->capability |= SNDRV_SEQ_PORT_CAP_DUPLEX;
364	port->ump_group = 0; /* no associated group, no conversion */
365	port->type = SNDRV_SEQ_PORT_TYPE_MIDI_UMP |
366		SNDRV_SEQ_PORT_TYPE_HARDWARE |
367		SNDRV_SEQ_PORT_TYPE_PORT;
368	port->midi_channels = 16;
369	strcpy(port->name, "MIDI 2.0");
370	memset(&pcallbacks, 0, sizeof(pcallbacks));
371	pcallbacks.owner = THIS_MODULE;
372	pcallbacks.private_data = client;
373	if (rawmidi_info & SNDRV_RAWMIDI_INFO_INPUT) {
374		pcallbacks.subscribe = seq_ump_subscribe;
375		pcallbacks.unsubscribe = seq_ump_unsubscribe;
376	}
377	if (rawmidi_info & SNDRV_RAWMIDI_INFO_OUTPUT) {
378		pcallbacks.use = seq_ump_use;
379		pcallbacks.unuse = seq_ump_unuse;
380		pcallbacks.event_input = seq_ump_process_event;
381	}
382	port->kernel = &pcallbacks;
383	err = snd_seq_kernel_client_ctl(client->seq_client,
384					SNDRV_SEQ_IOCTL_CREATE_PORT,
385					port);
386	kfree(port);
387	return err;
388}
389
390/* release the client resources */
391static void seq_ump_client_free(struct seq_ump_client *client)
392{
393	cancel_work_sync(&client->group_notify_work);
394
395	if (client->seq_client >= 0)
396		snd_seq_delete_kernel_client(client->seq_client);
397
398	client->ump->seq_ops = NULL;
399	client->ump->seq_client = NULL;
400
401	kfree(client);
402}
403
404/* update the MIDI version for the given client */
405static void setup_client_midi_version(struct seq_ump_client *client)
406{
407	struct snd_seq_client *cptr;
408
409	cptr = snd_seq_kernel_client_get(client->seq_client);
410	if (!cptr)
411		return;
412	if (client->ump->info.protocol & SNDRV_UMP_EP_INFO_PROTO_MIDI2)
413		cptr->midi_version = SNDRV_SEQ_CLIENT_UMP_MIDI_2_0;
414	else
415		cptr->midi_version = SNDRV_SEQ_CLIENT_UMP_MIDI_1_0;
416	snd_seq_kernel_client_put(cptr);
417}
418
419/* set up client's group_filter bitmap */
420static void setup_client_group_filter(struct seq_ump_client *client)
421{
422	struct snd_seq_client *cptr;
423	unsigned int filter;
424	int p;
425
426	cptr = snd_seq_kernel_client_get(client->seq_client);
427	if (!cptr)
428		return;
429	filter = ~(1U << 0); /* always allow groupless messages */
430	for (p = 0; p < SNDRV_UMP_MAX_GROUPS; p++) {
431		if (client->groups[p].active)
432			filter &= ~(1U << (p + 1));
433	}
434	cptr->group_filter = filter;
435	snd_seq_kernel_client_put(cptr);
436}
437
438/* UMP group change notification */
439static void handle_group_notify(struct work_struct *work)
440{
441	struct seq_ump_client *client =
442		container_of(work, struct seq_ump_client, group_notify_work);
443
444	update_group_attrs(client);
445	update_port_infos(client);
446	setup_client_group_filter(client);
447}
448
449/* UMP FB change notification */
450static int seq_ump_notify_fb_change(struct snd_ump_endpoint *ump,
451				    struct snd_ump_block *fb)
452{
453	struct seq_ump_client *client = ump->seq_client;
454
455	if (!client)
456		return -ENODEV;
457	schedule_work(&client->group_notify_work);
458	return 0;
459}
460
461/* UMP protocol change notification; just update the midi_version field */
462static int seq_ump_switch_protocol(struct snd_ump_endpoint *ump)
463{
464	if (!ump->seq_client)
465		return -ENODEV;
466	setup_client_midi_version(ump->seq_client);
467	return 0;
468}
469
470static const struct snd_seq_ump_ops seq_ump_ops = {
471	.input_receive = seq_ump_input_receive,
472	.notify_fb_change = seq_ump_notify_fb_change,
473	.switch_protocol = seq_ump_switch_protocol,
474};
475
476/* create a sequencer client and ports for the given UMP endpoint */
477static int snd_seq_ump_probe(struct device *_dev)
478{
479	struct snd_seq_device *dev = to_seq_dev(_dev);
480	struct snd_ump_endpoint *ump = dev->private_data;
481	struct snd_card *card = dev->card;
482	struct seq_ump_client *client;
483	struct snd_ump_block *fb;
484	struct snd_seq_client *cptr;
485	int p, err;
486
487	client = kzalloc(sizeof(*client), GFP_KERNEL);
488	if (!client)
489		return -ENOMEM;
490
491	INIT_WORK(&client->group_notify_work, handle_group_notify);
492	client->ump = ump;
493
494	client->seq_client =
495		snd_seq_create_kernel_client(card, ump->core.device,
496					     ump->core.name);
497	if (client->seq_client < 0) {
498		err = client->seq_client;
499		goto error;
500	}
501
502	client->ump_info[0] = &ump->info;
503	list_for_each_entry(fb, &ump->block_list, list)
504		client->ump_info[fb->info.block_id + 1] = &fb->info;
505
506	setup_client_midi_version(client);
507	update_group_attrs(client);
508
509	for (p = 0; p < SNDRV_UMP_MAX_GROUPS; p++) {
510		err = seq_ump_group_init(client, p);
511		if (err < 0)
512			goto error;
513	}
514
515	setup_client_group_filter(client);
516
517	err = create_ump_endpoint_port(client);
518	if (err < 0)
519		goto error;
520
521	cptr = snd_seq_kernel_client_get(client->seq_client);
522	if (!cptr) {
523		err = -EINVAL;
524		goto error;
525	}
526	cptr->ump_info = client->ump_info;
527	snd_seq_kernel_client_put(cptr);
528
529	ump->seq_client = client;
530	ump->seq_ops = &seq_ump_ops;
531	return 0;
532
533 error:
534	seq_ump_client_free(client);
535	return err;
536}
537
538/* remove a sequencer client */
539static int snd_seq_ump_remove(struct device *_dev)
540{
541	struct snd_seq_device *dev = to_seq_dev(_dev);
542	struct snd_ump_endpoint *ump = dev->private_data;
543
544	if (ump->seq_client)
545		seq_ump_client_free(ump->seq_client);
546	return 0;
547}
548
549static struct snd_seq_driver seq_ump_driver = {
550	.driver = {
551		.name = KBUILD_MODNAME,
552		.probe = snd_seq_ump_probe,
553		.remove = snd_seq_ump_remove,
554	},
555	.id = SNDRV_SEQ_DEV_ID_UMP,
556	.argsize = 0,
557};
558
559module_snd_seq_driver(seq_ump_driver);
560
561MODULE_DESCRIPTION("ALSA sequencer client for UMP rawmidi");
562MODULE_LICENSE("GPL");