xref: /linux/sound/core/seq/seq_ump_client.c (revision 36ec807b627b4c0a0a382f0ae48eac7187d14b2b)
181fd444aSTakashi Iwai // SPDX-License-Identifier: GPL-2.0-or-later
281fd444aSTakashi Iwai /* ALSA sequencer binding for UMP device */
381fd444aSTakashi Iwai 
481fd444aSTakashi Iwai #include <linux/init.h>
581fd444aSTakashi Iwai #include <linux/slab.h>
681fd444aSTakashi Iwai #include <linux/errno.h>
781fd444aSTakashi Iwai #include <linux/mutex.h>
881fd444aSTakashi Iwai #include <linux/string.h>
981fd444aSTakashi Iwai #include <linux/module.h>
1081fd444aSTakashi Iwai #include <asm/byteorder.h>
1181fd444aSTakashi Iwai #include <sound/core.h>
1281fd444aSTakashi Iwai #include <sound/ump.h>
1381fd444aSTakashi Iwai #include <sound/seq_kernel.h>
1481fd444aSTakashi Iwai #include <sound/seq_device.h>
1581fd444aSTakashi Iwai #include "seq_clientmgr.h"
16174a6dfbSTakashi Iwai #include "seq_system.h"
1781fd444aSTakashi Iwai 
1881fd444aSTakashi Iwai struct seq_ump_client;
1981fd444aSTakashi Iwai struct seq_ump_group;
2081fd444aSTakashi Iwai 
2181fd444aSTakashi Iwai enum {
2281fd444aSTakashi Iwai 	STR_IN = SNDRV_RAWMIDI_STREAM_INPUT,
2381fd444aSTakashi Iwai 	STR_OUT = SNDRV_RAWMIDI_STREAM_OUTPUT
2481fd444aSTakashi Iwai };
2581fd444aSTakashi Iwai 
2681fd444aSTakashi Iwai /* object per UMP group; corresponding to a sequencer port */
2781fd444aSTakashi Iwai struct seq_ump_group {
2881fd444aSTakashi Iwai 	int group;			/* group index (0-based) */
2981fd444aSTakashi Iwai 	unsigned int dir_bits;		/* directions */
3081fd444aSTakashi Iwai 	bool active;			/* activeness */
31*3bfd7c0bSTakashi Iwai 	bool valid;			/* valid group (referred by blocks) */
3281fd444aSTakashi Iwai 	char name[64];			/* seq port name */
3381fd444aSTakashi Iwai };
3481fd444aSTakashi Iwai 
3581fd444aSTakashi Iwai /* context for UMP input parsing, per EP */
3681fd444aSTakashi Iwai struct seq_ump_input_buffer {
3781fd444aSTakashi Iwai 	unsigned char len;		/* total length in words */
3881fd444aSTakashi Iwai 	unsigned char pending;		/* pending words */
3981fd444aSTakashi Iwai 	unsigned char type;		/* parsed UMP packet type */
4081fd444aSTakashi Iwai 	unsigned char group;		/* parsed UMP packet group */
4181fd444aSTakashi Iwai 	u32 buf[4];			/* incoming UMP packet */
4281fd444aSTakashi Iwai };
4381fd444aSTakashi Iwai 
4481fd444aSTakashi Iwai /* sequencer client, per UMP EP (rawmidi) */
4581fd444aSTakashi Iwai struct seq_ump_client {
4681fd444aSTakashi Iwai 	struct snd_ump_endpoint *ump;	/* assigned endpoint */
4781fd444aSTakashi Iwai 	int seq_client;			/* sequencer client id */
4881fd444aSTakashi Iwai 	int opened[2];			/* current opens for each direction */
4981fd444aSTakashi Iwai 	struct snd_rawmidi_file out_rfile; /* rawmidi for output */
5081fd444aSTakashi Iwai 	struct seq_ump_input_buffer input; /* input parser context */
5181fd444aSTakashi Iwai 	struct seq_ump_group groups[SNDRV_UMP_MAX_GROUPS]; /* table of groups */
52d2d247e3STakashi Iwai 	void *ump_info[SNDRV_UMP_MAX_BLOCKS + 1]; /* shadow of seq client ump_info */
534a16a3afSTakashi Iwai 	struct work_struct group_notify_work; /* FB change notification */
5481fd444aSTakashi Iwai };
5581fd444aSTakashi Iwai 
5681fd444aSTakashi Iwai /* number of 32bit words for each UMP message type */
5781fd444aSTakashi Iwai static unsigned char ump_packet_words[0x10] = {
5881fd444aSTakashi Iwai 	1, 1, 1, 2, 2, 4, 1, 1, 2, 2, 2, 3, 3, 4, 4, 4
5981fd444aSTakashi Iwai };
6081fd444aSTakashi Iwai 
6181fd444aSTakashi Iwai /* conversion between UMP group and seq port;
6281fd444aSTakashi Iwai  * assume the port number is equal with UMP group number (1-based)
6381fd444aSTakashi Iwai  */
6481fd444aSTakashi Iwai static unsigned char ump_group_to_seq_port(unsigned char group)
6581fd444aSTakashi Iwai {
6681fd444aSTakashi Iwai 	return group + 1;
6781fd444aSTakashi Iwai }
6881fd444aSTakashi Iwai 
6981fd444aSTakashi Iwai /* process the incoming rawmidi stream */
7081fd444aSTakashi Iwai static void seq_ump_input_receive(struct snd_ump_endpoint *ump,
7181fd444aSTakashi Iwai 				  const u32 *val, int words)
7281fd444aSTakashi Iwai {
7381fd444aSTakashi Iwai 	struct seq_ump_client *client = ump->seq_client;
7481fd444aSTakashi Iwai 	struct snd_seq_ump_event ev = {};
7581fd444aSTakashi Iwai 
7681fd444aSTakashi Iwai 	if (!client->opened[STR_IN])
7781fd444aSTakashi Iwai 		return;
7881fd444aSTakashi Iwai 
795437ac9bSTakashi Iwai 	if (ump_is_groupless_msg(ump_message_type(*val)))
805437ac9bSTakashi Iwai 		ev.source.port = 0; /* UMP EP port */
815437ac9bSTakashi Iwai 	else
8281fd444aSTakashi Iwai 		ev.source.port = ump_group_to_seq_port(ump_message_group(*val));
8381fd444aSTakashi Iwai 	ev.dest.client = SNDRV_SEQ_ADDRESS_SUBSCRIBERS;
8481fd444aSTakashi Iwai 	ev.flags = SNDRV_SEQ_EVENT_UMP;
8581fd444aSTakashi Iwai 	memcpy(ev.ump, val, words << 2);
8681fd444aSTakashi Iwai 	snd_seq_kernel_client_dispatch(client->seq_client,
8781fd444aSTakashi Iwai 				       (struct snd_seq_event *)&ev,
8881fd444aSTakashi Iwai 				       true, 0);
8981fd444aSTakashi Iwai }
9081fd444aSTakashi Iwai 
9181fd444aSTakashi Iwai /* process an input sequencer event; only deal with UMP types */
9281fd444aSTakashi Iwai static int seq_ump_process_event(struct snd_seq_event *ev, int direct,
9381fd444aSTakashi Iwai 				 void *private_data, int atomic, int hop)
9481fd444aSTakashi Iwai {
9581fd444aSTakashi Iwai 	struct seq_ump_client *client = private_data;
9681fd444aSTakashi Iwai 	struct snd_rawmidi_substream *substream;
9781fd444aSTakashi Iwai 	struct snd_seq_ump_event *ump_ev;
9881fd444aSTakashi Iwai 	unsigned char type;
9981fd444aSTakashi Iwai 	int len;
10081fd444aSTakashi Iwai 
10181fd444aSTakashi Iwai 	substream = client->out_rfile.output;
10281fd444aSTakashi Iwai 	if (!substream)
10381fd444aSTakashi Iwai 		return -ENODEV;
10481fd444aSTakashi Iwai 	if (!snd_seq_ev_is_ump(ev))
10581fd444aSTakashi Iwai 		return 0; /* invalid event, skip */
10681fd444aSTakashi Iwai 	ump_ev = (struct snd_seq_ump_event *)ev;
10781fd444aSTakashi Iwai 	type = ump_message_type(ump_ev->ump[0]);
10881fd444aSTakashi Iwai 	len = ump_packet_words[type];
10981fd444aSTakashi Iwai 	if (len > 4)
11081fd444aSTakashi Iwai 		return 0; // invalid - skip
11181fd444aSTakashi Iwai 	snd_rawmidi_kernel_write(substream, ev->data.raw8.d, len << 2);
11281fd444aSTakashi Iwai 	return 0;
11381fd444aSTakashi Iwai }
11481fd444aSTakashi Iwai 
11581fd444aSTakashi Iwai /* open the rawmidi */
11681fd444aSTakashi Iwai static int seq_ump_client_open(struct seq_ump_client *client, int dir)
11781fd444aSTakashi Iwai {
11881fd444aSTakashi Iwai 	struct snd_ump_endpoint *ump = client->ump;
1196487e363STakashi Iwai 	int err;
12081fd444aSTakashi Iwai 
1216487e363STakashi Iwai 	guard(mutex)(&ump->open_mutex);
12281fd444aSTakashi Iwai 	if (dir == STR_OUT && !client->opened[dir]) {
12381fd444aSTakashi Iwai 		err = snd_rawmidi_kernel_open(&ump->core, 0,
12481fd444aSTakashi Iwai 					      SNDRV_RAWMIDI_LFLG_OUTPUT |
12581fd444aSTakashi Iwai 					      SNDRV_RAWMIDI_LFLG_APPEND,
12681fd444aSTakashi Iwai 					      &client->out_rfile);
12781fd444aSTakashi Iwai 		if (err < 0)
1286487e363STakashi Iwai 			return err;
12981fd444aSTakashi Iwai 	}
13081fd444aSTakashi Iwai 	client->opened[dir]++;
1316487e363STakashi Iwai 	return 0;
13281fd444aSTakashi Iwai }
13381fd444aSTakashi Iwai 
13481fd444aSTakashi Iwai /* close the rawmidi */
13581fd444aSTakashi Iwai static int seq_ump_client_close(struct seq_ump_client *client, int dir)
13681fd444aSTakashi Iwai {
13781fd444aSTakashi Iwai 	struct snd_ump_endpoint *ump = client->ump;
13881fd444aSTakashi Iwai 
1396487e363STakashi Iwai 	guard(mutex)(&ump->open_mutex);
14081fd444aSTakashi Iwai 	if (!--client->opened[dir])
14181fd444aSTakashi Iwai 		if (dir == STR_OUT)
14281fd444aSTakashi Iwai 			snd_rawmidi_kernel_release(&client->out_rfile);
14381fd444aSTakashi Iwai 	return 0;
14481fd444aSTakashi Iwai }
14581fd444aSTakashi Iwai 
14681fd444aSTakashi Iwai /* sequencer subscription ops for each client */
14781fd444aSTakashi Iwai static int seq_ump_subscribe(void *pdata, struct snd_seq_port_subscribe *info)
14881fd444aSTakashi Iwai {
14981fd444aSTakashi Iwai 	struct seq_ump_client *client = pdata;
15081fd444aSTakashi Iwai 
15181fd444aSTakashi Iwai 	return seq_ump_client_open(client, STR_IN);
15281fd444aSTakashi Iwai }
15381fd444aSTakashi Iwai 
15481fd444aSTakashi Iwai static int seq_ump_unsubscribe(void *pdata, struct snd_seq_port_subscribe *info)
15581fd444aSTakashi Iwai {
15681fd444aSTakashi Iwai 	struct seq_ump_client *client = pdata;
15781fd444aSTakashi Iwai 
15881fd444aSTakashi Iwai 	return seq_ump_client_close(client, STR_IN);
15981fd444aSTakashi Iwai }
16081fd444aSTakashi Iwai 
16181fd444aSTakashi Iwai static int seq_ump_use(void *pdata, struct snd_seq_port_subscribe *info)
16281fd444aSTakashi Iwai {
16381fd444aSTakashi Iwai 	struct seq_ump_client *client = pdata;
16481fd444aSTakashi Iwai 
16581fd444aSTakashi Iwai 	return seq_ump_client_open(client, STR_OUT);
16681fd444aSTakashi Iwai }
16781fd444aSTakashi Iwai 
16881fd444aSTakashi Iwai static int seq_ump_unuse(void *pdata, struct snd_seq_port_subscribe *info)
16981fd444aSTakashi Iwai {
17081fd444aSTakashi Iwai 	struct seq_ump_client *client = pdata;
17181fd444aSTakashi Iwai 
17281fd444aSTakashi Iwai 	return seq_ump_client_close(client, STR_OUT);
17381fd444aSTakashi Iwai }
17481fd444aSTakashi Iwai 
17581fd444aSTakashi Iwai /* fill port_info from the given UMP EP and group info */
17681fd444aSTakashi Iwai static void fill_port_info(struct snd_seq_port_info *port,
17781fd444aSTakashi Iwai 			   struct seq_ump_client *client,
17881fd444aSTakashi Iwai 			   struct seq_ump_group *group)
17981fd444aSTakashi Iwai {
18081fd444aSTakashi Iwai 	unsigned int rawmidi_info = client->ump->core.info_flags;
18181fd444aSTakashi Iwai 
18281fd444aSTakashi Iwai 	port->addr.client = client->seq_client;
18381fd444aSTakashi Iwai 	port->addr.port = ump_group_to_seq_port(group->group);
18481fd444aSTakashi Iwai 	port->capability = 0;
18581fd444aSTakashi Iwai 	if (rawmidi_info & SNDRV_RAWMIDI_INFO_OUTPUT)
18681fd444aSTakashi Iwai 		port->capability |= SNDRV_SEQ_PORT_CAP_WRITE |
18781fd444aSTakashi Iwai 			SNDRV_SEQ_PORT_CAP_SYNC_WRITE |
18881fd444aSTakashi Iwai 			SNDRV_SEQ_PORT_CAP_SUBS_WRITE;
18981fd444aSTakashi Iwai 	if (rawmidi_info & SNDRV_RAWMIDI_INFO_INPUT)
19081fd444aSTakashi Iwai 		port->capability |= SNDRV_SEQ_PORT_CAP_READ |
19181fd444aSTakashi Iwai 			SNDRV_SEQ_PORT_CAP_SYNC_READ |
19281fd444aSTakashi Iwai 			SNDRV_SEQ_PORT_CAP_SUBS_READ;
19381fd444aSTakashi Iwai 	if (rawmidi_info & SNDRV_RAWMIDI_INFO_DUPLEX)
19481fd444aSTakashi Iwai 		port->capability |= SNDRV_SEQ_PORT_CAP_DUPLEX;
19581fd444aSTakashi Iwai 	if (group->dir_bits & (1 << STR_IN))
19681fd444aSTakashi Iwai 		port->direction |= SNDRV_SEQ_PORT_DIR_INPUT;
19781fd444aSTakashi Iwai 	if (group->dir_bits & (1 << STR_OUT))
19881fd444aSTakashi Iwai 		port->direction |= SNDRV_SEQ_PORT_DIR_OUTPUT;
19981fd444aSTakashi Iwai 	port->ump_group = group->group + 1;
20081fd444aSTakashi Iwai 	if (!group->active)
20181fd444aSTakashi Iwai 		port->capability |= SNDRV_SEQ_PORT_CAP_INACTIVE;
20281fd444aSTakashi Iwai 	port->type = SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC |
20381fd444aSTakashi Iwai 		SNDRV_SEQ_PORT_TYPE_MIDI_UMP |
20481fd444aSTakashi Iwai 		SNDRV_SEQ_PORT_TYPE_HARDWARE |
20581fd444aSTakashi Iwai 		SNDRV_SEQ_PORT_TYPE_PORT;
20681fd444aSTakashi Iwai 	port->midi_channels = 16;
20781fd444aSTakashi Iwai 	if (*group->name)
2080d422608STakashi Iwai 		snprintf(port->name, sizeof(port->name), "Group %d (%.53s)",
20981fd444aSTakashi Iwai 			 group->group + 1, group->name);
21081fd444aSTakashi Iwai 	else
21181fd444aSTakashi Iwai 		sprintf(port->name, "Group %d", group->group + 1);
21281fd444aSTakashi Iwai }
21381fd444aSTakashi Iwai 
214*3bfd7c0bSTakashi Iwai /* skip non-existing group for static blocks */
215*3bfd7c0bSTakashi Iwai static bool skip_group(struct seq_ump_client *client, struct seq_ump_group *group)
216*3bfd7c0bSTakashi Iwai {
217*3bfd7c0bSTakashi Iwai 	return !group->valid &&
218*3bfd7c0bSTakashi Iwai 		(client->ump->info.flags & SNDRV_UMP_EP_INFO_STATIC_BLOCKS);
219*3bfd7c0bSTakashi Iwai }
220*3bfd7c0bSTakashi Iwai 
22181fd444aSTakashi Iwai /* create a new sequencer port per UMP group */
22281fd444aSTakashi Iwai static int seq_ump_group_init(struct seq_ump_client *client, int group_index)
22381fd444aSTakashi Iwai {
22481fd444aSTakashi Iwai 	struct seq_ump_group *group = &client->groups[group_index];
225316e38efSTakashi Iwai 	struct snd_seq_port_info *port __free(kfree) = NULL;
22681fd444aSTakashi Iwai 	struct snd_seq_port_callback pcallbacks;
22781fd444aSTakashi Iwai 
228*3bfd7c0bSTakashi Iwai 	if (skip_group(client, group))
229*3bfd7c0bSTakashi Iwai 		return 0;
230*3bfd7c0bSTakashi Iwai 
23181fd444aSTakashi Iwai 	port = kzalloc(sizeof(*port), GFP_KERNEL);
232316e38efSTakashi Iwai 	if (!port)
233316e38efSTakashi Iwai 		return -ENOMEM;
23481fd444aSTakashi Iwai 
23581fd444aSTakashi Iwai 	fill_port_info(port, client, group);
23681fd444aSTakashi Iwai 	port->flags = SNDRV_SEQ_PORT_FLG_GIVEN_PORT;
23781fd444aSTakashi Iwai 	memset(&pcallbacks, 0, sizeof(pcallbacks));
23881fd444aSTakashi Iwai 	pcallbacks.owner = THIS_MODULE;
23981fd444aSTakashi Iwai 	pcallbacks.private_data = client;
24081fd444aSTakashi Iwai 	pcallbacks.subscribe = seq_ump_subscribe;
24181fd444aSTakashi Iwai 	pcallbacks.unsubscribe = seq_ump_unsubscribe;
24281fd444aSTakashi Iwai 	pcallbacks.use = seq_ump_use;
24381fd444aSTakashi Iwai 	pcallbacks.unuse = seq_ump_unuse;
24481fd444aSTakashi Iwai 	pcallbacks.event_input = seq_ump_process_event;
24581fd444aSTakashi Iwai 	port->kernel = &pcallbacks;
246316e38efSTakashi Iwai 	return snd_seq_kernel_client_ctl(client->seq_client,
24781fd444aSTakashi Iwai 					 SNDRV_SEQ_IOCTL_CREATE_PORT,
24881fd444aSTakashi Iwai 					 port);
24981fd444aSTakashi Iwai }
25081fd444aSTakashi Iwai 
2514a16a3afSTakashi Iwai /* update the sequencer ports; called from notify_fb_change callback */
2524a16a3afSTakashi Iwai static void update_port_infos(struct seq_ump_client *client)
2534a16a3afSTakashi Iwai {
254316e38efSTakashi Iwai 	struct snd_seq_port_info *old __free(kfree) = NULL;
255316e38efSTakashi Iwai 	struct snd_seq_port_info *new __free(kfree) = NULL;
2564a16a3afSTakashi Iwai 	int i, err;
2574a16a3afSTakashi Iwai 
2584a16a3afSTakashi Iwai 	old = kzalloc(sizeof(*old), GFP_KERNEL);
2594a16a3afSTakashi Iwai 	new = kzalloc(sizeof(*new), GFP_KERNEL);
2604a16a3afSTakashi Iwai 	if (!old || !new)
261316e38efSTakashi Iwai 		return;
2624a16a3afSTakashi Iwai 
2634a16a3afSTakashi Iwai 	for (i = 0; i < SNDRV_UMP_MAX_GROUPS; i++) {
264*3bfd7c0bSTakashi Iwai 		if (skip_group(client, &client->groups[i]))
265*3bfd7c0bSTakashi Iwai 			continue;
266*3bfd7c0bSTakashi Iwai 
2674a16a3afSTakashi Iwai 		old->addr.client = client->seq_client;
2684a16a3afSTakashi Iwai 		old->addr.port = i;
2694a16a3afSTakashi Iwai 		err = snd_seq_kernel_client_ctl(client->seq_client,
2704a16a3afSTakashi Iwai 						SNDRV_SEQ_IOCTL_GET_PORT_INFO,
2714a16a3afSTakashi Iwai 						old);
2724a16a3afSTakashi Iwai 		if (err < 0)
273316e38efSTakashi Iwai 			return;
2744a16a3afSTakashi Iwai 		fill_port_info(new, client, &client->groups[i]);
2754a16a3afSTakashi Iwai 		if (old->capability == new->capability &&
2764a16a3afSTakashi Iwai 		    !strcmp(old->name, new->name))
2774a16a3afSTakashi Iwai 			continue;
2784a16a3afSTakashi Iwai 		err = snd_seq_kernel_client_ctl(client->seq_client,
2794a16a3afSTakashi Iwai 						SNDRV_SEQ_IOCTL_SET_PORT_INFO,
2804a16a3afSTakashi Iwai 						new);
2814a16a3afSTakashi Iwai 		if (err < 0)
282316e38efSTakashi Iwai 			return;
283174a6dfbSTakashi Iwai 		/* notify to system port */
284174a6dfbSTakashi Iwai 		snd_seq_system_client_ev_port_change(client->seq_client, i);
2854a16a3afSTakashi Iwai 	}
2864a16a3afSTakashi Iwai }
2874a16a3afSTakashi Iwai 
28881fd444aSTakashi Iwai /* update dir_bits and active flag for all groups in the client */
28981fd444aSTakashi Iwai static void update_group_attrs(struct seq_ump_client *client)
29081fd444aSTakashi Iwai {
29181fd444aSTakashi Iwai 	struct snd_ump_block *fb;
29281fd444aSTakashi Iwai 	struct seq_ump_group *group;
29381fd444aSTakashi Iwai 	int i;
29481fd444aSTakashi Iwai 
29581fd444aSTakashi Iwai 	for (i = 0; i < SNDRV_UMP_MAX_GROUPS; i++) {
29681fd444aSTakashi Iwai 		group = &client->groups[i];
29781fd444aSTakashi Iwai 		*group->name = 0;
29881fd444aSTakashi Iwai 		group->dir_bits = 0;
29981fd444aSTakashi Iwai 		group->active = 0;
30081fd444aSTakashi Iwai 		group->group = i;
301*3bfd7c0bSTakashi Iwai 		group->valid = false;
30281fd444aSTakashi Iwai 	}
30381fd444aSTakashi Iwai 
30481fd444aSTakashi Iwai 	list_for_each_entry(fb, &client->ump->block_list, list) {
30559ea9138SWang Weiyang 		if (fb->info.first_group + fb->info.num_groups > SNDRV_UMP_MAX_GROUPS)
30681fd444aSTakashi Iwai 			break;
30781fd444aSTakashi Iwai 		group = &client->groups[fb->info.first_group];
30881fd444aSTakashi Iwai 		for (i = 0; i < fb->info.num_groups; i++, group++) {
309*3bfd7c0bSTakashi Iwai 			group->valid = true;
31081fd444aSTakashi Iwai 			if (fb->info.active)
31181fd444aSTakashi Iwai 				group->active = 1;
31281fd444aSTakashi Iwai 			switch (fb->info.direction) {
31381fd444aSTakashi Iwai 			case SNDRV_UMP_DIR_INPUT:
31481fd444aSTakashi Iwai 				group->dir_bits |= (1 << STR_IN);
31581fd444aSTakashi Iwai 				break;
31681fd444aSTakashi Iwai 			case SNDRV_UMP_DIR_OUTPUT:
31781fd444aSTakashi Iwai 				group->dir_bits |= (1 << STR_OUT);
31881fd444aSTakashi Iwai 				break;
31981fd444aSTakashi Iwai 			case SNDRV_UMP_DIR_BIDIRECTION:
32081fd444aSTakashi Iwai 				group->dir_bits |= (1 << STR_OUT) | (1 << STR_IN);
32181fd444aSTakashi Iwai 				break;
32281fd444aSTakashi Iwai 			}
32381fd444aSTakashi Iwai 			if (!*fb->info.name)
32481fd444aSTakashi Iwai 				continue;
32581fd444aSTakashi Iwai 			if (!*group->name) {
32681fd444aSTakashi Iwai 				/* store the first matching name */
32781fd444aSTakashi Iwai 				strscpy(group->name, fb->info.name,
32881fd444aSTakashi Iwai 					sizeof(group->name));
32981fd444aSTakashi Iwai 			} else {
33081fd444aSTakashi Iwai 				/* when overlapping, concat names */
33181fd444aSTakashi Iwai 				strlcat(group->name, ", ", sizeof(group->name));
33281fd444aSTakashi Iwai 				strlcat(group->name, fb->info.name,
33381fd444aSTakashi Iwai 					sizeof(group->name));
33481fd444aSTakashi Iwai 			}
33581fd444aSTakashi Iwai 		}
33681fd444aSTakashi Iwai 	}
33781fd444aSTakashi Iwai }
33881fd444aSTakashi Iwai 
3394025f0e6STakashi Iwai /* create a UMP Endpoint port */
3404025f0e6STakashi Iwai static int create_ump_endpoint_port(struct seq_ump_client *client)
3414025f0e6STakashi Iwai {
342316e38efSTakashi Iwai 	struct snd_seq_port_info *port __free(kfree) = NULL;
3434025f0e6STakashi Iwai 	struct snd_seq_port_callback pcallbacks;
3444025f0e6STakashi Iwai 	unsigned int rawmidi_info = client->ump->core.info_flags;
3454025f0e6STakashi Iwai 	int err;
3464025f0e6STakashi Iwai 
3474025f0e6STakashi Iwai 	port = kzalloc(sizeof(*port), GFP_KERNEL);
3484025f0e6STakashi Iwai 	if (!port)
3494025f0e6STakashi Iwai 		return -ENOMEM;
3504025f0e6STakashi Iwai 
3514025f0e6STakashi Iwai 	port->addr.client = client->seq_client;
3524025f0e6STakashi Iwai 	port->addr.port = 0; /* fixed */
3534025f0e6STakashi Iwai 	port->flags = SNDRV_SEQ_PORT_FLG_GIVEN_PORT;
3544025f0e6STakashi Iwai 	port->capability = SNDRV_SEQ_PORT_CAP_UMP_ENDPOINT;
3554025f0e6STakashi Iwai 	if (rawmidi_info & SNDRV_RAWMIDI_INFO_INPUT) {
3564025f0e6STakashi Iwai 		port->capability |= SNDRV_SEQ_PORT_CAP_READ |
3574025f0e6STakashi Iwai 			SNDRV_SEQ_PORT_CAP_SYNC_READ |
3584025f0e6STakashi Iwai 			SNDRV_SEQ_PORT_CAP_SUBS_READ;
3594025f0e6STakashi Iwai 		port->direction |= SNDRV_SEQ_PORT_DIR_INPUT;
3604025f0e6STakashi Iwai 	}
3614025f0e6STakashi Iwai 	if (rawmidi_info & SNDRV_RAWMIDI_INFO_OUTPUT) {
3624025f0e6STakashi Iwai 		port->capability |= SNDRV_SEQ_PORT_CAP_WRITE |
3634025f0e6STakashi Iwai 			SNDRV_SEQ_PORT_CAP_SYNC_WRITE |
3644025f0e6STakashi Iwai 			SNDRV_SEQ_PORT_CAP_SUBS_WRITE;
3654025f0e6STakashi Iwai 		port->direction |= SNDRV_SEQ_PORT_DIR_OUTPUT;
3664025f0e6STakashi Iwai 	}
3674025f0e6STakashi Iwai 	if (rawmidi_info & SNDRV_RAWMIDI_INFO_DUPLEX)
3684025f0e6STakashi Iwai 		port->capability |= SNDRV_SEQ_PORT_CAP_DUPLEX;
3694025f0e6STakashi Iwai 	port->ump_group = 0; /* no associated group, no conversion */
3704025f0e6STakashi Iwai 	port->type = SNDRV_SEQ_PORT_TYPE_MIDI_UMP |
3714025f0e6STakashi Iwai 		SNDRV_SEQ_PORT_TYPE_HARDWARE |
3724025f0e6STakashi Iwai 		SNDRV_SEQ_PORT_TYPE_PORT;
3734025f0e6STakashi Iwai 	port->midi_channels = 16;
3744025f0e6STakashi Iwai 	strcpy(port->name, "MIDI 2.0");
3754025f0e6STakashi Iwai 	memset(&pcallbacks, 0, sizeof(pcallbacks));
3764025f0e6STakashi Iwai 	pcallbacks.owner = THIS_MODULE;
3774025f0e6STakashi Iwai 	pcallbacks.private_data = client;
3784025f0e6STakashi Iwai 	if (rawmidi_info & SNDRV_RAWMIDI_INFO_INPUT) {
3794025f0e6STakashi Iwai 		pcallbacks.subscribe = seq_ump_subscribe;
3804025f0e6STakashi Iwai 		pcallbacks.unsubscribe = seq_ump_unsubscribe;
3814025f0e6STakashi Iwai 	}
3824025f0e6STakashi Iwai 	if (rawmidi_info & SNDRV_RAWMIDI_INFO_OUTPUT) {
3834025f0e6STakashi Iwai 		pcallbacks.use = seq_ump_use;
3844025f0e6STakashi Iwai 		pcallbacks.unuse = seq_ump_unuse;
3854025f0e6STakashi Iwai 		pcallbacks.event_input = seq_ump_process_event;
3864025f0e6STakashi Iwai 	}
3874025f0e6STakashi Iwai 	port->kernel = &pcallbacks;
3884025f0e6STakashi Iwai 	err = snd_seq_kernel_client_ctl(client->seq_client,
3894025f0e6STakashi Iwai 					SNDRV_SEQ_IOCTL_CREATE_PORT,
3904025f0e6STakashi Iwai 					port);
3914025f0e6STakashi Iwai 	return err;
3924025f0e6STakashi Iwai }
3934025f0e6STakashi Iwai 
39481fd444aSTakashi Iwai /* release the client resources */
39581fd444aSTakashi Iwai static void seq_ump_client_free(struct seq_ump_client *client)
39681fd444aSTakashi Iwai {
3974a16a3afSTakashi Iwai 	cancel_work_sync(&client->group_notify_work);
3984a16a3afSTakashi Iwai 
39981fd444aSTakashi Iwai 	if (client->seq_client >= 0)
40081fd444aSTakashi Iwai 		snd_seq_delete_kernel_client(client->seq_client);
40181fd444aSTakashi Iwai 
40281fd444aSTakashi Iwai 	client->ump->seq_ops = NULL;
40381fd444aSTakashi Iwai 	client->ump->seq_client = NULL;
40481fd444aSTakashi Iwai 
40581fd444aSTakashi Iwai 	kfree(client);
40681fd444aSTakashi Iwai }
40781fd444aSTakashi Iwai 
40881fd444aSTakashi Iwai /* update the MIDI version for the given client */
40981fd444aSTakashi Iwai static void setup_client_midi_version(struct seq_ump_client *client)
41081fd444aSTakashi Iwai {
41181fd444aSTakashi Iwai 	struct snd_seq_client *cptr;
41281fd444aSTakashi Iwai 
41381fd444aSTakashi Iwai 	cptr = snd_seq_kernel_client_get(client->seq_client);
41481fd444aSTakashi Iwai 	if (!cptr)
41581fd444aSTakashi Iwai 		return;
41681fd444aSTakashi Iwai 	if (client->ump->info.protocol & SNDRV_UMP_EP_INFO_PROTO_MIDI2)
41781fd444aSTakashi Iwai 		cptr->midi_version = SNDRV_SEQ_CLIENT_UMP_MIDI_2_0;
41881fd444aSTakashi Iwai 	else
41981fd444aSTakashi Iwai 		cptr->midi_version = SNDRV_SEQ_CLIENT_UMP_MIDI_1_0;
42081fd444aSTakashi Iwai 	snd_seq_kernel_client_put(cptr);
42181fd444aSTakashi Iwai }
42281fd444aSTakashi Iwai 
42322eefaeaSTakashi Iwai /* set up client's group_filter bitmap */
42422eefaeaSTakashi Iwai static void setup_client_group_filter(struct seq_ump_client *client)
42522eefaeaSTakashi Iwai {
42622eefaeaSTakashi Iwai 	struct snd_seq_client *cptr;
42722eefaeaSTakashi Iwai 	unsigned int filter;
42822eefaeaSTakashi Iwai 	int p;
42922eefaeaSTakashi Iwai 
43022eefaeaSTakashi Iwai 	cptr = snd_seq_kernel_client_get(client->seq_client);
43122eefaeaSTakashi Iwai 	if (!cptr)
43222eefaeaSTakashi Iwai 		return;
43322eefaeaSTakashi Iwai 	filter = ~(1U << 0); /* always allow groupless messages */
43422eefaeaSTakashi Iwai 	for (p = 0; p < SNDRV_UMP_MAX_GROUPS; p++) {
43522eefaeaSTakashi Iwai 		if (client->groups[p].active)
43622eefaeaSTakashi Iwai 			filter &= ~(1U << (p + 1));
43722eefaeaSTakashi Iwai 	}
43822eefaeaSTakashi Iwai 	cptr->group_filter = filter;
43922eefaeaSTakashi Iwai 	snd_seq_kernel_client_put(cptr);
44022eefaeaSTakashi Iwai }
44122eefaeaSTakashi Iwai 
4424a16a3afSTakashi Iwai /* UMP group change notification */
4434a16a3afSTakashi Iwai static void handle_group_notify(struct work_struct *work)
4444a16a3afSTakashi Iwai {
4454a16a3afSTakashi Iwai 	struct seq_ump_client *client =
4464a16a3afSTakashi Iwai 		container_of(work, struct seq_ump_client, group_notify_work);
4474a16a3afSTakashi Iwai 
4484a16a3afSTakashi Iwai 	update_group_attrs(client);
4494a16a3afSTakashi Iwai 	update_port_infos(client);
45022eefaeaSTakashi Iwai 	setup_client_group_filter(client);
4514a16a3afSTakashi Iwai }
4524a16a3afSTakashi Iwai 
4534a16a3afSTakashi Iwai /* UMP FB change notification */
4544a16a3afSTakashi Iwai static int seq_ump_notify_fb_change(struct snd_ump_endpoint *ump,
4554a16a3afSTakashi Iwai 				    struct snd_ump_block *fb)
4564a16a3afSTakashi Iwai {
4574a16a3afSTakashi Iwai 	struct seq_ump_client *client = ump->seq_client;
4584a16a3afSTakashi Iwai 
4594a16a3afSTakashi Iwai 	if (!client)
4604a16a3afSTakashi Iwai 		return -ENODEV;
4614a16a3afSTakashi Iwai 	schedule_work(&client->group_notify_work);
4624a16a3afSTakashi Iwai 	return 0;
4634a16a3afSTakashi Iwai }
4644a16a3afSTakashi Iwai 
4656a8b4800STakashi Iwai /* UMP protocol change notification; just update the midi_version field */
4666a8b4800STakashi Iwai static int seq_ump_switch_protocol(struct snd_ump_endpoint *ump)
4676a8b4800STakashi Iwai {
4686a8b4800STakashi Iwai 	if (!ump->seq_client)
4696a8b4800STakashi Iwai 		return -ENODEV;
4706a8b4800STakashi Iwai 	setup_client_midi_version(ump->seq_client);
4716a8b4800STakashi Iwai 	return 0;
4726a8b4800STakashi Iwai }
4736a8b4800STakashi Iwai 
47481fd444aSTakashi Iwai static const struct snd_seq_ump_ops seq_ump_ops = {
47581fd444aSTakashi Iwai 	.input_receive = seq_ump_input_receive,
4764a16a3afSTakashi Iwai 	.notify_fb_change = seq_ump_notify_fb_change,
4776a8b4800STakashi Iwai 	.switch_protocol = seq_ump_switch_protocol,
47881fd444aSTakashi Iwai };
47981fd444aSTakashi Iwai 
48081fd444aSTakashi Iwai /* create a sequencer client and ports for the given UMP endpoint */
48181fd444aSTakashi Iwai static int snd_seq_ump_probe(struct device *_dev)
48281fd444aSTakashi Iwai {
48381fd444aSTakashi Iwai 	struct snd_seq_device *dev = to_seq_dev(_dev);
48481fd444aSTakashi Iwai 	struct snd_ump_endpoint *ump = dev->private_data;
48581fd444aSTakashi Iwai 	struct snd_card *card = dev->card;
48681fd444aSTakashi Iwai 	struct seq_ump_client *client;
487d2d247e3STakashi Iwai 	struct snd_ump_block *fb;
488d2d247e3STakashi Iwai 	struct snd_seq_client *cptr;
48981fd444aSTakashi Iwai 	int p, err;
49081fd444aSTakashi Iwai 
49181fd444aSTakashi Iwai 	client = kzalloc(sizeof(*client), GFP_KERNEL);
49281fd444aSTakashi Iwai 	if (!client)
49381fd444aSTakashi Iwai 		return -ENOMEM;
49481fd444aSTakashi Iwai 
4954a16a3afSTakashi Iwai 	INIT_WORK(&client->group_notify_work, handle_group_notify);
49681fd444aSTakashi Iwai 	client->ump = ump;
49781fd444aSTakashi Iwai 
49881fd444aSTakashi Iwai 	client->seq_client =
49981fd444aSTakashi Iwai 		snd_seq_create_kernel_client(card, ump->core.device,
50081fd444aSTakashi Iwai 					     ump->core.name);
50181fd444aSTakashi Iwai 	if (client->seq_client < 0) {
50281fd444aSTakashi Iwai 		err = client->seq_client;
50381fd444aSTakashi Iwai 		goto error;
50481fd444aSTakashi Iwai 	}
50581fd444aSTakashi Iwai 
506d2d247e3STakashi Iwai 	client->ump_info[0] = &ump->info;
507d2d247e3STakashi Iwai 	list_for_each_entry(fb, &ump->block_list, list)
508d2d247e3STakashi Iwai 		client->ump_info[fb->info.block_id + 1] = &fb->info;
509d2d247e3STakashi Iwai 
51081fd444aSTakashi Iwai 	setup_client_midi_version(client);
51181fd444aSTakashi Iwai 	update_group_attrs(client);
51281fd444aSTakashi Iwai 
51381fd444aSTakashi Iwai 	for (p = 0; p < SNDRV_UMP_MAX_GROUPS; p++) {
51481fd444aSTakashi Iwai 		err = seq_ump_group_init(client, p);
51581fd444aSTakashi Iwai 		if (err < 0)
51681fd444aSTakashi Iwai 			goto error;
51781fd444aSTakashi Iwai 	}
51881fd444aSTakashi Iwai 
51922eefaeaSTakashi Iwai 	setup_client_group_filter(client);
52022eefaeaSTakashi Iwai 
5214025f0e6STakashi Iwai 	err = create_ump_endpoint_port(client);
5224025f0e6STakashi Iwai 	if (err < 0)
5234025f0e6STakashi Iwai 		goto error;
5244025f0e6STakashi Iwai 
525d2d247e3STakashi Iwai 	cptr = snd_seq_kernel_client_get(client->seq_client);
526d2d247e3STakashi Iwai 	if (!cptr) {
527d2d247e3STakashi Iwai 		err = -EINVAL;
528d2d247e3STakashi Iwai 		goto error;
529d2d247e3STakashi Iwai 	}
530d2d247e3STakashi Iwai 	cptr->ump_info = client->ump_info;
531d2d247e3STakashi Iwai 	snd_seq_kernel_client_put(cptr);
532d2d247e3STakashi Iwai 
53381fd444aSTakashi Iwai 	ump->seq_client = client;
53481fd444aSTakashi Iwai 	ump->seq_ops = &seq_ump_ops;
53581fd444aSTakashi Iwai 	return 0;
53681fd444aSTakashi Iwai 
53781fd444aSTakashi Iwai  error:
53881fd444aSTakashi Iwai 	seq_ump_client_free(client);
53981fd444aSTakashi Iwai 	return err;
54081fd444aSTakashi Iwai }
54181fd444aSTakashi Iwai 
54281fd444aSTakashi Iwai /* remove a sequencer client */
54381fd444aSTakashi Iwai static int snd_seq_ump_remove(struct device *_dev)
54481fd444aSTakashi Iwai {
54581fd444aSTakashi Iwai 	struct snd_seq_device *dev = to_seq_dev(_dev);
54681fd444aSTakashi Iwai 	struct snd_ump_endpoint *ump = dev->private_data;
54781fd444aSTakashi Iwai 
54881fd444aSTakashi Iwai 	if (ump->seq_client)
54981fd444aSTakashi Iwai 		seq_ump_client_free(ump->seq_client);
55081fd444aSTakashi Iwai 	return 0;
55181fd444aSTakashi Iwai }
55281fd444aSTakashi Iwai 
55381fd444aSTakashi Iwai static struct snd_seq_driver seq_ump_driver = {
55481fd444aSTakashi Iwai 	.driver = {
55581fd444aSTakashi Iwai 		.name = KBUILD_MODNAME,
55681fd444aSTakashi Iwai 		.probe = snd_seq_ump_probe,
55781fd444aSTakashi Iwai 		.remove = snd_seq_ump_remove,
55881fd444aSTakashi Iwai 	},
55981fd444aSTakashi Iwai 	.id = SNDRV_SEQ_DEV_ID_UMP,
56081fd444aSTakashi Iwai 	.argsize = 0,
56181fd444aSTakashi Iwai };
56281fd444aSTakashi Iwai 
56381fd444aSTakashi Iwai module_snd_seq_driver(seq_ump_driver);
56481fd444aSTakashi Iwai 
56581fd444aSTakashi Iwai MODULE_DESCRIPTION("ALSA sequencer client for UMP rawmidi");
56681fd444aSTakashi Iwai MODULE_LICENSE("GPL");
567