~ [ source navigation ] ~ [ diff markup ] ~ [ identifier search ] ~

TOMOYO Linux Cross Reference
Linux/sound/core/seq/seq_ump_client.c

Version: ~ [ linux-6.11.5 ] ~ [ linux-6.10.14 ] ~ [ linux-6.9.12 ] ~ [ linux-6.8.12 ] ~ [ linux-6.7.12 ] ~ [ linux-6.6.58 ] ~ [ linux-6.5.13 ] ~ [ linux-6.4.16 ] ~ [ linux-6.3.13 ] ~ [ linux-6.2.16 ] ~ [ linux-6.1.114 ] ~ [ linux-6.0.19 ] ~ [ linux-5.19.17 ] ~ [ linux-5.18.19 ] ~ [ linux-5.17.15 ] ~ [ linux-5.16.20 ] ~ [ linux-5.15.169 ] ~ [ linux-5.14.21 ] ~ [ linux-5.13.19 ] ~ [ linux-5.12.19 ] ~ [ linux-5.11.22 ] ~ [ linux-5.10.228 ] ~ [ linux-5.9.16 ] ~ [ linux-5.8.18 ] ~ [ linux-5.7.19 ] ~ [ linux-5.6.19 ] ~ [ linux-5.5.19 ] ~ [ linux-5.4.284 ] ~ [ linux-5.3.18 ] ~ [ linux-5.2.21 ] ~ [ linux-5.1.21 ] ~ [ linux-5.0.21 ] ~ [ linux-4.20.17 ] ~ [ linux-4.19.322 ] ~ [ linux-4.18.20 ] ~ [ linux-4.17.19 ] ~ [ linux-4.16.18 ] ~ [ linux-4.15.18 ] ~ [ linux-4.14.336 ] ~ [ linux-4.13.16 ] ~ [ linux-4.12.14 ] ~ [ linux-4.11.12 ] ~ [ linux-4.10.17 ] ~ [ linux-4.9.337 ] ~ [ linux-4.4.302 ] ~ [ linux-3.10.108 ] ~ [ linux-2.6.32.71 ] ~ [ linux-2.6.0 ] ~ [ linux-2.4.37.11 ] ~ [ unix-v6-master ] ~ [ ccs-tools-1.8.9 ] ~ [ policy-sample ] ~
Architecture: ~ [ i386 ] ~ [ alpha ] ~ [ m68k ] ~ [ mips ] ~ [ ppc ] ~ [ sparc ] ~ [ sparc64 ] ~

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

~ [ source navigation ] ~ [ diff markup ] ~ [ identifier search ] ~

kernel.org | git.kernel.org | LWN.net | Project Home | SVN repository | Mail admin

Linux® is a registered trademark of Linus Torvalds in the United States and other countries.
TOMOYO® is a registered trademark of NTT DATA CORPORATION.

sflogo.php