714 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * OSS compatible sequencer driver4 *5 * MIDI device handlers6 *7 * Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de>8 */9 10#include <sound/asoundef.h>11#include "seq_oss_midi.h"12#include "seq_oss_readq.h"13#include "seq_oss_timer.h"14#include "seq_oss_event.h"15#include <sound/seq_midi_event.h>16#include "../seq_lock.h"17#include <linux/init.h>18#include <linux/slab.h>19#include <linux/nospec.h>20 21 22/*23 * constants24 */25#define SNDRV_SEQ_OSS_MAX_MIDI_NAME 3026 27/*28 * definition of midi device record29 */30struct seq_oss_midi {31 int seq_device; /* device number */32 int client; /* sequencer client number */33 int port; /* sequencer port number */34 unsigned int flags; /* port capability */35 int opened; /* flag for opening */36 unsigned char name[SNDRV_SEQ_OSS_MAX_MIDI_NAME];37 struct snd_midi_event *coder; /* MIDI event coder */38 struct seq_oss_devinfo *devinfo; /* assigned OSSseq device */39 snd_use_lock_t use_lock;40 struct mutex open_mutex;41};42 43 44/*45 * midi device table46 */47static int max_midi_devs;48static struct seq_oss_midi *midi_devs[SNDRV_SEQ_OSS_MAX_MIDI_DEVS];49 50static DEFINE_SPINLOCK(register_lock);51 52/*53 * prototypes54 */55static struct seq_oss_midi *get_mdev(int dev);56static struct seq_oss_midi *get_mididev(struct seq_oss_devinfo *dp, int dev);57static int send_synth_event(struct seq_oss_devinfo *dp, struct snd_seq_event *ev, int dev);58static int send_midi_event(struct seq_oss_devinfo *dp, struct snd_seq_event *ev, struct seq_oss_midi *mdev);59 60/*61 * look up the existing ports62 * this looks a very exhausting job.63 */64int65snd_seq_oss_midi_lookup_ports(int client)66{67 struct snd_seq_client_info *clinfo __free(kfree) = NULL;68 struct snd_seq_port_info *pinfo __free(kfree) = NULL;69 70 clinfo = kzalloc(sizeof(*clinfo), GFP_KERNEL);71 pinfo = kzalloc(sizeof(*pinfo), GFP_KERNEL);72 if (!clinfo || !pinfo)73 return -ENOMEM;74 clinfo->client = -1;75 while (snd_seq_kernel_client_ctl(client, SNDRV_SEQ_IOCTL_QUERY_NEXT_CLIENT, clinfo) == 0) {76 if (clinfo->client == client)77 continue; /* ignore myself */78 pinfo->addr.client = clinfo->client;79 pinfo->addr.port = -1;80 while (snd_seq_kernel_client_ctl(client, SNDRV_SEQ_IOCTL_QUERY_NEXT_PORT, pinfo) == 0)81 snd_seq_oss_midi_check_new_port(pinfo);82 }83 return 0;84}85 86 87/*88 */89static struct seq_oss_midi *90get_mdev(int dev)91{92 struct seq_oss_midi *mdev;93 unsigned long flags;94 95 spin_lock_irqsave(®ister_lock, flags);96 mdev = midi_devs[dev];97 if (mdev)98 snd_use_lock_use(&mdev->use_lock);99 spin_unlock_irqrestore(®ister_lock, flags);100 return mdev;101}102 103/*104 * look for the identical slot105 */106static struct seq_oss_midi *107find_slot(int client, int port)108{109 int i;110 struct seq_oss_midi *mdev;111 unsigned long flags;112 113 spin_lock_irqsave(®ister_lock, flags);114 for (i = 0; i < max_midi_devs; i++) {115 mdev = midi_devs[i];116 if (mdev && mdev->client == client && mdev->port == port) {117 /* found! */118 snd_use_lock_use(&mdev->use_lock);119 spin_unlock_irqrestore(®ister_lock, flags);120 return mdev;121 }122 }123 spin_unlock_irqrestore(®ister_lock, flags);124 return NULL;125}126 127 128#define PERM_WRITE (SNDRV_SEQ_PORT_CAP_WRITE|SNDRV_SEQ_PORT_CAP_SUBS_WRITE)129#define PERM_READ (SNDRV_SEQ_PORT_CAP_READ|SNDRV_SEQ_PORT_CAP_SUBS_READ)130/*131 * register a new port if it doesn't exist yet132 */133int134snd_seq_oss_midi_check_new_port(struct snd_seq_port_info *pinfo)135{136 int i;137 struct seq_oss_midi *mdev;138 unsigned long flags;139 140 /* the port must include generic midi */141 if (! (pinfo->type & SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC))142 return 0;143 /* either read or write subscribable */144 if ((pinfo->capability & PERM_WRITE) != PERM_WRITE &&145 (pinfo->capability & PERM_READ) != PERM_READ)146 return 0;147 148 /*149 * look for the identical slot150 */151 mdev = find_slot(pinfo->addr.client, pinfo->addr.port);152 if (mdev) {153 /* already exists */154 snd_use_lock_free(&mdev->use_lock);155 return 0;156 }157 158 /*159 * allocate midi info record160 */161 mdev = kzalloc(sizeof(*mdev), GFP_KERNEL);162 if (!mdev)163 return -ENOMEM;164 165 /* copy the port information */166 mdev->client = pinfo->addr.client;167 mdev->port = pinfo->addr.port;168 mdev->flags = pinfo->capability;169 mdev->opened = 0;170 snd_use_lock_init(&mdev->use_lock);171 mutex_init(&mdev->open_mutex);172 173 /* copy and truncate the name of synth device */174 strscpy(mdev->name, pinfo->name, sizeof(mdev->name));175 176 /* create MIDI coder */177 if (snd_midi_event_new(MAX_MIDI_EVENT_BUF, &mdev->coder) < 0) {178 pr_err("ALSA: seq_oss: can't malloc midi coder\n");179 kfree(mdev);180 return -ENOMEM;181 }182 /* OSS sequencer adds running status to all sequences */183 snd_midi_event_no_status(mdev->coder, 1);184 185 /*186 * look for en empty slot187 */188 spin_lock_irqsave(®ister_lock, flags);189 for (i = 0; i < max_midi_devs; i++) {190 if (midi_devs[i] == NULL)191 break;192 }193 if (i >= max_midi_devs) {194 if (max_midi_devs >= SNDRV_SEQ_OSS_MAX_MIDI_DEVS) {195 spin_unlock_irqrestore(®ister_lock, flags);196 snd_midi_event_free(mdev->coder);197 kfree(mdev);198 return -ENOMEM;199 }200 max_midi_devs++;201 }202 mdev->seq_device = i;203 midi_devs[mdev->seq_device] = mdev;204 spin_unlock_irqrestore(®ister_lock, flags);205 206 return 0;207}208 209/*210 * release the midi device if it was registered211 */212int213snd_seq_oss_midi_check_exit_port(int client, int port)214{215 struct seq_oss_midi *mdev;216 unsigned long flags;217 int index;218 219 mdev = find_slot(client, port);220 if (mdev) {221 spin_lock_irqsave(®ister_lock, flags);222 midi_devs[mdev->seq_device] = NULL;223 spin_unlock_irqrestore(®ister_lock, flags);224 snd_use_lock_free(&mdev->use_lock);225 snd_use_lock_sync(&mdev->use_lock);226 snd_midi_event_free(mdev->coder);227 kfree(mdev);228 }229 spin_lock_irqsave(®ister_lock, flags);230 for (index = max_midi_devs - 1; index >= 0; index--) {231 if (midi_devs[index])232 break;233 }234 max_midi_devs = index + 1;235 spin_unlock_irqrestore(®ister_lock, flags);236 return 0;237}238 239 240/*241 * release the midi device if it was registered242 */243void244snd_seq_oss_midi_clear_all(void)245{246 int i;247 struct seq_oss_midi *mdev;248 unsigned long flags;249 250 spin_lock_irqsave(®ister_lock, flags);251 for (i = 0; i < max_midi_devs; i++) {252 mdev = midi_devs[i];253 if (mdev) {254 snd_midi_event_free(mdev->coder);255 kfree(mdev);256 midi_devs[i] = NULL;257 }258 }259 max_midi_devs = 0;260 spin_unlock_irqrestore(®ister_lock, flags);261}262 263 264/*265 * set up midi tables266 */267void268snd_seq_oss_midi_setup(struct seq_oss_devinfo *dp)269{270 spin_lock_irq(®ister_lock);271 dp->max_mididev = max_midi_devs;272 spin_unlock_irq(®ister_lock);273}274 275/*276 * clean up midi tables277 */278void279snd_seq_oss_midi_cleanup(struct seq_oss_devinfo *dp)280{281 int i;282 for (i = 0; i < dp->max_mididev; i++)283 snd_seq_oss_midi_close(dp, i);284 dp->max_mididev = 0;285}286 287 288/*289 * open all midi devices. ignore errors.290 */291void292snd_seq_oss_midi_open_all(struct seq_oss_devinfo *dp, int file_mode)293{294 int i;295 for (i = 0; i < dp->max_mididev; i++)296 snd_seq_oss_midi_open(dp, i, file_mode);297}298 299 300/*301 * get the midi device information302 */303static struct seq_oss_midi *304get_mididev(struct seq_oss_devinfo *dp, int dev)305{306 if (dev < 0 || dev >= dp->max_mididev)307 return NULL;308 dev = array_index_nospec(dev, dp->max_mididev);309 return get_mdev(dev);310}311 312 313/*314 * open the midi device if not opened yet315 */316int317snd_seq_oss_midi_open(struct seq_oss_devinfo *dp, int dev, int fmode)318{319 int perm;320 struct seq_oss_midi *mdev;321 struct snd_seq_port_subscribe subs;322 int err;323 324 mdev = get_mididev(dp, dev);325 if (!mdev)326 return -ENODEV;327 328 mutex_lock(&mdev->open_mutex);329 /* already used? */330 if (mdev->opened && mdev->devinfo != dp) {331 err = -EBUSY;332 goto unlock;333 }334 335 perm = 0;336 if (is_write_mode(fmode))337 perm |= PERM_WRITE;338 if (is_read_mode(fmode))339 perm |= PERM_READ;340 perm &= mdev->flags;341 if (perm == 0) {342 err = -ENXIO;343 goto unlock;344 }345 346 /* already opened? */347 if ((mdev->opened & perm) == perm) {348 err = 0;349 goto unlock;350 }351 352 perm &= ~mdev->opened;353 354 memset(&subs, 0, sizeof(subs));355 356 if (perm & PERM_WRITE) {357 subs.sender = dp->addr;358 subs.dest.client = mdev->client;359 subs.dest.port = mdev->port;360 if (snd_seq_kernel_client_ctl(dp->cseq, SNDRV_SEQ_IOCTL_SUBSCRIBE_PORT, &subs) >= 0)361 mdev->opened |= PERM_WRITE;362 }363 if (perm & PERM_READ) {364 subs.sender.client = mdev->client;365 subs.sender.port = mdev->port;366 subs.dest = dp->addr;367 subs.flags = SNDRV_SEQ_PORT_SUBS_TIMESTAMP;368 subs.queue = dp->queue; /* queue for timestamps */369 if (snd_seq_kernel_client_ctl(dp->cseq, SNDRV_SEQ_IOCTL_SUBSCRIBE_PORT, &subs) >= 0)370 mdev->opened |= PERM_READ;371 }372 373 if (! mdev->opened) {374 err = -ENXIO;375 goto unlock;376 }377 378 mdev->devinfo = dp;379 err = 0;380 381 unlock:382 mutex_unlock(&mdev->open_mutex);383 snd_use_lock_free(&mdev->use_lock);384 return err;385}386 387/*388 * close the midi device if already opened389 */390int391snd_seq_oss_midi_close(struct seq_oss_devinfo *dp, int dev)392{393 struct seq_oss_midi *mdev;394 struct snd_seq_port_subscribe subs;395 396 mdev = get_mididev(dp, dev);397 if (!mdev)398 return -ENODEV;399 mutex_lock(&mdev->open_mutex);400 if (!mdev->opened || mdev->devinfo != dp)401 goto unlock;402 403 memset(&subs, 0, sizeof(subs));404 if (mdev->opened & PERM_WRITE) {405 subs.sender = dp->addr;406 subs.dest.client = mdev->client;407 subs.dest.port = mdev->port;408 snd_seq_kernel_client_ctl(dp->cseq, SNDRV_SEQ_IOCTL_UNSUBSCRIBE_PORT, &subs);409 }410 if (mdev->opened & PERM_READ) {411 subs.sender.client = mdev->client;412 subs.sender.port = mdev->port;413 subs.dest = dp->addr;414 snd_seq_kernel_client_ctl(dp->cseq, SNDRV_SEQ_IOCTL_UNSUBSCRIBE_PORT, &subs);415 }416 417 mdev->opened = 0;418 mdev->devinfo = NULL;419 420 unlock:421 mutex_unlock(&mdev->open_mutex);422 snd_use_lock_free(&mdev->use_lock);423 return 0;424}425 426/*427 * change seq capability flags to file mode flags428 */429int430snd_seq_oss_midi_filemode(struct seq_oss_devinfo *dp, int dev)431{432 struct seq_oss_midi *mdev;433 int mode;434 435 mdev = get_mididev(dp, dev);436 if (!mdev)437 return 0;438 439 mode = 0;440 if (mdev->opened & PERM_WRITE)441 mode |= SNDRV_SEQ_OSS_FILE_WRITE;442 if (mdev->opened & PERM_READ)443 mode |= SNDRV_SEQ_OSS_FILE_READ;444 445 snd_use_lock_free(&mdev->use_lock);446 return mode;447}448 449/*450 * reset the midi device and close it:451 * so far, only close the device.452 */453void454snd_seq_oss_midi_reset(struct seq_oss_devinfo *dp, int dev)455{456 struct seq_oss_midi *mdev;457 458 mdev = get_mididev(dp, dev);459 if (!mdev)460 return;461 if (! mdev->opened) {462 snd_use_lock_free(&mdev->use_lock);463 return;464 }465 466 if (mdev->opened & PERM_WRITE) {467 struct snd_seq_event ev;468 int c;469 470 memset(&ev, 0, sizeof(ev));471 ev.dest.client = mdev->client;472 ev.dest.port = mdev->port;473 ev.queue = dp->queue;474 ev.source.port = dp->port;475 if (dp->seq_mode == SNDRV_SEQ_OSS_MODE_SYNTH) {476 ev.type = SNDRV_SEQ_EVENT_SENSING;477 snd_seq_oss_dispatch(dp, &ev, 0, 0);478 }479 for (c = 0; c < 16; c++) {480 ev.type = SNDRV_SEQ_EVENT_CONTROLLER;481 ev.data.control.channel = c;482 ev.data.control.param = MIDI_CTL_ALL_NOTES_OFF;483 snd_seq_oss_dispatch(dp, &ev, 0, 0);484 if (dp->seq_mode == SNDRV_SEQ_OSS_MODE_MUSIC) {485 ev.data.control.param =486 MIDI_CTL_RESET_CONTROLLERS;487 snd_seq_oss_dispatch(dp, &ev, 0, 0);488 ev.type = SNDRV_SEQ_EVENT_PITCHBEND;489 ev.data.control.value = 0;490 snd_seq_oss_dispatch(dp, &ev, 0, 0);491 }492 }493 }494 // snd_seq_oss_midi_close(dp, dev);495 snd_use_lock_free(&mdev->use_lock);496}497 498 499/*500 * get client/port of the specified MIDI device501 */502void503snd_seq_oss_midi_get_addr(struct seq_oss_devinfo *dp, int dev, struct snd_seq_addr *addr)504{505 struct seq_oss_midi *mdev;506 507 mdev = get_mididev(dp, dev);508 if (!mdev)509 return;510 addr->client = mdev->client;511 addr->port = mdev->port;512 snd_use_lock_free(&mdev->use_lock);513}514 515 516/*517 * input callback - this can be atomic518 */519int520snd_seq_oss_midi_input(struct snd_seq_event *ev, int direct, void *private_data)521{522 struct seq_oss_devinfo *dp = (struct seq_oss_devinfo *)private_data;523 struct seq_oss_midi *mdev;524 int rc;525 526 if (dp->readq == NULL)527 return 0;528 mdev = find_slot(ev->source.client, ev->source.port);529 if (!mdev)530 return 0;531 if (! (mdev->opened & PERM_READ)) {532 snd_use_lock_free(&mdev->use_lock);533 return 0;534 }535 536 if (dp->seq_mode == SNDRV_SEQ_OSS_MODE_MUSIC)537 rc = send_synth_event(dp, ev, mdev->seq_device);538 else539 rc = send_midi_event(dp, ev, mdev);540 541 snd_use_lock_free(&mdev->use_lock);542 return rc;543}544 545/*546 * convert ALSA sequencer event to OSS synth event547 */548static int549send_synth_event(struct seq_oss_devinfo *dp, struct snd_seq_event *ev, int dev)550{551 union evrec ossev;552 553 memset(&ossev, 0, sizeof(ossev));554 555 switch (ev->type) {556 case SNDRV_SEQ_EVENT_NOTEON:557 ossev.v.cmd = MIDI_NOTEON; break;558 case SNDRV_SEQ_EVENT_NOTEOFF:559 ossev.v.cmd = MIDI_NOTEOFF; break;560 case SNDRV_SEQ_EVENT_KEYPRESS:561 ossev.v.cmd = MIDI_KEY_PRESSURE; break;562 case SNDRV_SEQ_EVENT_CONTROLLER:563 ossev.l.cmd = MIDI_CTL_CHANGE; break;564 case SNDRV_SEQ_EVENT_PGMCHANGE:565 ossev.l.cmd = MIDI_PGM_CHANGE; break;566 case SNDRV_SEQ_EVENT_CHANPRESS:567 ossev.l.cmd = MIDI_CHN_PRESSURE; break;568 case SNDRV_SEQ_EVENT_PITCHBEND:569 ossev.l.cmd = MIDI_PITCH_BEND; break;570 default:571 return 0; /* not supported */572 }573 574 ossev.v.dev = dev;575 576 switch (ev->type) {577 case SNDRV_SEQ_EVENT_NOTEON:578 case SNDRV_SEQ_EVENT_NOTEOFF:579 case SNDRV_SEQ_EVENT_KEYPRESS:580 ossev.v.code = EV_CHN_VOICE;581 ossev.v.note = ev->data.note.note;582 ossev.v.parm = ev->data.note.velocity;583 ossev.v.chn = ev->data.note.channel;584 break;585 case SNDRV_SEQ_EVENT_CONTROLLER:586 case SNDRV_SEQ_EVENT_PGMCHANGE:587 case SNDRV_SEQ_EVENT_CHANPRESS:588 ossev.l.code = EV_CHN_COMMON;589 ossev.l.p1 = ev->data.control.param;590 ossev.l.val = ev->data.control.value;591 ossev.l.chn = ev->data.control.channel;592 break;593 case SNDRV_SEQ_EVENT_PITCHBEND:594 ossev.l.code = EV_CHN_COMMON;595 ossev.l.val = ev->data.control.value + 8192;596 ossev.l.chn = ev->data.control.channel;597 break;598 }599 600 snd_seq_oss_readq_put_timestamp(dp->readq, ev->time.tick, dp->seq_mode);601 snd_seq_oss_readq_put_event(dp->readq, &ossev);602 603 return 0;604}605 606/*607 * decode event and send MIDI bytes to read queue608 */609static int610send_midi_event(struct seq_oss_devinfo *dp, struct snd_seq_event *ev, struct seq_oss_midi *mdev)611{612 char msg[32];613 int len;614 615 snd_seq_oss_readq_put_timestamp(dp->readq, ev->time.tick, dp->seq_mode);616 if (!dp->timer->running)617 len = snd_seq_oss_timer_start(dp->timer);618 if (ev->type == SNDRV_SEQ_EVENT_SYSEX) {619 snd_seq_oss_readq_sysex(dp->readq, mdev->seq_device, ev);620 snd_midi_event_reset_decode(mdev->coder);621 } else {622 len = snd_midi_event_decode(mdev->coder, msg, sizeof(msg), ev);623 if (len > 0)624 snd_seq_oss_readq_puts(dp->readq, mdev->seq_device, msg, len);625 }626 627 return 0;628}629 630 631/*632 * dump midi data633 * return 0 : enqueued634 * non-zero : invalid - ignored635 */636int637snd_seq_oss_midi_putc(struct seq_oss_devinfo *dp, int dev, unsigned char c, struct snd_seq_event *ev)638{639 struct seq_oss_midi *mdev;640 641 mdev = get_mididev(dp, dev);642 if (!mdev)643 return -ENODEV;644 if (snd_midi_event_encode_byte(mdev->coder, c, ev)) {645 snd_seq_oss_fill_addr(dp, ev, mdev->client, mdev->port);646 snd_use_lock_free(&mdev->use_lock);647 return 0;648 }649 snd_use_lock_free(&mdev->use_lock);650 return -EINVAL;651}652 653/*654 * create OSS compatible midi_info record655 */656int657snd_seq_oss_midi_make_info(struct seq_oss_devinfo *dp, int dev, struct midi_info *inf)658{659 struct seq_oss_midi *mdev;660 661 mdev = get_mididev(dp, dev);662 if (!mdev)663 return -ENXIO;664 inf->device = dev;665 inf->dev_type = 0; /* FIXME: ?? */666 inf->capabilities = 0; /* FIXME: ?? */667 strscpy(inf->name, mdev->name, sizeof(inf->name));668 snd_use_lock_free(&mdev->use_lock);669 return 0;670}671 672 673#ifdef CONFIG_SND_PROC_FS674/*675 * proc interface676 */677static char *678capmode_str(int val)679{680 val &= PERM_READ|PERM_WRITE;681 if (val == (PERM_READ|PERM_WRITE))682 return "read/write";683 else if (val == PERM_READ)684 return "read";685 else if (val == PERM_WRITE)686 return "write";687 else688 return "none";689}690 691void692snd_seq_oss_midi_info_read(struct snd_info_buffer *buf)693{694 int i;695 struct seq_oss_midi *mdev;696 697 snd_iprintf(buf, "\nNumber of MIDI devices: %d\n", max_midi_devs);698 for (i = 0; i < max_midi_devs; i++) {699 snd_iprintf(buf, "\nmidi %d: ", i);700 mdev = get_mdev(i);701 if (mdev == NULL) {702 snd_iprintf(buf, "*empty*\n");703 continue;704 }705 snd_iprintf(buf, "[%s] ALSA port %d:%d\n", mdev->name,706 mdev->client, mdev->port);707 snd_iprintf(buf, " capability %s / opened %s\n",708 capmode_str(mdev->flags),709 capmode_str(mdev->opened));710 snd_use_lock_free(&mdev->use_lock);711 }712}713#endif /* CONFIG_SND_PROC_FS */714