| @@ -0,0 +1,389 @@ |
| 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 | + |
| 17 | +struct seq_ump_client; |
| 18 | +struct seq_ump_group; |
| 19 | + |
| 20 | +enum { |
| 21 | + STR_IN = SNDRV_RAWMIDI_STREAM_INPUT, |
| 22 | + STR_OUT = SNDRV_RAWMIDI_STREAM_OUTPUT |
| 23 | +}; |
| 24 | + |
| 25 | +/* object per UMP group; corresponding to a sequencer port */ |
| 26 | +struct seq_ump_group { |
| 27 | + int group; /* group index (0-based) */ |
| 28 | + unsigned int dir_bits; /* directions */ |
| 29 | + bool active; /* activeness */ |
| 30 | + char name[64]; /* seq port name */ |
| 31 | +}; |
| 32 | + |
| 33 | +/* context for UMP input parsing, per EP */ |
| 34 | +struct seq_ump_input_buffer { |
| 35 | + unsigned char len; /* total length in words */ |
| 36 | + unsigned char pending; /* pending words */ |
| 37 | + unsigned char type; /* parsed UMP packet type */ |
| 38 | + unsigned char group; /* parsed UMP packet group */ |
| 39 | + u32 buf[4]; /* incoming UMP packet */ |
| 40 | +}; |
| 41 | + |
| 42 | +/* sequencer client, per UMP EP (rawmidi) */ |
| 43 | +struct seq_ump_client { |
| 44 | + struct snd_ump_endpoint *ump; /* assigned endpoint */ |
| 45 | + int seq_client; /* sequencer client id */ |
| 46 | + int opened[2]; /* current opens for each direction */ |
| 47 | + struct snd_rawmidi_file out_rfile; /* rawmidi for output */ |
| 48 | + struct seq_ump_input_buffer input; /* input parser context */ |
| 49 | + struct seq_ump_group groups[SNDRV_UMP_MAX_GROUPS]; /* table of groups */ |
| 50 | +}; |
| 51 | + |
| 52 | +/* number of 32bit words for each UMP message type */ |
| 53 | +static unsigned char ump_packet_words[0x10] = { |
| 54 | + 1, 1, 1, 2, 2, 4, 1, 1, 2, 2, 2, 3, 3, 4, 4, 4 |
| 55 | +}; |
| 56 | + |
| 57 | +/* conversion between UMP group and seq port; |
| 58 | + * assume the port number is equal with UMP group number (1-based) |
| 59 | + */ |
| 60 | +static unsigned char ump_group_to_seq_port(unsigned char group) |
| 61 | +{ |
| 62 | + return group + 1; |
| 63 | +} |
| 64 | + |
| 65 | +/* process the incoming rawmidi stream */ |
| 66 | +static void seq_ump_input_receive(struct snd_ump_endpoint *ump, |
| 67 | + const u32 *val, int words) |
| 68 | +{ |
| 69 | + struct seq_ump_client *client = ump->seq_client; |
| 70 | + struct snd_seq_ump_event ev = {}; |
| 71 | + |
| 72 | + if (!client->opened[STR_IN]) |
| 73 | + return; |
| 74 | + |
| 75 | + ev.source.port = ump_group_to_seq_port(ump_message_group(*val)); |
| 76 | + ev.dest.client = SNDRV_SEQ_ADDRESS_SUBSCRIBERS; |
| 77 | + ev.flags = SNDRV_SEQ_EVENT_UMP; |
| 78 | + memcpy(ev.ump, val, words << 2); |
| 79 | + snd_seq_kernel_client_dispatch(client->seq_client, |
| 80 | + (struct snd_seq_event *)&ev, |
| 81 | + true, 0); |
| 82 | +} |
| 83 | + |
| 84 | +/* process an input sequencer event; only deal with UMP types */ |
| 85 | +static int seq_ump_process_event(struct snd_seq_event *ev, int direct, |
| 86 | + void *private_data, int atomic, int hop) |
| 87 | +{ |
| 88 | + struct seq_ump_client *client = private_data; |
| 89 | + struct snd_rawmidi_substream *substream; |
| 90 | + struct snd_seq_ump_event *ump_ev; |
| 91 | + unsigned char type; |
| 92 | + int len; |
| 93 | + |
| 94 | + substream = client->out_rfile.output; |
| 95 | + if (!substream) |
| 96 | + return -ENODEV; |
| 97 | + if (!snd_seq_ev_is_ump(ev)) |
| 98 | + return 0; /* invalid event, skip */ |
| 99 | + ump_ev = (struct snd_seq_ump_event *)ev; |
| 100 | + type = ump_message_type(ump_ev->ump[0]); |
| 101 | + len = ump_packet_words[type]; |
| 102 | + if (len > 4) |
| 103 | + return 0; // invalid - skip |
| 104 | + snd_rawmidi_kernel_write(substream, ev->data.raw8.d, len << 2); |
| 105 | + return 0; |
| 106 | +} |
| 107 | + |
| 108 | +/* open the rawmidi */ |
| 109 | +static int seq_ump_client_open(struct seq_ump_client *client, int dir) |
| 110 | +{ |
| 111 | + struct snd_ump_endpoint *ump = client->ump; |
| 112 | + int err = 0; |
| 113 | + |
| 114 | + mutex_lock(&ump->open_mutex); |
| 115 | + if (dir == STR_OUT && !client->opened[dir]) { |
| 116 | + err = snd_rawmidi_kernel_open(&ump->core, 0, |
| 117 | + SNDRV_RAWMIDI_LFLG_OUTPUT | |
| 118 | + SNDRV_RAWMIDI_LFLG_APPEND, |
| 119 | + &client->out_rfile); |
| 120 | + if (err < 0) |
| 121 | + goto unlock; |
| 122 | + } |
| 123 | + client->opened[dir]++; |
| 124 | + unlock: |
| 125 | + mutex_unlock(&ump->open_mutex); |
| 126 | + return err; |
| 127 | +} |
| 128 | + |
| 129 | +/* close the rawmidi */ |
| 130 | +static int seq_ump_client_close(struct seq_ump_client *client, int dir) |
| 131 | +{ |
| 132 | + struct snd_ump_endpoint *ump = client->ump; |
| 133 | + |
| 134 | + mutex_lock(&ump->open_mutex); |
| 135 | + if (!--client->opened[dir]) |
| 136 | + if (dir == STR_OUT) |
| 137 | + snd_rawmidi_kernel_release(&client->out_rfile); |
| 138 | + mutex_unlock(&ump->open_mutex); |
| 139 | + return 0; |
| 140 | +} |
| 141 | + |
| 142 | +/* sequencer subscription ops for each client */ |
| 143 | +static int seq_ump_subscribe(void *pdata, struct snd_seq_port_subscribe *info) |
| 144 | +{ |
| 145 | + struct seq_ump_client *client = pdata; |
| 146 | + |
| 147 | + return seq_ump_client_open(client, STR_IN); |
| 148 | +} |
| 149 | + |
| 150 | +static int seq_ump_unsubscribe(void *pdata, struct snd_seq_port_subscribe *info) |
| 151 | +{ |
| 152 | + struct seq_ump_client *client = pdata; |
| 153 | + |
| 154 | + return seq_ump_client_close(client, STR_IN); |
| 155 | +} |
| 156 | + |
| 157 | +static int seq_ump_use(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_OUT); |
| 162 | +} |
| 163 | + |
| 164 | +static int seq_ump_unuse(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_OUT); |
| 169 | +} |
| 170 | + |
| 171 | +/* fill port_info from the given UMP EP and group info */ |
| 172 | +static void fill_port_info(struct snd_seq_port_info *port, |
| 173 | + struct seq_ump_client *client, |
| 174 | + struct seq_ump_group *group) |
| 175 | +{ |
| 176 | + unsigned int rawmidi_info = client->ump->core.info_flags; |
| 177 | + |
| 178 | + port->addr.client = client->seq_client; |
| 179 | + port->addr.port = ump_group_to_seq_port(group->group); |
| 180 | + port->capability = 0; |
| 181 | + if (rawmidi_info & SNDRV_RAWMIDI_INFO_OUTPUT) |
| 182 | + port->capability |= SNDRV_SEQ_PORT_CAP_WRITE | |
| 183 | + SNDRV_SEQ_PORT_CAP_SYNC_WRITE | |
| 184 | + SNDRV_SEQ_PORT_CAP_SUBS_WRITE; |
| 185 | + if (rawmidi_info & SNDRV_RAWMIDI_INFO_INPUT) |
| 186 | + port->capability |= SNDRV_SEQ_PORT_CAP_READ | |
| 187 | + SNDRV_SEQ_PORT_CAP_SYNC_READ | |
| 188 | + SNDRV_SEQ_PORT_CAP_SUBS_READ; |
| 189 | + if (rawmidi_info & SNDRV_RAWMIDI_INFO_DUPLEX) |
| 190 | + port->capability |= SNDRV_SEQ_PORT_CAP_DUPLEX; |
| 191 | + if (group->dir_bits & (1 << STR_IN)) |
| 192 | + port->direction |= SNDRV_SEQ_PORT_DIR_INPUT; |
| 193 | + if (group->dir_bits & (1 << STR_OUT)) |
| 194 | + port->direction |= SNDRV_SEQ_PORT_DIR_OUTPUT; |
| 195 | + port->ump_group = group->group + 1; |
| 196 | + if (!group->active) |
| 197 | + port->capability |= SNDRV_SEQ_PORT_CAP_INACTIVE; |
| 198 | + port->type = SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC | |
| 199 | + SNDRV_SEQ_PORT_TYPE_MIDI_UMP | |
| 200 | + SNDRV_SEQ_PORT_TYPE_HARDWARE | |
| 201 | + SNDRV_SEQ_PORT_TYPE_PORT; |
| 202 | + port->midi_channels = 16; |
| 203 | + if (*group->name) |
| 204 | + snprintf(port->name, sizeof(port->name), "Group %d (%s)", |
| 205 | + group->group + 1, group->name); |
| 206 | + else |
| 207 | + sprintf(port->name, "Group %d", group->group + 1); |
| 208 | +} |
| 209 | + |
| 210 | +/* create a new sequencer port per UMP group */ |
| 211 | +static int seq_ump_group_init(struct seq_ump_client *client, int group_index) |
| 212 | +{ |
| 213 | + struct seq_ump_group *group = &client->groups[group_index]; |
| 214 | + struct snd_seq_port_info *port; |
| 215 | + struct snd_seq_port_callback pcallbacks; |
| 216 | + int err; |
| 217 | + |
| 218 | + port = kzalloc(sizeof(*port), GFP_KERNEL); |
| 219 | + if (!port) { |
| 220 | + err = -ENOMEM; |
| 221 | + goto error; |
| 222 | + } |
| 223 | + |
| 224 | + fill_port_info(port, client, group); |
| 225 | + port->flags = SNDRV_SEQ_PORT_FLG_GIVEN_PORT; |
| 226 | + memset(&pcallbacks, 0, sizeof(pcallbacks)); |
| 227 | + pcallbacks.owner = THIS_MODULE; |
| 228 | + pcallbacks.private_data = client; |
| 229 | + pcallbacks.subscribe = seq_ump_subscribe; |
| 230 | + pcallbacks.unsubscribe = seq_ump_unsubscribe; |
| 231 | + pcallbacks.use = seq_ump_use; |
| 232 | + pcallbacks.unuse = seq_ump_unuse; |
| 233 | + pcallbacks.event_input = seq_ump_process_event; |
| 234 | + port->kernel = &pcallbacks; |
| 235 | + err = snd_seq_kernel_client_ctl(client->seq_client, |
| 236 | + SNDRV_SEQ_IOCTL_CREATE_PORT, |
| 237 | + port); |
| 238 | + error: |
| 239 | + kfree(port); |
| 240 | + return err; |
| 241 | +} |
| 242 | + |
| 243 | +/* update dir_bits and active flag for all groups in the client */ |
| 244 | +static void update_group_attrs(struct seq_ump_client *client) |
| 245 | +{ |
| 246 | + struct snd_ump_block *fb; |
| 247 | + struct seq_ump_group *group; |
| 248 | + int i; |
| 249 | + |
| 250 | + for (i = 0; i < SNDRV_UMP_MAX_GROUPS; i++) { |
| 251 | + group = &client->groups[i]; |
| 252 | + *group->name = 0; |
| 253 | + group->dir_bits = 0; |
| 254 | + group->active = 0; |
| 255 | + group->group = i; |
| 256 | + } |
| 257 | + |
| 258 | + list_for_each_entry(fb, &client->ump->block_list, list) { |
| 259 | + if (fb->info.first_group < 0 || |
| 260 | + fb->info.first_group + fb->info.num_groups > SNDRV_UMP_MAX_GROUPS) |
| 261 | + break; |
| 262 | + group = &client->groups[fb->info.first_group]; |
| 263 | + for (i = 0; i < fb->info.num_groups; i++, group++) { |
| 264 | + if (fb->info.active) |
| 265 | + group->active = 1; |
| 266 | + switch (fb->info.direction) { |
| 267 | + case SNDRV_UMP_DIR_INPUT: |
| 268 | + group->dir_bits |= (1 << STR_IN); |
| 269 | + break; |
| 270 | + case SNDRV_UMP_DIR_OUTPUT: |
| 271 | + group->dir_bits |= (1 << STR_OUT); |
| 272 | + break; |
| 273 | + case SNDRV_UMP_DIR_BIDIRECTION: |
| 274 | + group->dir_bits |= (1 << STR_OUT) | (1 << STR_IN); |
| 275 | + break; |
| 276 | + } |
| 277 | + if (!*fb->info.name) |
| 278 | + continue; |
| 279 | + if (!*group->name) { |
| 280 | + /* store the first matching name */ |
| 281 | + strscpy(group->name, fb->info.name, |
| 282 | + sizeof(group->name)); |
| 283 | + } else { |
| 284 | + /* when overlapping, concat names */ |
| 285 | + strlcat(group->name, ", ", sizeof(group->name)); |
| 286 | + strlcat(group->name, fb->info.name, |
| 287 | + sizeof(group->name)); |
| 288 | + } |
| 289 | + } |
| 290 | + } |
| 291 | +} |
| 292 | + |
| 293 | +/* release the client resources */ |
| 294 | +static void seq_ump_client_free(struct seq_ump_client *client) |
| 295 | +{ |
| 296 | + if (client->seq_client >= 0) |
| 297 | + snd_seq_delete_kernel_client(client->seq_client); |
| 298 | + |
| 299 | + client->ump->seq_ops = NULL; |
| 300 | + client->ump->seq_client = NULL; |
| 301 | + |
| 302 | + kfree(client); |
| 303 | +} |
| 304 | + |
| 305 | +/* update the MIDI version for the given client */ |
| 306 | +static void setup_client_midi_version(struct seq_ump_client *client) |
| 307 | +{ |
| 308 | + struct snd_seq_client *cptr; |
| 309 | + |
| 310 | + cptr = snd_seq_kernel_client_get(client->seq_client); |
| 311 | + if (!cptr) |
| 312 | + return; |
| 313 | + if (client->ump->info.protocol & SNDRV_UMP_EP_INFO_PROTO_MIDI2) |
| 314 | + cptr->midi_version = SNDRV_SEQ_CLIENT_UMP_MIDI_2_0; |
| 315 | + else |
| 316 | + cptr->midi_version = SNDRV_SEQ_CLIENT_UMP_MIDI_1_0; |
| 317 | + snd_seq_kernel_client_put(cptr); |
| 318 | +} |
| 319 | + |
| 320 | +static const struct snd_seq_ump_ops seq_ump_ops = { |
| 321 | + .input_receive = seq_ump_input_receive, |
| 322 | +}; |
| 323 | + |
| 324 | +/* create a sequencer client and ports for the given UMP endpoint */ |
| 325 | +static int snd_seq_ump_probe(struct device *_dev) |
| 326 | +{ |
| 327 | + struct snd_seq_device *dev = to_seq_dev(_dev); |
| 328 | + struct snd_ump_endpoint *ump = dev->private_data; |
| 329 | + struct snd_card *card = dev->card; |
| 330 | + struct seq_ump_client *client; |
| 331 | + int p, err; |
| 332 | + |
| 333 | + client = kzalloc(sizeof(*client), GFP_KERNEL); |
| 334 | + if (!client) |
| 335 | + return -ENOMEM; |
| 336 | + |
| 337 | + client->ump = ump; |
| 338 | + |
| 339 | + client->seq_client = |
| 340 | + snd_seq_create_kernel_client(card, ump->core.device, |
| 341 | + ump->core.name); |
| 342 | + if (client->seq_client < 0) { |
| 343 | + err = client->seq_client; |
| 344 | + goto error; |
| 345 | + } |
| 346 | + |
| 347 | + setup_client_midi_version(client); |
| 348 | + update_group_attrs(client); |
| 349 | + |
| 350 | + for (p = 0; p < SNDRV_UMP_MAX_GROUPS; p++) { |
| 351 | + err = seq_ump_group_init(client, p); |
| 352 | + if (err < 0) |
| 353 | + goto error; |
| 354 | + } |
| 355 | + |
| 356 | + ump->seq_client = client; |
| 357 | + ump->seq_ops = &seq_ump_ops; |
| 358 | + return 0; |
| 359 | + |
| 360 | + error: |
| 361 | + seq_ump_client_free(client); |
| 362 | + return err; |
| 363 | +} |
| 364 | + |
| 365 | +/* remove a sequencer client */ |
| 366 | +static int snd_seq_ump_remove(struct device *_dev) |
| 367 | +{ |
| 368 | + struct snd_seq_device *dev = to_seq_dev(_dev); |
| 369 | + struct snd_ump_endpoint *ump = dev->private_data; |
| 370 | + |
| 371 | + if (ump->seq_client) |
| 372 | + seq_ump_client_free(ump->seq_client); |
| 373 | + return 0; |
| 374 | +} |
| 375 | + |
| 376 | +static struct snd_seq_driver seq_ump_driver = { |
| 377 | + .driver = { |
| 378 | + .name = KBUILD_MODNAME, |
| 379 | + .probe = snd_seq_ump_probe, |
| 380 | + .remove = snd_seq_ump_remove, |
| 381 | + }, |
| 382 | + .id = SNDRV_SEQ_DEV_ID_UMP, |
| 383 | + .argsize = 0, |
| 384 | +}; |
| 385 | + |
| 386 | +module_snd_seq_driver(seq_ump_driver); |
| 387 | + |
| 388 | +MODULE_DESCRIPTION("ALSA sequencer client for UMP rawmidi"); |
| 389 | +MODULE_LICENSE("GPL"); |