File Index
Files and directories
Sign in
FILE

seq_ump_client.c

File preview with syntax highlighting.

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