| 1 | |
| 2 | |
| 3 | * Generic MIDI synth driver for ALSA sequencer
|
| 4 | * Copyright (c) 1998 by Frank van de Pol <[email protected]>
|
| 5 | * Jaroslav Kysela <[email protected]>
|
| 6 |
|
| 7 |
|
| 8 | |
| 9 | Possible options for midisynth module:
|
| 10 | - automatic opening of midi ports on first received event or subscription
|
| 11 | (close will be performed when client leaves)
|
| 12 |
|
| 13 |
|
| 14 |
|
| 15 | #include <linux/init.h>
|
| 16 | #include <linux/slab.h>
|
| 17 | #include <linux/errno.h>
|
| 18 | #include <linux/string.h>
|
| 19 | #include <linux/module.h>
|
| 20 | #include <linux/mutex.h>
|
| 21 | #include <sound/core.h>
|
| 22 | #include <sound/rawmidi.h>
|
| 23 | #include <sound/seq_kernel.h>
|
| 24 | #include <sound/seq_device.h>
|
| 25 | #include <sound/seq_midi_event.h>
|
| 26 | #include <sound/initval.h>
|
| 27 | #include "seq_lock.h"
|
| 28 |
|
| 29 | MODULE_AUTHOR("Frank van de Pol <[email protected]>, Jaroslav Kysela <[email protected]>");
|
| 30 | MODULE_DESCRIPTION("Advanced Linux Sound Architecture sequencer MIDI synth.");
|
| 31 | MODULE_LICENSE("GPL");
|
| 32 | static int output_buffer_size = PAGE_SIZE;
|
| 33 | module_param(output_buffer_size, int, 0644);
|
| 34 | MODULE_PARM_DESC(output_buffer_size, "Output buffer size in bytes.");
|
| 35 | static int input_buffer_size = PAGE_SIZE;
|
| 36 | module_param(input_buffer_size, int, 0644);
|
| 37 | MODULE_PARM_DESC(input_buffer_size, "Input buffer size in bytes.");
|
| 38 |
|
| 39 |
|
| 40 | struct seq_midisynth {
|
| 41 | struct snd_card *card;
|
| 42 | struct snd_rawmidi *rmidi;
|
| 43 | int device;
|
| 44 | int subdevice;
|
| 45 | struct snd_rawmidi_substream __rcu *input_substream;
|
| 46 | snd_use_lock_t input_use_lock;
|
| 47 | struct snd_rawmidi_file input_rfile;
|
| 48 | snd_use_lock_t output_use_lock;
|
| 49 | struct snd_rawmidi_substream __rcu *output_substream;
|
| 50 | struct snd_rawmidi_file output_rfile;
|
| 51 | int seq_client;
|
| 52 | int seq_port;
|
| 53 | struct snd_midi_event *parser;
|
| 54 | };
|
| 55 |
|
| 56 | struct seq_midisynth_client {
|
| 57 | int seq_client;
|
| 58 | int num_ports;
|
| 59 | int ports_per_device[SNDRV_RAWMIDI_DEVICES];
|
| 60 | struct seq_midisynth *ports[SNDRV_RAWMIDI_DEVICES];
|
| 61 | };
|
| 62 |
|
| 63 | static struct seq_midisynth_client *synths[SNDRV_CARDS];
|
| 64 | static DEFINE_MUTEX(register_mutex);
|
| 65 |
|
| 66 |
|
| 67 | static void snd_midi_input_event(struct snd_rawmidi_substream *substream)
|
| 68 | {
|
| 69 | struct snd_rawmidi_runtime *runtime;
|
| 70 | struct seq_midisynth *msynth;
|
| 71 | struct snd_seq_event ev;
|
| 72 | char buf[16], *pbuf;
|
| 73 | long res;
|
| 74 |
|
| 75 | if (substream == NULL)
|
| 76 | return;
|
| 77 | runtime = substream->runtime;
|
| 78 | msynth = runtime->private_data;
|
| 79 | if (msynth == NULL)
|
| 80 | return;
|
| 81 |
|
| 82 | scoped_guard(rcu) {
|
| 83 | if (rcu_dereference(msynth->input_substream) != substream)
|
| 84 | return;
|
| 85 |
|
| 86 | snd_use_lock_use(&msynth->input_use_lock);
|
| 87 | }
|
| 88 |
|
| 89 | memset(&ev, 0, sizeof(ev));
|
| 90 | while (runtime->avail > 0) {
|
| 91 | res = snd_rawmidi_kernel_read(substream, buf, sizeof(buf));
|
| 92 | if (res <= 0)
|
| 93 | continue;
|
| 94 | if (msynth->parser == NULL)
|
| 95 | continue;
|
| 96 | pbuf = buf;
|
| 97 | while (res-- > 0) {
|
| 98 | if (!snd_midi_event_encode_byte(msynth->parser,
|
| 99 | *pbuf++, &ev))
|
| 100 | continue;
|
| 101 | ev.source.port = msynth->seq_port;
|
| 102 | ev.dest.client = SNDRV_SEQ_ADDRESS_SUBSCRIBERS;
|
| 103 | snd_seq_kernel_client_dispatch(msynth->seq_client, &ev, 1, 0);
|
| 104 |
|
| 105 | memset(&ev, 0, sizeof(ev));
|
| 106 | }
|
| 107 | }
|
| 108 |
|
| 109 | snd_use_lock_free(&msynth->input_use_lock);
|
| 110 | }
|
| 111 |
|
| 112 | static int dump_midi(struct snd_rawmidi_substream *substream, const char *buf, int count)
|
| 113 | {
|
| 114 | struct snd_rawmidi_runtime *runtime;
|
| 115 | int tmp;
|
| 116 |
|
| 117 | if (snd_BUG_ON(!substream || !buf))
|
| 118 | return -EINVAL;
|
| 119 | runtime = substream->runtime;
|
| 120 | tmp = runtime->avail;
|
| 121 | if (tmp < count) {
|
| 122 | if (printk_ratelimit())
|
| 123 | pr_err("ALSA: seq_midi: MIDI output buffer overrun\n");
|
| 124 | return -ENOMEM;
|
| 125 | }
|
| 126 | if (snd_rawmidi_kernel_write(substream, buf, count) < count)
|
| 127 | return -EINVAL;
|
| 128 | return 0;
|
| 129 | }
|
| 130 |
|
| 131 |
|
| 132 | static int __dump_midi(void *ptr, void *buf, int count)
|
| 133 | {
|
| 134 | return dump_midi(ptr, buf, count);
|
| 135 | }
|
| 136 |
|
| 137 | static int event_process_midi(struct snd_seq_event *ev, int direct,
|
| 138 | void *private_data, int atomic, int hop)
|
| 139 | {
|
| 140 | struct seq_midisynth *msynth = private_data;
|
| 141 | unsigned char msg[10];
|
| 142 | struct snd_rawmidi_substream *substream;
|
| 143 | int err = 0;
|
| 144 | int len;
|
| 145 |
|
| 146 | if (snd_BUG_ON(!msynth))
|
| 147 | return -EINVAL;
|
| 148 |
|
| 149 | scoped_guard(rcu) {
|
| 150 | substream = rcu_dereference(msynth->output_substream);
|
| 151 | if (!substream)
|
| 152 | return -ENODEV;
|
| 153 | snd_use_lock_use(&msynth->output_use_lock);
|
| 154 | }
|
| 155 |
|
| 156 | if (ev->type == SNDRV_SEQ_EVENT_SYSEX) {
|
| 157 | if ((ev->flags & SNDRV_SEQ_EVENT_LENGTH_MASK) != SNDRV_SEQ_EVENT_LENGTH_VARIABLE) {
|
| 158 |
|
| 159 | pr_debug("ALSA: seq_midi: invalid sysex event flags = 0x%x\n", ev->flags);
|
| 160 | goto out;
|
| 161 | }
|
| 162 | snd_seq_dump_var_event(ev, __dump_midi, substream);
|
| 163 | snd_midi_event_reset_decode(msynth->parser);
|
| 164 | } else {
|
| 165 | if (!msynth->parser) {
|
| 166 | err = -EIO;
|
| 167 | goto out;
|
| 168 | }
|
| 169 | len = snd_midi_event_decode(msynth->parser, msg, sizeof(msg), ev);
|
| 170 | if (len < 0)
|
| 171 | goto out;
|
| 172 | if (dump_midi(substream, msg, len) < 0)
|
| 173 | snd_midi_event_reset_decode(msynth->parser);
|
| 174 | }
|
| 175 |
|
| 176 | out:
|
| 177 | snd_use_lock_free(&msynth->output_use_lock);
|
| 178 | return err;
|
| 179 | }
|
| 180 |
|
| 181 |
|
| 182 | static int snd_seq_midisynth_new(struct seq_midisynth *msynth,
|
| 183 | struct snd_card *card,
|
| 184 | int device,
|
| 185 | int subdevice)
|
| 186 | {
|
| 187 | if (snd_midi_event_new(MAX_MIDI_EVENT_BUF, &msynth->parser) < 0)
|
| 188 | return -ENOMEM;
|
| 189 | msynth->card = card;
|
| 190 | msynth->device = device;
|
| 191 | msynth->subdevice = subdevice;
|
| 192 | snd_use_lock_init(&msynth->input_use_lock);
|
| 193 | snd_use_lock_init(&msynth->output_use_lock);
|
| 194 | return 0;
|
| 195 | }
|
| 196 |
|
| 197 |
|
| 198 | static int midisynth_subscribe(void *private_data, struct snd_seq_port_subscribe *info)
|
| 199 | {
|
| 200 | int err;
|
| 201 | struct seq_midisynth *msynth = private_data;
|
| 202 | struct snd_rawmidi_runtime *runtime;
|
| 203 | struct snd_rawmidi_file rfile = {};
|
| 204 | struct snd_rawmidi_params params;
|
| 205 |
|
| 206 |
|
| 207 | err = snd_rawmidi_kernel_open(msynth->rmidi, msynth->subdevice,
|
| 208 | SNDRV_RAWMIDI_LFLG_INPUT,
|
| 209 | &rfile);
|
| 210 | if (err < 0) {
|
| 211 | pr_debug("ALSA: seq_midi: midi input open failed!!!\n");
|
| 212 | return err;
|
| 213 | }
|
| 214 | runtime = rfile.input->runtime;
|
| 215 | memset(¶ms, 0, sizeof(params));
|
| 216 | params.avail_min = 1;
|
| 217 | params.buffer_size = input_buffer_size;
|
| 218 | err = snd_rawmidi_input_params(rfile.input, ¶ms);
|
| 219 | if (err < 0) {
|
| 220 | snd_rawmidi_kernel_release(&rfile);
|
| 221 | return err;
|
| 222 | }
|
| 223 | snd_midi_event_reset_encode(msynth->parser);
|
| 224 | runtime->event = snd_midi_input_event;
|
| 225 | runtime->private_data = msynth;
|
| 226 | msynth->input_rfile = rfile;
|
| 227 | rcu_assign_pointer(msynth->input_substream, rfile.input);
|
| 228 | snd_rawmidi_kernel_read(msynth->input_rfile.input, NULL, 0);
|
| 229 | return 0;
|
| 230 | }
|
| 231 |
|
| 232 |
|
| 233 | static int midisynth_unsubscribe(void *private_data, struct snd_seq_port_subscribe *info)
|
| 234 | {
|
| 235 | int err;
|
| 236 | struct seq_midisynth *msynth = private_data;
|
| 237 | struct snd_rawmidi_file rfile;
|
| 238 |
|
| 239 | rcu_assign_pointer(msynth->input_substream, NULL);
|
| 240 | synchronize_rcu();
|
| 241 | snd_use_lock_sync(&msynth->input_use_lock);
|
| 242 |
|
| 243 | rfile = msynth->input_rfile;
|
| 244 | msynth->input_rfile = (struct snd_rawmidi_file){};
|
| 245 |
|
| 246 | if (snd_BUG_ON(!rfile.input))
|
| 247 | return -EINVAL;
|
| 248 |
|
| 249 | err = snd_rawmidi_kernel_release(&rfile);
|
| 250 | return err;
|
| 251 | }
|
| 252 |
|
| 253 |
|
| 254 | static int midisynth_use(void *private_data, struct snd_seq_port_subscribe *info)
|
| 255 | {
|
| 256 | int err;
|
| 257 | struct seq_midisynth *msynth = private_data;
|
| 258 | struct snd_rawmidi_file rfile = {};
|
| 259 | struct snd_rawmidi_params params;
|
| 260 |
|
| 261 |
|
| 262 | err = snd_rawmidi_kernel_open(msynth->rmidi, msynth->subdevice,
|
| 263 | SNDRV_RAWMIDI_LFLG_OUTPUT,
|
| 264 | &rfile);
|
| 265 | if (err < 0) {
|
| 266 | pr_debug("ALSA: seq_midi: midi output open failed!!!\n");
|
| 267 | return err;
|
| 268 | }
|
| 269 | memset(¶ms, 0, sizeof(params));
|
| 270 | params.avail_min = 1;
|
| 271 | params.buffer_size = output_buffer_size;
|
| 272 | params.no_active_sensing = 1;
|
| 273 | err = snd_rawmidi_output_params(rfile.output, ¶ms);
|
| 274 | if (err < 0) {
|
| 275 | snd_rawmidi_kernel_release(&rfile);
|
| 276 | return err;
|
| 277 | }
|
| 278 | snd_midi_event_reset_decode(msynth->parser);
|
| 279 | msynth->output_rfile = rfile;
|
| 280 | rcu_assign_pointer(msynth->output_substream, rfile.output);
|
| 281 | return 0;
|
| 282 | }
|
| 283 |
|
| 284 |
|
| 285 | static int midisynth_unuse(void *private_data, struct snd_seq_port_subscribe *info)
|
| 286 | {
|
| 287 | struct seq_midisynth *msynth = private_data;
|
| 288 | struct snd_rawmidi_file rfile;
|
| 289 |
|
| 290 | rcu_assign_pointer(msynth->output_substream, NULL);
|
| 291 | synchronize_rcu();
|
| 292 | snd_use_lock_sync(&msynth->output_use_lock);
|
| 293 | rfile = msynth->output_rfile;
|
| 294 | msynth->output_rfile = (struct snd_rawmidi_file){};
|
| 295 |
|
| 296 | if (snd_BUG_ON(!rfile.output))
|
| 297 | return -EINVAL;
|
| 298 | snd_rawmidi_drain_output(rfile.output);
|
| 299 | return snd_rawmidi_kernel_release(&rfile);
|
| 300 | }
|
| 301 |
|
| 302 |
|
| 303 | static void snd_seq_midisynth_delete(struct seq_midisynth *msynth)
|
| 304 | {
|
| 305 | if (msynth == NULL)
|
| 306 | return;
|
| 307 |
|
| 308 | if (msynth->seq_client > 0) {
|
| 309 |
|
| 310 | snd_seq_event_port_detach(msynth->seq_client, msynth->seq_port);
|
| 311 | }
|
| 312 |
|
| 313 | snd_midi_event_free(msynth->parser);
|
| 314 | }
|
| 315 |
|
| 316 |
|
| 317 | static int
|
| 318 | snd_seq_midisynth_probe(struct snd_seq_device *dev)
|
| 319 | {
|
| 320 | struct seq_midisynth_client *client;
|
| 321 | struct seq_midisynth *msynth, *ms;
|
| 322 | struct snd_rawmidi *rmidi = dev->private_data;
|
| 323 | int newclient = 0;
|
| 324 | unsigned int p, ports;
|
| 325 | struct snd_seq_port_callback pcallbacks;
|
| 326 | struct snd_card *card = dev->card;
|
| 327 | int device = dev->device;
|
| 328 | unsigned int input_count = 0, output_count = 0;
|
| 329 |
|
| 330 | if (snd_BUG_ON(!card || device < 0 || device >= SNDRV_RAWMIDI_DEVICES))
|
| 331 | return -EINVAL;
|
| 332 |
|
| 333 | struct snd_rawmidi_info *info __free(kfree) =
|
| 334 | kmalloc_obj(*info);
|
| 335 | if (! info)
|
| 336 | return -ENOMEM;
|
| 337 | info->device = device;
|
| 338 | info->stream = SNDRV_RAWMIDI_STREAM_OUTPUT;
|
| 339 | info->subdevice = 0;
|
| 340 | if (snd_rawmidi_info_select(card, info) >= 0)
|
| 341 | output_count = info->subdevices_count;
|
| 342 | info->stream = SNDRV_RAWMIDI_STREAM_INPUT;
|
| 343 | if (snd_rawmidi_info_select(card, info) >= 0) {
|
| 344 | input_count = info->subdevices_count;
|
| 345 | }
|
| 346 | ports = output_count;
|
| 347 | if (ports < input_count)
|
| 348 | ports = input_count;
|
| 349 | if (ports == 0)
|
| 350 | return -ENODEV;
|
| 351 | if (ports > (256 / SNDRV_RAWMIDI_DEVICES))
|
| 352 | ports = 256 / SNDRV_RAWMIDI_DEVICES;
|
| 353 |
|
| 354 | guard(mutex)(®ister_mutex);
|
| 355 | client = synths[card->number];
|
| 356 | if (client == NULL) {
|
| 357 | newclient = 1;
|
| 358 | client = kzalloc_obj(*client);
|
| 359 | if (client == NULL)
|
| 360 | return -ENOMEM;
|
| 361 | client->seq_client =
|
| 362 | snd_seq_create_kernel_client(
|
| 363 | card, 0, "%s", card->shortname[0] ?
|
| 364 | (const char *)card->shortname : "External MIDI");
|
| 365 | if (client->seq_client < 0) {
|
| 366 | kfree(client);
|
| 367 | return -ENOMEM;
|
| 368 | }
|
| 369 | }
|
| 370 |
|
| 371 | msynth = kzalloc_objs(struct seq_midisynth, ports);
|
| 372 |
|
| 373 | struct snd_seq_port_info *port __free(kfree) =
|
| 374 | kmalloc_obj(*port);
|
| 375 | if (msynth == NULL || port == NULL)
|
| 376 | goto __nomem;
|
| 377 |
|
| 378 | for (p = 0; p < ports; p++) {
|
| 379 | ms = &msynth[p];
|
| 380 | ms->rmidi = rmidi;
|
| 381 |
|
| 382 | if (snd_seq_midisynth_new(ms, card, device, p) < 0)
|
| 383 | goto __nomem;
|
| 384 |
|
| 385 |
|
| 386 | memset(port, 0, sizeof(*port));
|
| 387 | port->addr.client = client->seq_client;
|
| 388 | port->addr.port = device * (256 / SNDRV_RAWMIDI_DEVICES) + p;
|
| 389 | port->flags = SNDRV_SEQ_PORT_FLG_GIVEN_PORT;
|
| 390 | memset(info, 0, sizeof(*info));
|
| 391 | info->device = device;
|
| 392 | if (p < output_count)
|
| 393 | info->stream = SNDRV_RAWMIDI_STREAM_OUTPUT;
|
| 394 | else
|
| 395 | info->stream = SNDRV_RAWMIDI_STREAM_INPUT;
|
| 396 | info->subdevice = p;
|
| 397 | if (snd_rawmidi_info_select(card, info) >= 0)
|
| 398 | strscpy(port->name, info->subname);
|
| 399 | if (! port->name[0]) {
|
| 400 | if (info->name[0]) {
|
| 401 | if (ports > 1)
|
| 402 | scnprintf(port->name, sizeof(port->name), "%s-%u", info->name, p);
|
| 403 | else
|
| 404 | scnprintf(port->name, sizeof(port->name), "%s", info->name);
|
| 405 | } else {
|
| 406 |
|
| 407 | if (ports > 1)
|
| 408 | sprintf(port->name, "MIDI %d-%d-%u", card->number, device, p);
|
| 409 | else
|
| 410 | sprintf(port->name, "MIDI %d-%d", card->number, device);
|
| 411 | }
|
| 412 | }
|
| 413 | if ((info->flags & SNDRV_RAWMIDI_INFO_OUTPUT) && p < output_count)
|
| 414 | port->capability |= SNDRV_SEQ_PORT_CAP_WRITE | SNDRV_SEQ_PORT_CAP_SYNC_WRITE | SNDRV_SEQ_PORT_CAP_SUBS_WRITE;
|
| 415 | if ((info->flags & SNDRV_RAWMIDI_INFO_INPUT) && p < input_count)
|
| 416 | port->capability |= SNDRV_SEQ_PORT_CAP_READ | SNDRV_SEQ_PORT_CAP_SYNC_READ | SNDRV_SEQ_PORT_CAP_SUBS_READ;
|
| 417 | if ((port->capability & (SNDRV_SEQ_PORT_CAP_WRITE|SNDRV_SEQ_PORT_CAP_READ)) == (SNDRV_SEQ_PORT_CAP_WRITE|SNDRV_SEQ_PORT_CAP_READ) &&
|
| 418 | info->flags & SNDRV_RAWMIDI_INFO_DUPLEX)
|
| 419 | port->capability |= SNDRV_SEQ_PORT_CAP_DUPLEX;
|
| 420 | if (port->capability & SNDRV_SEQ_PORT_CAP_READ)
|
| 421 | port->direction |= SNDRV_SEQ_PORT_DIR_INPUT;
|
| 422 | if (port->capability & SNDRV_SEQ_PORT_CAP_WRITE)
|
| 423 | port->direction |= SNDRV_SEQ_PORT_DIR_OUTPUT;
|
| 424 | port->type = SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC
|
| 425 | | SNDRV_SEQ_PORT_TYPE_HARDWARE
|
| 426 | | SNDRV_SEQ_PORT_TYPE_PORT;
|
| 427 | port->midi_channels = 16;
|
| 428 | memset(&pcallbacks, 0, sizeof(pcallbacks));
|
| 429 | pcallbacks.owner = THIS_MODULE;
|
| 430 | pcallbacks.private_data = ms;
|
| 431 | pcallbacks.subscribe = midisynth_subscribe;
|
| 432 | pcallbacks.unsubscribe = midisynth_unsubscribe;
|
| 433 | pcallbacks.use = midisynth_use;
|
| 434 | pcallbacks.unuse = midisynth_unuse;
|
| 435 | pcallbacks.event_input = event_process_midi;
|
| 436 | port->kernel = &pcallbacks;
|
| 437 | if (rmidi->ops && rmidi->ops->get_port_info)
|
| 438 | rmidi->ops->get_port_info(rmidi, p, port);
|
| 439 | if (snd_seq_kernel_client_ctl(client->seq_client, SNDRV_SEQ_IOCTL_CREATE_PORT, port)<0)
|
| 440 | goto __nomem;
|
| 441 | ms->seq_client = client->seq_client;
|
| 442 | ms->seq_port = port->addr.port;
|
| 443 | }
|
| 444 | client->ports_per_device[device] = ports;
|
| 445 | client->ports[device] = msynth;
|
| 446 | client->num_ports++;
|
| 447 | if (newclient)
|
| 448 | synths[card->number] = client;
|
| 449 | return 0;
|
| 450 |
|
| 451 | __nomem:
|
| 452 | if (msynth != NULL) {
|
| 453 | for (p = 0; p < ports; p++)
|
| 454 | snd_seq_midisynth_delete(&msynth[p]);
|
| 455 | kfree(msynth);
|
| 456 | }
|
| 457 | if (newclient) {
|
| 458 | snd_seq_delete_kernel_client(client->seq_client);
|
| 459 | kfree(client);
|
| 460 | }
|
| 461 | return -ENOMEM;
|
| 462 | }
|
| 463 |
|
| 464 |
|
| 465 | static void
|
| 466 | snd_seq_midisynth_remove(struct snd_seq_device *dev)
|
| 467 | {
|
| 468 | struct seq_midisynth_client *client;
|
| 469 | struct seq_midisynth *msynth;
|
| 470 | struct snd_card *card = dev->card;
|
| 471 | int device = dev->device, p, ports;
|
| 472 |
|
| 473 | guard(mutex)(®ister_mutex);
|
| 474 | client = synths[card->number];
|
| 475 | if (client == NULL || client->ports[device] == NULL)
|
| 476 | return;
|
| 477 | ports = client->ports_per_device[device];
|
| 478 | client->ports_per_device[device] = 0;
|
| 479 | msynth = client->ports[device];
|
| 480 | client->ports[device] = NULL;
|
| 481 | for (p = 0; p < ports; p++)
|
| 482 | snd_seq_midisynth_delete(&msynth[p]);
|
| 483 | kfree(msynth);
|
| 484 | client->num_ports--;
|
| 485 | if (client->num_ports <= 0) {
|
| 486 | snd_seq_delete_kernel_client(client->seq_client);
|
| 487 | synths[card->number] = NULL;
|
| 488 | kfree(client);
|
| 489 | }
|
| 490 | }
|
| 491 |
|
| 492 | static struct snd_seq_driver seq_midisynth_driver = {
|
| 493 | .probe = snd_seq_midisynth_probe,
|
| 494 | .remove = snd_seq_midisynth_remove,
|
| 495 | .driver = {
|
| 496 | .name = KBUILD_MODNAME,
|
| 497 | },
|
| 498 | .id = SNDRV_SEQ_DEV_ID_MIDISYNTH,
|
| 499 | .argsize = 0,
|
| 500 | };
|
| 501 |
|
| 502 | module_snd_seq_driver(seq_midisynth_driver);
|