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(group: ump_message_group(data: *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: client->seq_client,
86 ev: (struct snd_seq_event *)&ev,
87 atomic: true, hop: 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(data: 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, buf: ev->data.raw8.d, count: 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;
119
120 guard(mutex)(T: &ump->open_mutex);
121 if (dir == STR_OUT && !client->opened[dir]) {
122 err = snd_rawmidi_kernel_open(rmidi: &ump->core, subdevice: 0,
123 SNDRV_RAWMIDI_LFLG_OUTPUT |
124 SNDRV_RAWMIDI_LFLG_APPEND,
125 rfile: &client->out_rfile);
126 if (err < 0)
127 return err;
128 }
129 client->opened[dir]++;
130 return 0;
131}
132
133/* close the rawmidi */
134static int seq_ump_client_close(struct seq_ump_client *client, int dir)
135{
136 struct snd_ump_endpoint *ump = client->ump;
137
138 guard(mutex)(T: &ump->open_mutex);
139 if (!--client->opened[dir])
140 if (dir == STR_OUT)
141 snd_rawmidi_kernel_release(rfile: &client->out_rfile);
142 return 0;
143}
144
145/* sequencer subscription ops for each client */
146static int seq_ump_subscribe(void *pdata, struct snd_seq_port_subscribe *info)
147{
148 struct seq_ump_client *client = pdata;
149
150 return seq_ump_client_open(client, dir: STR_IN);
151}
152
153static int seq_ump_unsubscribe(void *pdata, struct snd_seq_port_subscribe *info)
154{
155 struct seq_ump_client *client = pdata;
156
157 return seq_ump_client_close(client, dir: STR_IN);
158}
159
160static int seq_ump_use(void *pdata, struct snd_seq_port_subscribe *info)
161{
162 struct seq_ump_client *client = pdata;
163
164 return seq_ump_client_open(client, dir: STR_OUT);
165}
166
167static int seq_ump_unuse(void *pdata, struct snd_seq_port_subscribe *info)
168{
169 struct seq_ump_client *client = pdata;
170
171 return seq_ump_client_close(client, dir: STR_OUT);
172}
173
174/* fill port_info from the given UMP EP and group info */
175static void fill_port_info(struct snd_seq_port_info *port,
176 struct seq_ump_client *client,
177 struct seq_ump_group *group)
178{
179 unsigned int rawmidi_info = client->ump->core.info_flags;
180
181 port->addr.client = client->seq_client;
182 port->addr.port = ump_group_to_seq_port(group: group->group);
183 port->capability = 0;
184 if (rawmidi_info & SNDRV_RAWMIDI_INFO_OUTPUT)
185 port->capability |= SNDRV_SEQ_PORT_CAP_WRITE |
186 SNDRV_SEQ_PORT_CAP_SYNC_WRITE |
187 SNDRV_SEQ_PORT_CAP_SUBS_WRITE;
188 if (rawmidi_info & SNDRV_RAWMIDI_INFO_INPUT)
189 port->capability |= SNDRV_SEQ_PORT_CAP_READ |
190 SNDRV_SEQ_PORT_CAP_SYNC_READ |
191 SNDRV_SEQ_PORT_CAP_SUBS_READ;
192 if (rawmidi_info & SNDRV_RAWMIDI_INFO_DUPLEX)
193 port->capability |= SNDRV_SEQ_PORT_CAP_DUPLEX;
194 if (group->dir_bits & (1 << STR_IN))
195 port->direction |= SNDRV_SEQ_PORT_DIR_INPUT;
196 if (group->dir_bits & (1 << STR_OUT))
197 port->direction |= SNDRV_SEQ_PORT_DIR_OUTPUT;
198 port->ump_group = group->group + 1;
199 if (!group->active)
200 port->capability |= SNDRV_SEQ_PORT_CAP_INACTIVE;
201 port->type = SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC |
202 SNDRV_SEQ_PORT_TYPE_MIDI_UMP |
203 SNDRV_SEQ_PORT_TYPE_HARDWARE |
204 SNDRV_SEQ_PORT_TYPE_PORT;
205 port->midi_channels = 16;
206 if (*group->name)
207 snprintf(buf: port->name, size: sizeof(port->name), fmt: "Group %d (%.53s)",
208 group->group + 1, group->name);
209 else
210 sprintf(buf: port->name, fmt: "Group %d", group->group + 1);
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 seq_ump_group *group = &client->groups[group_index];
217 struct snd_seq_port_info *port __free(kfree) = NULL;
218 struct snd_seq_port_callback pcallbacks;
219
220 port = kzalloc(size: sizeof(*port), GFP_KERNEL);
221 if (!port)
222 return -ENOMEM;
223
224 fill_port_info(port, client, group);
225 port->flags = SNDRV_SEQ_PORT_FLG_GIVEN_PORT;
226 memset(&pcallbacks, 0, sizeof(pcallbacks));
227 pcallbacks.owner = THIS_MODULE;
228 pcallbacks.private_data = client;
229 pcallbacks.subscribe = seq_ump_subscribe;
230 pcallbacks.unsubscribe = seq_ump_unsubscribe;
231 pcallbacks.use = seq_ump_use;
232 pcallbacks.unuse = seq_ump_unuse;
233 pcallbacks.event_input = seq_ump_process_event;
234 port->kernel = &pcallbacks;
235 return snd_seq_kernel_client_ctl(client: client->seq_client,
236 SNDRV_SEQ_IOCTL_CREATE_PORT,
237 arg: port);
238}
239
240/* update the sequencer ports; called from notify_fb_change callback */
241static void update_port_infos(struct seq_ump_client *client)
242{
243 struct snd_seq_port_info *old __free(kfree) = NULL;
244 struct snd_seq_port_info *new __free(kfree) = NULL;
245 int i, err;
246
247 old = kzalloc(size: sizeof(*old), GFP_KERNEL);
248 new = kzalloc(size: sizeof(*new), GFP_KERNEL);
249 if (!old || !new)
250 return;
251
252 for (i = 0; i < SNDRV_UMP_MAX_GROUPS; i++) {
253 old->addr.client = client->seq_client;
254 old->addr.port = i;
255 err = snd_seq_kernel_client_ctl(client: client->seq_client,
256 SNDRV_SEQ_IOCTL_GET_PORT_INFO,
257 arg: old);
258 if (err < 0)
259 return;
260 fill_port_info(port: new, client, group: &client->groups[i]);
261 if (old->capability == new->capability &&
262 !strcmp(old->name, new->name))
263 continue;
264 err = snd_seq_kernel_client_ctl(client: client->seq_client,
265 SNDRV_SEQ_IOCTL_SET_PORT_INFO,
266 arg: new);
267 if (err < 0)
268 return;
269 /* notify to system port */
270 snd_seq_system_client_ev_port_change(client->seq_client, i);
271 }
272}
273
274/* update dir_bits and active flag for all groups in the client */
275static void update_group_attrs(struct seq_ump_client *client)
276{
277 struct snd_ump_block *fb;
278 struct seq_ump_group *group;
279 int i;
280
281 for (i = 0; i < SNDRV_UMP_MAX_GROUPS; i++) {
282 group = &client->groups[i];
283 *group->name = 0;
284 group->dir_bits = 0;
285 group->active = 0;
286 group->group = i;
287 }
288
289 list_for_each_entry(fb, &client->ump->block_list, list) {
290 if (fb->info.first_group + fb->info.num_groups > SNDRV_UMP_MAX_GROUPS)
291 break;
292 group = &client->groups[fb->info.first_group];
293 for (i = 0; i < fb->info.num_groups; i++, group++) {
294 if (fb->info.active)
295 group->active = 1;
296 switch (fb->info.direction) {
297 case SNDRV_UMP_DIR_INPUT:
298 group->dir_bits |= (1 << STR_IN);
299 break;
300 case SNDRV_UMP_DIR_OUTPUT:
301 group->dir_bits |= (1 << STR_OUT);
302 break;
303 case SNDRV_UMP_DIR_BIDIRECTION:
304 group->dir_bits |= (1 << STR_OUT) | (1 << STR_IN);
305 break;
306 }
307 if (!*fb->info.name)
308 continue;
309 if (!*group->name) {
310 /* store the first matching name */
311 strscpy(group->name, fb->info.name,
312 sizeof(group->name));
313 } else {
314 /* when overlapping, concat names */
315 strlcat(p: group->name, q: ", ", avail: sizeof(group->name));
316 strlcat(p: group->name, q: fb->info.name,
317 avail: sizeof(group->name));
318 }
319 }
320 }
321}
322
323/* create a UMP Endpoint port */
324static int create_ump_endpoint_port(struct seq_ump_client *client)
325{
326 struct snd_seq_port_info *port __free(kfree) = NULL;
327 struct snd_seq_port_callback pcallbacks;
328 unsigned int rawmidi_info = client->ump->core.info_flags;
329 int err;
330
331 port = kzalloc(size: sizeof(*port), GFP_KERNEL);
332 if (!port)
333 return -ENOMEM;
334
335 port->addr.client = client->seq_client;
336 port->addr.port = 0; /* fixed */
337 port->flags = SNDRV_SEQ_PORT_FLG_GIVEN_PORT;
338 port->capability = SNDRV_SEQ_PORT_CAP_UMP_ENDPOINT;
339 if (rawmidi_info & SNDRV_RAWMIDI_INFO_INPUT) {
340 port->capability |= SNDRV_SEQ_PORT_CAP_READ |
341 SNDRV_SEQ_PORT_CAP_SYNC_READ |
342 SNDRV_SEQ_PORT_CAP_SUBS_READ;
343 port->direction |= SNDRV_SEQ_PORT_DIR_INPUT;
344 }
345 if (rawmidi_info & SNDRV_RAWMIDI_INFO_OUTPUT) {
346 port->capability |= SNDRV_SEQ_PORT_CAP_WRITE |
347 SNDRV_SEQ_PORT_CAP_SYNC_WRITE |
348 SNDRV_SEQ_PORT_CAP_SUBS_WRITE;
349 port->direction |= SNDRV_SEQ_PORT_DIR_OUTPUT;
350 }
351 if (rawmidi_info & SNDRV_RAWMIDI_INFO_DUPLEX)
352 port->capability |= SNDRV_SEQ_PORT_CAP_DUPLEX;
353 port->ump_group = 0; /* no associated group, no conversion */
354 port->type = SNDRV_SEQ_PORT_TYPE_MIDI_UMP |
355 SNDRV_SEQ_PORT_TYPE_HARDWARE |
356 SNDRV_SEQ_PORT_TYPE_PORT;
357 port->midi_channels = 16;
358 strcpy(p: port->name, q: "MIDI 2.0");
359 memset(&pcallbacks, 0, sizeof(pcallbacks));
360 pcallbacks.owner = THIS_MODULE;
361 pcallbacks.private_data = client;
362 if (rawmidi_info & SNDRV_RAWMIDI_INFO_INPUT) {
363 pcallbacks.subscribe = seq_ump_subscribe;
364 pcallbacks.unsubscribe = seq_ump_unsubscribe;
365 }
366 if (rawmidi_info & SNDRV_RAWMIDI_INFO_OUTPUT) {
367 pcallbacks.use = seq_ump_use;
368 pcallbacks.unuse = seq_ump_unuse;
369 pcallbacks.event_input = seq_ump_process_event;
370 }
371 port->kernel = &pcallbacks;
372 err = snd_seq_kernel_client_ctl(client: client->seq_client,
373 SNDRV_SEQ_IOCTL_CREATE_PORT,
374 arg: port);
375 return err;
376}
377
378/* release the client resources */
379static void seq_ump_client_free(struct seq_ump_client *client)
380{
381 cancel_work_sync(work: &client->group_notify_work);
382
383 if (client->seq_client >= 0)
384 snd_seq_delete_kernel_client(client: client->seq_client);
385
386 client->ump->seq_ops = NULL;
387 client->ump->seq_client = NULL;
388
389 kfree(objp: client);
390}
391
392/* update the MIDI version for the given client */
393static void setup_client_midi_version(struct seq_ump_client *client)
394{
395 struct snd_seq_client *cptr;
396
397 cptr = snd_seq_kernel_client_get(client: client->seq_client);
398 if (!cptr)
399 return;
400 if (client->ump->info.protocol & SNDRV_UMP_EP_INFO_PROTO_MIDI2)
401 cptr->midi_version = SNDRV_SEQ_CLIENT_UMP_MIDI_2_0;
402 else
403 cptr->midi_version = SNDRV_SEQ_CLIENT_UMP_MIDI_1_0;
404 snd_seq_kernel_client_put(cptr);
405}
406
407/* set up client's group_filter bitmap */
408static void setup_client_group_filter(struct seq_ump_client *client)
409{
410 struct snd_seq_client *cptr;
411 unsigned int filter;
412 int p;
413
414 cptr = snd_seq_kernel_client_get(client: client->seq_client);
415 if (!cptr)
416 return;
417 filter = ~(1U << 0); /* always allow groupless messages */
418 for (p = 0; p < SNDRV_UMP_MAX_GROUPS; p++) {
419 if (client->groups[p].active)
420 filter &= ~(1U << (p + 1));
421 }
422 cptr->group_filter = filter;
423 snd_seq_kernel_client_put(cptr);
424}
425
426/* UMP group change notification */
427static void handle_group_notify(struct work_struct *work)
428{
429 struct seq_ump_client *client =
430 container_of(work, struct seq_ump_client, group_notify_work);
431
432 update_group_attrs(client);
433 update_port_infos(client);
434 setup_client_group_filter(client);
435}
436
437/* UMP FB change notification */
438static int seq_ump_notify_fb_change(struct snd_ump_endpoint *ump,
439 struct snd_ump_block *fb)
440{
441 struct seq_ump_client *client = ump->seq_client;
442
443 if (!client)
444 return -ENODEV;
445 schedule_work(work: &client->group_notify_work);
446 return 0;
447}
448
449/* UMP protocol change notification; just update the midi_version field */
450static int seq_ump_switch_protocol(struct snd_ump_endpoint *ump)
451{
452 if (!ump->seq_client)
453 return -ENODEV;
454 setup_client_midi_version(ump->seq_client);
455 return 0;
456}
457
458static const struct snd_seq_ump_ops seq_ump_ops = {
459 .input_receive = seq_ump_input_receive,
460 .notify_fb_change = seq_ump_notify_fb_change,
461 .switch_protocol = seq_ump_switch_protocol,
462};
463
464/* create a sequencer client and ports for the given UMP endpoint */
465static int snd_seq_ump_probe(struct device *_dev)
466{
467 struct snd_seq_device *dev = to_seq_dev(_dev);
468 struct snd_ump_endpoint *ump = dev->private_data;
469 struct snd_card *card = dev->card;
470 struct seq_ump_client *client;
471 struct snd_ump_block *fb;
472 struct snd_seq_client *cptr;
473 int p, err;
474
475 client = kzalloc(size: sizeof(*client), GFP_KERNEL);
476 if (!client)
477 return -ENOMEM;
478
479 INIT_WORK(&client->group_notify_work, handle_group_notify);
480 client->ump = ump;
481
482 client->seq_client =
483 snd_seq_create_kernel_client(card, client_index: ump->core.device,
484 name_fmt: ump->core.name);
485 if (client->seq_client < 0) {
486 err = client->seq_client;
487 goto error;
488 }
489
490 client->ump_info[0] = &ump->info;
491 list_for_each_entry(fb, &ump->block_list, list)
492 client->ump_info[fb->info.block_id + 1] = &fb->info;
493
494 setup_client_midi_version(client);
495 update_group_attrs(client);
496
497 for (p = 0; p < SNDRV_UMP_MAX_GROUPS; p++) {
498 err = seq_ump_group_init(client, group_index: p);
499 if (err < 0)
500 goto error;
501 }
502
503 setup_client_group_filter(client);
504
505 err = create_ump_endpoint_port(client);
506 if (err < 0)
507 goto error;
508
509 cptr = snd_seq_kernel_client_get(client: client->seq_client);
510 if (!cptr) {
511 err = -EINVAL;
512 goto error;
513 }
514 cptr->ump_info = client->ump_info;
515 snd_seq_kernel_client_put(cptr);
516
517 ump->seq_client = client;
518 ump->seq_ops = &seq_ump_ops;
519 return 0;
520
521 error:
522 seq_ump_client_free(client);
523 return err;
524}
525
526/* remove a sequencer client */
527static int snd_seq_ump_remove(struct device *_dev)
528{
529 struct snd_seq_device *dev = to_seq_dev(_dev);
530 struct snd_ump_endpoint *ump = dev->private_data;
531
532 if (ump->seq_client)
533 seq_ump_client_free(client: ump->seq_client);
534 return 0;
535}
536
537static struct snd_seq_driver seq_ump_driver = {
538 .driver = {
539 .name = KBUILD_MODNAME,
540 .probe = snd_seq_ump_probe,
541 .remove = snd_seq_ump_remove,
542 },
543 .id = SNDRV_SEQ_DEV_ID_UMP,
544 .argsize = 0,
545};
546
547module_snd_seq_driver(seq_ump_driver);
548
549MODULE_DESCRIPTION("ALSA sequencer client for UMP rawmidi");
550MODULE_LICENSE("GPL");
551

source code of linux/sound/core/seq/seq_ump_client.c