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

ALSA: seq: Add UMP support

Starting from this commit, we add the basic support of UMP (Universal
MIDI Packet) events on ALSA sequencer infrastructure. The biggest
change here is that, for transferring UMP packets that are up to 128
bits, we extend the data payload of ALSA sequencer event record when
the client is declared to support for the new UMP events.

A new event flag bit, SNDRV_SEQ_EVENT_UMP, is defined and it shall be
set for the UMP packet events that have the larger payload of 128
bits, defined as struct snd_seq_ump_event.

For controlling the UMP feature enablement in kernel, a new Kconfig,
CONFIG_SND_SEQ_UMP is introduced. The extended event for UMP is
available only when this Kconfig item is set. Similarly, the size of
the internal snd_seq_event_cell also increases (in 4 bytes) when the
Kconfig item is set. (But the size increase is effective only for
32bit architectures; 64bit archs already have padding there.)
Overall, when CONFIG_SND_SEQ_UMP isn't set, there is no change in the
event and cell, keeping the old sizes.

For applications that want to access the UMP packets, first of all, a
sequencer client has to declare the user-protocol to match with the
latest one via the new SNDRV_SEQ_IOCTL_USER_PVERSION; otherwise it's
treated as if a legacy client without UMP support.

Then the client can switch to the new UMP mode (MIDI 1.0 or MIDI 2.0)
with a new field, midi_version, in snd_seq_client_info. When switched
to UMP mode (midi_version = 1 or 2), the client can write the UMP
events with SNDRV_SEQ_EVENT_UMP flag. For reads, the alignment size
is changed from snd_seq_event (28 bytes) to snd_seq_ump_event (32
bytes). When a UMP sequencer event is delivered to a legacy sequencer
client, it's ignored or handled as an error.

Conceptually, ALSA sequencer client and port correspond to the UMP
Endpoint and Group, respectively; each client may have multiple ports
and each port has the fixed number (16) of channels, total up to 256
channels.

As of this commit, ALSA sequencer core just sends and receives the UMP
events as-is from/to clients. The automatic conversions between the
legacy events and the new UMP events will be implemented in a later
patch.

Along with this commit, bump the sequencer protocol version to 1.0.3.

Reviewed-by: Jaroslav Kysela <perex@perex.cz>
Link: https://lore.kernel.org/r/20230523075358.9672-26-tiwai@suse.de
Signed-off-by: Takashi Iwai <tiwai@suse.de>

