| 1 | |
| 2 |
|
| 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 |
|
| 27 | struct seq_ump_input_buffer {
|
| 28 | unsigned char len;
|
| 29 | unsigned char pending;
|
| 30 | unsigned char type;
|
| 31 | unsigned char group;
|
| 32 | u32 buf[4];
|
| 33 | };
|
| 34 |
|
| 35 |
|
| 36 | struct seq_ump_client {
|
| 37 | struct snd_ump_endpoint *ump;
|
| 38 | int seq_client;
|
| 39 | int opened[2];
|
| 40 | struct snd_rawmidi_file out_rfile;
|
| 41 | |
| 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;
|
| 46 | void *ump_info[SNDRV_UMP_MAX_BLOCKS + 1];
|
| 47 | struct work_struct group_notify_work;
|
| 48 | };
|
| 49 |
|
| 50 |
|
| 51 | static 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 | |
| 56 | * assume the port number is equal with UMP group number (1-based)
|
| 57 |
|
| 58 | static unsigned char ump_group_to_seq_port(unsigned char group)
|
| 59 | {
|
| 60 | return group + 1;
|
| 61 | }
|
| 62 |
|
| 63 |
|
| 64 | static 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;
|
| 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 |
|
| 86 | static 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;
|
| 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; |
| 106 | snd_rawmidi_kernel_write(substream, ev->data.raw8.d, len << 2);
|
| 107 | return 0;
|
| 108 | }
|
| 109 |
|
| 110 |
|
| 111 | static 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 | |
| 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 |
|
| 136 | static 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 | |
| 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 |
|
| 157 | static 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 |
|
| 164 | static 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 |
|
| 171 | static 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 |
|
| 178 | static 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 |
|
| 186 | static 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 |
|
| 227 | static 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 |
|
| 234 | static 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 |
|
| 264 | static 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 |
|
| 299 | static 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;
|
| 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;
|
| 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 |
|
| 354 | static 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 |
|
| 368 | static 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 |
|
| 383 | static 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;
|
| 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 |
|
| 402 | static 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 |
|
| 412 | static 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 |
|
| 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 |
|
| 439 | static 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 |
|
| 454 | static 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 |
|
| 467 | static 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 |
|
| 475 | static 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 |
|
| 535 | static 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 |
|
| 543 | static 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 |
|
| 553 | module_snd_seq_driver(seq_ump_driver);
|
| 554 |
|
| 555 | MODULE_DESCRIPTION("ALSA sequencer client for UMP rawmidi");
|
| 556 | MODULE_LICENSE("GPL");
|