Loading...
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_info *port __free(kfree) = NULL;
218 struct snd_seq_port_callback pcallbacks;
219
220 if (skip_group(client, group))
221 return 0;
222
223 port = 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 struct snd_seq_port_info *old __free(kfree) = NULL;
247 struct snd_seq_port_info *new __free(kfree) = NULL;
248 int i, err;
249
250 old = kzalloc(sizeof(*old), GFP_KERNEL);
251 new = 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 /* notify to system port */
276 snd_seq_system_client_ev_port_change(client->seq_client, i);
277 }
278}
279
280/* create a UMP Endpoint port */
281static int create_ump_endpoint_port(struct seq_ump_client *client)
282{
283 struct snd_seq_port_info *port __free(kfree) = NULL;
284 struct snd_seq_port_callback pcallbacks;
285 unsigned int rawmidi_info = client->ump->core.info_flags;
286 int err;
287
288 port = kzalloc(sizeof(*port), GFP_KERNEL);
289 if (!port)
290 return -ENOMEM;
291
292 port->addr.client = client->seq_client;
293 port->addr.port = 0; /* fixed */
294 port->flags = SNDRV_SEQ_PORT_FLG_GIVEN_PORT;
295 port->capability = SNDRV_SEQ_PORT_CAP_UMP_ENDPOINT;
296 if (rawmidi_info & SNDRV_RAWMIDI_INFO_INPUT) {
297 port->capability |= SNDRV_SEQ_PORT_CAP_READ |
298 SNDRV_SEQ_PORT_CAP_SYNC_READ |
299 SNDRV_SEQ_PORT_CAP_SUBS_READ;
300 port->direction |= SNDRV_SEQ_PORT_DIR_INPUT;
301 }
302 if (rawmidi_info & SNDRV_RAWMIDI_INFO_OUTPUT) {
303 port->capability |= SNDRV_SEQ_PORT_CAP_WRITE |
304 SNDRV_SEQ_PORT_CAP_SYNC_WRITE |
305 SNDRV_SEQ_PORT_CAP_SUBS_WRITE;
306 port->direction |= SNDRV_SEQ_PORT_DIR_OUTPUT;
307 }
308 if (rawmidi_info & SNDRV_RAWMIDI_INFO_DUPLEX)
309 port->capability |= SNDRV_SEQ_PORT_CAP_DUPLEX;
310 port->ump_group = 0; /* no associated group, no conversion */
311 port->type = SNDRV_SEQ_PORT_TYPE_MIDI_UMP |
312 SNDRV_SEQ_PORT_TYPE_HARDWARE |
313 SNDRV_SEQ_PORT_TYPE_PORT;
314 port->midi_channels = 16;
315 strcpy(port->name, "MIDI 2.0");
316 memset(&pcallbacks, 0, sizeof(pcallbacks));
317 pcallbacks.owner = THIS_MODULE;
318 pcallbacks.private_data = client;
319 if (rawmidi_info & SNDRV_RAWMIDI_INFO_INPUT) {
320 pcallbacks.subscribe = seq_ump_subscribe;
321 pcallbacks.unsubscribe = seq_ump_unsubscribe;
322 }
323 if (rawmidi_info & SNDRV_RAWMIDI_INFO_OUTPUT) {
324 pcallbacks.use = seq_ump_use;
325 pcallbacks.unuse = seq_ump_unuse;
326 pcallbacks.event_input = seq_ump_process_event;
327 }
328 port->kernel = &pcallbacks;
329 err = snd_seq_kernel_client_ctl(client->seq_client,
330 SNDRV_SEQ_IOCTL_CREATE_PORT,
331 port);
332 return err;
333}
334
335/* release the client resources */
336static void seq_ump_client_free(struct seq_ump_client *client)
337{
338 cancel_work_sync(&client->group_notify_work);
339
340 if (client->seq_client >= 0)
341 snd_seq_delete_kernel_client(client->seq_client);
342
343 client->ump->seq_ops = NULL;
344 client->ump->seq_client = NULL;
345
346 kfree(client);
347}
348
349/* update the MIDI version for the given client */
350static void setup_client_midi_version(struct seq_ump_client *client)
351{
352 struct snd_seq_client *cptr;
353
354 cptr = snd_seq_kernel_client_get(client->seq_client);
355 if (!cptr)
356 return;
357 if (client->ump->info.protocol & SNDRV_UMP_EP_INFO_PROTO_MIDI2)
358 cptr->midi_version = SNDRV_SEQ_CLIENT_UMP_MIDI_2_0;
359 else
360 cptr->midi_version = SNDRV_SEQ_CLIENT_UMP_MIDI_1_0;
361 snd_seq_kernel_client_put(cptr);
362}
363
364/* set up client's group_filter bitmap */
365static void setup_client_group_filter(struct seq_ump_client *client)
366{
367 struct snd_seq_client *cptr;
368 unsigned int filter;
369 int p;
370
371 cptr = snd_seq_kernel_client_get(client->seq_client);
372 if (!cptr)
373 return;
374 filter = ~(1U << 0); /* always allow groupless messages */
375 for (p = 0; p < SNDRV_UMP_MAX_GROUPS; p++) {
376 if (client->ump->groups[p].active)
377 filter &= ~(1U << (p + 1));
378 }
379 cptr->group_filter = filter;
380 snd_seq_kernel_client_put(cptr);
381}
382
383/* UMP group change notification */
384static void handle_group_notify(struct work_struct *work)
385{
386 struct seq_ump_client *client =
387 container_of(work, struct seq_ump_client, group_notify_work);
388
389 update_port_infos(client);
390 setup_client_group_filter(client);
391}
392
393/* UMP FB change notification */
394static int seq_ump_notify_fb_change(struct snd_ump_endpoint *ump,
395 struct snd_ump_block *fb)
396{
397 struct seq_ump_client *client = ump->seq_client;
398
399 if (!client)
400 return -ENODEV;
401 schedule_work(&client->group_notify_work);
402 return 0;
403}
404
405/* UMP protocol change notification; just update the midi_version field */
406static int seq_ump_switch_protocol(struct snd_ump_endpoint *ump)
407{
408 if (!ump->seq_client)
409 return -ENODEV;
410 setup_client_midi_version(ump->seq_client);
411 return 0;
412}
413
414static const struct snd_seq_ump_ops seq_ump_ops = {
415 .input_receive = seq_ump_input_receive,
416 .notify_fb_change = seq_ump_notify_fb_change,
417 .switch_protocol = seq_ump_switch_protocol,
418};
419
420/* create a sequencer client and ports for the given UMP endpoint */
421static int snd_seq_ump_probe(struct device *_dev)
422{
423 struct snd_seq_device *dev = to_seq_dev(_dev);
424 struct snd_ump_endpoint *ump = dev->private_data;
425 struct snd_card *card = dev->card;
426 struct seq_ump_client *client;
427 struct snd_ump_block *fb;
428 struct snd_seq_client *cptr;
429 int p, err;
430
431 client = kzalloc(sizeof(*client), GFP_KERNEL);
432 if (!client)
433 return -ENOMEM;
434
435 INIT_WORK(&client->group_notify_work, handle_group_notify);
436 client->ump = ump;
437
438 client->seq_client =
439 snd_seq_create_kernel_client(card, ump->core.device,
440 ump->core.name);
441 if (client->seq_client < 0) {
442 err = client->seq_client;
443 goto error;
444 }
445
446 client->ump_info[0] = &ump->info;
447 list_for_each_entry(fb, &ump->block_list, list)
448 client->ump_info[fb->info.block_id + 1] = &fb->info;
449
450 setup_client_midi_version(client);
451
452 for (p = 0; p < SNDRV_UMP_MAX_GROUPS; p++) {
453 err = seq_ump_group_init(client, p);
454 if (err < 0)
455 goto error;
456 }
457
458 setup_client_group_filter(client);
459
460 err = create_ump_endpoint_port(client);
461 if (err < 0)
462 goto error;
463
464 cptr = snd_seq_kernel_client_get(client->seq_client);
465 if (!cptr) {
466 err = -EINVAL;
467 goto error;
468 }
469 cptr->ump_info = client->ump_info;
470 snd_seq_kernel_client_put(cptr);
471
472 ump->seq_client = client;
473 ump->seq_ops = &seq_ump_ops;
474 return 0;
475
476 error:
477 seq_ump_client_free(client);
478 return err;
479}
480
481/* remove a sequencer client */
482static int snd_seq_ump_remove(struct device *_dev)
483{
484 struct snd_seq_device *dev = to_seq_dev(_dev);
485 struct snd_ump_endpoint *ump = dev->private_data;
486
487 if (ump->seq_client)
488 seq_ump_client_free(ump->seq_client);
489 return 0;
490}
491
492static struct snd_seq_driver seq_ump_driver = {
493 .driver = {
494 .name = KBUILD_MODNAME,
495 .probe = snd_seq_ump_probe,
496 .remove = snd_seq_ump_remove,
497 },
498 .id = SNDRV_SEQ_DEV_ID_UMP,
499 .argsize = 0,
500};
501
502module_snd_seq_driver(seq_ump_driver);
503
504MODULE_DESCRIPTION("ALSA sequencer client for UMP rawmidi");
505MODULE_LICENSE("GPL");