+194 -64
+4
include/sound/asequencer.h
··· 65 65 #define snd_seq_ev_is_abstime(ev) (snd_seq_ev_timemode_type(ev) == SNDRV_SEQ_TIME_MODE_ABS) 66 66 #define snd_seq_ev_is_reltime(ev) (snd_seq_ev_timemode_type(ev) == SNDRV_SEQ_TIME_MODE_REL) 67 67 68 + /* check whether the given event is a UMP event */ 69 + #define snd_seq_ev_is_ump(ev) \ 70 + (IS_ENABLED(CONFIG_SND_SEQ_UMP) && ((ev)->flags & SNDRV_SEQ_EVENT_UMP)) 71 + 68 72 /* queue sync port */ 69 73 #define snd_seq_queue_sync_port(q) ((q) + 16) 70 74
+8
include/sound/seq_kernel.h
··· 75 75 int snd_seq_dump_var_event(const struct snd_seq_event *event, 76 76 snd_seq_dump_func_t func, void *private_data); 77 77 78 + /* size of the event packet; it can be greater than snd_seq_event size */ 79 + static inline size_t snd_seq_event_packet_size(struct snd_seq_event *ev) 80 + { 81 + if (snd_seq_ev_is_ump(ev)) 82 + return sizeof(struct snd_seq_ump_event); 83 + return sizeof(struct snd_seq_event); 84 + } 85 + 78 86 /* interface for OSS emulation */ 79 87 int snd_seq_set_queue_tempo(int client, struct snd_seq_queue_tempo *tempo); 80 88
+37 -16
include/uapi/sound/asequencer.h
··· 10 10 #include <sound/asound.h> 11 11 12 12 /** version of the sequencer */ 13 - #define SNDRV_SEQ_VERSION SNDRV_PROTOCOL_VERSION(1, 0, 2) 13 + #define SNDRV_SEQ_VERSION SNDRV_PROTOCOL_VERSION(1, 0, 3) 14 14 15 15 /** 16 16 * definition of sequencer event types ··· 174 174 #define SNDRV_SEQ_PRIORITY_HIGH (1<<4) /* event should be processed before others */ 175 175 #define SNDRV_SEQ_PRIORITY_MASK (1<<4) 176 176 177 + #define SNDRV_SEQ_EVENT_UMP (1<<5) /* event holds a UMP packet */ 177 178 178 179 /* note event */ 179 180 struct snd_seq_ev_note { ··· 253 252 struct snd_seq_event *event; /* quoted event */ 254 253 } __attribute__((packed)); 255 254 255 + union snd_seq_event_data { /* event data... */ 256 + struct snd_seq_ev_note note; 257 + struct snd_seq_ev_ctrl control; 258 + struct snd_seq_ev_raw8 raw8; 259 + struct snd_seq_ev_raw32 raw32; 260 + struct snd_seq_ev_ext ext; 261 + struct snd_seq_ev_queue_control queue; 262 + union snd_seq_timestamp time; 263 + struct snd_seq_addr addr; 264 + struct snd_seq_connect connect; 265 + struct snd_seq_result result; 266 + struct snd_seq_ev_quote quote; 267 + }; 256 268 257 269 /* sequencer event */ 258 270 struct snd_seq_event { ··· 276 262 unsigned char queue; /* schedule queue */ 277 263 union snd_seq_timestamp time; /* schedule time */ 278 264 279 - 280 265 struct snd_seq_addr source; /* source address */ 281 266 struct snd_seq_addr dest; /* destination address */ 282 267 283 - union { /* event data... */ 284 - struct snd_seq_ev_note note; 285 - struct snd_seq_ev_ctrl control; 286 - struct snd_seq_ev_raw8 raw8; 287 - struct snd_seq_ev_raw32 raw32; 288 - struct snd_seq_ev_ext ext; 289 - struct snd_seq_ev_queue_control queue; 290 - union snd_seq_timestamp time; 291 - struct snd_seq_addr addr; 292 - struct snd_seq_connect connect; 293 - struct snd_seq_result result; 294 - struct snd_seq_ev_quote quote; 295 - } data; 268 + union snd_seq_event_data data; 296 269 }; 297 270 271 + /* (compatible) event for UMP-capable clients */ 272 + struct snd_seq_ump_event { 273 + snd_seq_event_type_t type; /* event type */ 274 + unsigned char flags; /* event flags */ 275 + char tag; 276 + unsigned char queue; /* schedule queue */ 277 + union snd_seq_timestamp time; /* schedule time */ 278 + struct snd_seq_addr source; /* source address */ 279 + struct snd_seq_addr dest; /* destination address */ 280 + 281 + union { 282 + union snd_seq_event_data data; 283 + unsigned int ump[4]; 284 + }; 285 + }; 298 286 299 287 /* 300 288 * bounce event - stored as variable size data ··· 360 344 int event_lost; /* number of lost events */ 361 345 int card; /* RO: card number[kernel] */ 362 346 int pid; /* RO: pid[user] */ 363 - char reserved[56]; /* for future use */ 347 + unsigned int midi_version; /* MIDI version */ 348 + char reserved[52]; /* for future use */ 364 349 }; 365 350 351 + /* MIDI version numbers in client info */ 352 + #define SNDRV_SEQ_CLIENT_LEGACY_MIDI 0 /* Legacy client */ 353 + #define SNDRV_SEQ_CLIENT_UMP_MIDI_1_0 1 /* UMP MIDI 1.0 */ 354 + #define SNDRV_SEQ_CLIENT_UMP_MIDI_2_0 2 /* UMP MIDI 2.0 */ 366 355 367 356 /* client pool size */ 368 357 struct snd_seq_client_pool {
+7
sound/core/seq/Kconfig
··· 60 60 config SND_SEQ_VIRMIDI 61 61 tristate 62 62 63 + config SND_SEQ_UMP 64 + bool "Support for UMP events" 65 + help 66 + Say Y here to enable the support for handling UMP (Universal MIDI 67 + Packet) events via ALSA sequencer infrastructure, which is an 68 + essential feature for enabling MIDI 2.0 support. 69 + 63 70 endif # SND_SEQUENCER
+111 -45
sound/core/seq/seq_clientmgr.c
··· 387 387 return 0; 388 388 } 389 389 390 + static bool event_is_compatible(const struct snd_seq_client *client, 391 + const struct snd_seq_event *ev) 392 + { 393 + if (snd_seq_ev_is_ump(ev) && !client->midi_version) 394 + return false; 395 + if (snd_seq_ev_is_ump(ev) && snd_seq_ev_is_variable(ev)) 396 + return false; 397 + return true; 398 + } 390 399 391 400 /* handle client read() */ 392 401 /* possible error values: ··· 409 400 { 410 401 struct snd_seq_client *client = file->private_data; 411 402 struct snd_seq_fifo *fifo; 403 + size_t aligned_size; 412 404 int err; 413 405 long result = 0; 414 406 struct snd_seq_event_cell *cell; ··· 441 431 err = 0; 442 432 snd_seq_fifo_lock(fifo); 443 433 434 + if (client->midi_version > 0) 435 + aligned_size = sizeof(struct snd_seq_ump_event); 436 + else 437 + aligned_size = sizeof(struct snd_seq_event); 438 + 444 439 /* while data available in queue */ 445 - while (count >= sizeof(struct snd_seq_event)) { 440 + while (count >= aligned_size) { 446 441 int nonblock; 447 442 448 443 nonblock = (file->f_flags & O_NONBLOCK) || result > 0; 449 444 err = snd_seq_fifo_cell_out(fifo, &cell, nonblock); 450 445 if (err < 0) 451 446 break; 447 + if (!event_is_compatible(client, &cell->event)) { 448 + snd_seq_cell_free(cell); 449 + cell = NULL; 450 + continue; 451 + } 452 452 if (snd_seq_ev_is_variable(&cell->event)) { 453 - struct snd_seq_event tmpev; 454 - tmpev = cell->event; 453 + struct snd_seq_ump_event tmpev; 454 + 455 + memcpy(&tmpev, &cell->event, aligned_size); 455 456 tmpev.data.ext.len &= ~SNDRV_SEQ_EXT_MASK; 456 - if (copy_to_user(buf, &tmpev, sizeof(struct snd_seq_event))) { 457 + if (copy_to_user(buf, &tmpev, aligned_size)) { 457 458 err = -EFAULT; 458 459 break; 459 460 } 460 - count -= sizeof(struct snd_seq_event); 461 - buf += sizeof(struct snd_seq_event); 461 + count -= aligned_size; 462 + buf += aligned_size; 462 463 err = snd_seq_expand_var_event(&cell->event, count, 463 464 (char __force *)buf, 0, 464 - sizeof(struct snd_seq_event)); 465 + aligned_size); 465 466 if (err < 0) 466 467 break; 467 468 result += err; 468 469 count -= err; 469 470 buf += err; 470 471 } else { 471 - if (copy_to_user(buf, &cell->event, sizeof(struct snd_seq_event))) { 472 + if (copy_to_user(buf, &cell->event, aligned_size)) { 472 473 err = -EFAULT; 473 474 break; 474 475 } 475 - count -= sizeof(struct snd_seq_event); 476 - buf += sizeof(struct snd_seq_event); 476 + count -= aligned_size; 477 + buf += aligned_size; 477 478 } 478 479 snd_seq_cell_free(cell); 479 480 cell = NULL; /* to be sure */ 480 - result += sizeof(struct snd_seq_event); 481 + result += aligned_size; 481 482 } 482 483 483 484 if (err < 0) { ··· 686 665 { 687 666 struct snd_seq_subscribers *subs; 688 667 int err, result = 0, num_ev = 0; 689 - struct snd_seq_event event_saved; 690 668 struct snd_seq_client_port *src_port; 669 + union __snd_seq_event event_saved; 670 + size_t saved_size; 691 671 struct snd_seq_port_subs_info *grp; 692 672 693 673 src_port = snd_seq_port_use_ptr(client, event->source.port); 694 674 if (src_port == NULL) 695 675 return -EINVAL; /* invalid source port */ 696 676 /* save original event record */ 697 - event_saved = *event; 677 + saved_size = snd_seq_event_packet_size(event); 678 + memcpy(&event_saved, event, saved_size); 698 679 grp = &src_port->c_src; 699 680 700 681 /* lock list */ ··· 723 700 } 724 701 num_ev++; 725 702 /* restore original event record */ 726 - *event = event_saved; 703 + memcpy(event, &event_saved, saved_size); 727 704 } 728 705 if (atomic) 729 706 read_unlock(&grp->list_lock); 730 707 else 731 708 up_read(&grp->list_mutex); 732 - *event = event_saved; /* restore */ 733 - snd_seq_port_unlock(src_port); 709 + memcpy(event, &event_saved, saved_size); 734 710 return (result < 0) ? result : num_ev; 735 711 } 736 712 ··· 791 769 return -EINVAL; 792 770 } 793 771 794 - if (cell->event.type == SNDRV_SEQ_EVENT_NOTE) { 772 + if (!snd_seq_ev_is_ump(&cell->event) && 773 + cell->event.type == SNDRV_SEQ_EVENT_NOTE) { 795 774 /* NOTE event: 796 775 * the event cell is re-used as a NOTE-OFF event and 797 776 * enqueued again. ··· 816 793 /* add the duration time */ 817 794 switch (ev->flags & SNDRV_SEQ_TIME_STAMP_MASK) { 818 795 case SNDRV_SEQ_TIME_STAMP_TICK: 819 - ev->time.tick += ev->data.note.duration; 796 + cell->event.time.tick += ev->data.note.duration; 820 797 break; 821 798 case SNDRV_SEQ_TIME_STAMP_REAL: 822 799 /* unit for duration is ms */ ··· 873 850 874 851 /* direct event processing without enqueued */ 875 852 if (snd_seq_ev_is_direct(event)) { 876 - if (event->type == SNDRV_SEQ_EVENT_NOTE) 853 + if (!snd_seq_ev_is_ump(event) && 854 + event->type == SNDRV_SEQ_EVENT_NOTE) 877 855 return -EINVAL; /* this event must be enqueued! */ 878 856 return snd_seq_deliver_event(client, event, atomic, hop); 879 857 } ··· 944 920 struct snd_seq_client *client = file->private_data; 945 921 int written = 0, len; 946 922 int err, handled; 947 - struct snd_seq_event event; 923 + union __snd_seq_event __event; 924 + struct snd_seq_event *ev = &__event.legacy; 948 925 949 926 if (!(snd_seq_file_flags(file) & SNDRV_SEQ_LFLG_OUTPUT)) 950 927 return -ENXIO; ··· 971 946 err = -EINVAL; 972 947 while (count >= sizeof(struct snd_seq_event)) { 973 948 /* Read in the event header from the user */ 974 - len = sizeof(event); 975 - if (copy_from_user(&event, buf, len)) { 949 + len = sizeof(struct snd_seq_event); 950 + if (copy_from_user(ev, buf, len)) { 976 951 err = -EFAULT; 977 952 break; 978 953 } 979 - event.source.client = client->number; /* fill in client number */ 954 + /* read in the rest bytes for UMP events */ 955 + if (snd_seq_ev_is_ump(ev)) { 956 + if (count < sizeof(struct snd_seq_ump_event)) 957 + break; 958 + if (copy_from_user((char *)ev + len, buf + len, 959 + sizeof(struct snd_seq_ump_event) - len)) { 960 + err = -EFAULT; 961 + break; 962 + } 963 + len = sizeof(struct snd_seq_ump_event); 964 + } 965 + 966 + ev->source.client = client->number; /* fill in client number */ 980 967 /* Check for extension data length */ 981 - if (check_event_type_and_length(&event)) { 968 + if (check_event_type_and_length(ev)) { 969 + err = -EINVAL; 970 + break; 971 + } 972 + 973 + if (!event_is_compatible(client, ev)) { 982 974 err = -EINVAL; 983 975 break; 984 976 } 985 977 986 978 /* check for special events */ 987 - if (event.type == SNDRV_SEQ_EVENT_NONE) 988 - goto __skip_event; 989 - else if (snd_seq_ev_is_reserved(&event)) { 990 - err = -EINVAL; 991 - break; 979 + if (!snd_seq_ev_is_ump(ev)) { 980 + if (ev->type == SNDRV_SEQ_EVENT_NONE) 981 + goto __skip_event; 982 + else if (snd_seq_ev_is_reserved(ev)) { 983 + err = -EINVAL; 984 + break; 985 + } 992 986 } 993 987 994 - if (snd_seq_ev_is_variable(&event)) { 995 - int extlen = event.data.ext.len & ~SNDRV_SEQ_EXT_MASK; 988 + if (snd_seq_ev_is_variable(ev)) { 989 + int extlen = ev->data.ext.len & ~SNDRV_SEQ_EXT_MASK; 996 990 if ((size_t)(extlen + len) > count) { 997 991 /* back out, will get an error this time or next */ 998 992 err = -EINVAL; 999 993 break; 1000 994 } 1001 995 /* set user space pointer */ 1002 - event.data.ext.len = extlen | SNDRV_SEQ_EXT_USRPTR; 1003 - event.data.ext.ptr = (char __force *)buf 1004 - + sizeof(struct snd_seq_event); 996 + ev->data.ext.len = extlen | SNDRV_SEQ_EXT_USRPTR; 997 + ev->data.ext.ptr = (char __force *)buf + len; 1005 998 len += extlen; /* increment data length */ 1006 999 } else { 1007 1000 #ifdef CONFIG_COMPAT 1008 - if (client->convert32 && snd_seq_ev_is_varusr(&event)) { 1009 - void *ptr = (void __force *)compat_ptr(event.data.raw32.d[1]); 1010 - event.data.ext.ptr = ptr; 1011 - } 1001 + if (client->convert32 && snd_seq_ev_is_varusr(ev)) 1002 + ev->data.ext.ptr = 1003 + (void __force *)compat_ptr(ev->data.raw32.d[1]); 1012 1004 #endif 1013 1005 } 1014 1006 1015 1007 /* ok, enqueue it */ 1016 - err = snd_seq_client_enqueue_event(client, &event, file, 1008 + err = snd_seq_client_enqueue_event(client, ev, file, 1017 1009 !(file->f_flags & O_NONBLOCK), 1018 1010 0, 0, &client->ioctl_mutex); 1019 1011 if (err < 0) ··· 1188 1146 else 1189 1147 info->card = -1; 1190 1148 1149 + info->midi_version = cptr->midi_version; 1191 1150 memset(info->reserved, 0, sizeof(info->reserved)); 1192 1151 } 1193 1152 ··· 1223 1180 if (client->type != client_info->type) 1224 1181 return -EINVAL; 1225 1182 1183 + /* check validity of midi_version field */ 1184 + if (client->user_pversion >= SNDRV_PROTOCOL_VERSION(1, 0, 3) && 1185 + client_info->midi_version > SNDRV_SEQ_CLIENT_UMP_MIDI_2_0) 1186 + return -EINVAL; 1187 + 1226 1188 /* fill the info fields */ 1227 1189 if (client_info->name[0]) 1228 1190 strscpy(client->name, client_info->name, sizeof(client->name)); 1229 1191 1230 1192 client->filter = client_info->filter; 1231 1193 client->event_lost = client_info->event_lost; 1194 + if (client->user_pversion >= SNDRV_PROTOCOL_VERSION(1, 0, 3)) 1195 + client->midi_version = client_info->midi_version; 1232 1196 memcpy(client->event_filter, client_info->event_filter, 32); 1233 1197 1234 1198 return 0; ··· 2231 2181 if (snd_BUG_ON(!ev)) 2232 2182 return -EINVAL; 2233 2183 2234 - if (ev->type == SNDRV_SEQ_EVENT_NONE) 2235 - return 0; /* ignore this */ 2236 - if (ev->type == SNDRV_SEQ_EVENT_KERNEL_ERROR) 2237 - return -EINVAL; /* quoted events can't be enqueued */ 2184 + if (!snd_seq_ev_is_ump(ev)) { 2185 + if (ev->type == SNDRV_SEQ_EVENT_NONE) 2186 + return 0; /* ignore this */ 2187 + if (ev->type == SNDRV_SEQ_EVENT_KERNEL_ERROR) 2188 + return -EINVAL; /* quoted events can't be enqueued */ 2189 + } 2238 2190 2239 2191 /* fill in client number */ 2240 2192 ev->source.client = client; ··· 2428 2376 mutex_unlock(&client->ports_mutex); 2429 2377 } 2430 2378 2379 + static const char *midi_version_string(unsigned int version) 2380 + { 2381 + switch (version) { 2382 + case SNDRV_SEQ_CLIENT_LEGACY_MIDI: 2383 + return "Legacy"; 2384 + case SNDRV_SEQ_CLIENT_UMP_MIDI_1_0: 2385 + return "UMP MIDI1"; 2386 + case SNDRV_SEQ_CLIENT_UMP_MIDI_2_0: 2387 + return "UMP MIDI2"; 2388 + default: 2389 + return "Unknown"; 2390 + } 2391 + } 2431 2392 2432 2393 /* exported to seq_info.c */ 2433 2394 void snd_seq_info_clients_read(struct snd_info_entry *entry, ··· 2465 2400 continue; 2466 2401 } 2467 2402 2468 - snd_iprintf(buffer, "Client %3d : \"%s\" [%s]\n", 2403 + snd_iprintf(buffer, "Client %3d : \"%s\" [%s %s]\n", 2469 2404 c, client->name, 2470 - client->type == USER_CLIENT ? "User" : "Kernel"); 2405 + client->type == USER_CLIENT ? "User" : "Kernel", 2406 + midi_version_string(client->midi_version)); 2471 2407 snd_seq_info_dump_ports(buffer, client); 2472 2408 if (snd_seq_write_pool_allocated(client)) { 2473 2409 snd_iprintf(buffer, " Output pool :\n");
+1
sound/core/seq/seq_clientmgr.h
··· 35 35 snd_seq_client_type_t type; 36 36 unsigned int accept_input: 1, 37 37 accept_output: 1; 38 + unsigned int midi_version; 38 39 unsigned int user_pversion; 39 40 char name[64]; /* client name */ 40 41 int number; /* client number */
+8 -2
sound/core/seq/seq_memory.c
··· 340 340 int ncells, err; 341 341 unsigned int extlen; 342 342 struct snd_seq_event_cell *cell; 343 + int size; 343 344 344 345 *cellp = NULL; 345 346 ··· 358 357 return err; 359 358 360 359 /* copy the event */ 361 - cell->event = *event; 360 + size = snd_seq_event_packet_size(event); 361 + memcpy(&cell->ump, event, size); 362 + #if IS_ENABLED(CONFIG_SND_SEQ_UMP) 363 + if (size < sizeof(cell->event)) 364 + cell->ump.raw.extra = 0; 365 + #endif 362 366 363 367 /* decompose */ 364 368 if (snd_seq_ev_is_variable(event)) { ··· 381 375 tail = NULL; 382 376 383 377 while (ncells-- > 0) { 384 - int size = sizeof(struct snd_seq_event); 378 + size = sizeof(struct snd_seq_event); 385 379 if (len < size) 386 380 size = len; 387 381 err = snd_seq_cell_alloc(pool, &tmp, nonblock, file,
+18 -1
sound/core/seq/seq_memory.h
··· 11 11 12 12 struct snd_info_buffer; 13 13 14 + /* aliasing for legacy and UMP event packet handling */ 15 + union __snd_seq_event { 16 + struct snd_seq_event legacy; 17 + #if IS_ENABLED(CONFIG_SND_SEQ_UMP) 18 + struct snd_seq_ump_event ump; 19 + #endif 20 + struct { 21 + struct snd_seq_event event; 22 + #if IS_ENABLED(CONFIG_SND_SEQ_UMP) 23 + u32 extra; 24 + #endif 25 + } __packed raw; 26 + }; 27 + 14 28 /* container for sequencer event (internal use) */ 15 29 struct snd_seq_event_cell { 16 - struct snd_seq_event event; 30 + union { 31 + struct snd_seq_event event; 32 + union __snd_seq_event ump; 33 + }; 17 34 struct snd_seq_pool *pool; /* used pool */ 18 35 struct snd_seq_event_cell *next; /* next cell */ 19 36 };