Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

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