projects/linux·blob master · refs

556 lines · 15.5 KiB

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