501 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * OSS compatible sequencer driver4 *5 * open/close and reset interface6 *7 * Copyright (C) 1998-1999 Takashi Iwai <tiwai@suse.de>8 */9 10#include "seq_oss_device.h"11#include "seq_oss_synth.h"12#include "seq_oss_midi.h"13#include "seq_oss_writeq.h"14#include "seq_oss_readq.h"15#include "seq_oss_timer.h"16#include "seq_oss_event.h"17#include <linux/init.h>18#include <linux/export.h>19#include <linux/moduleparam.h>20#include <linux/slab.h>21#include <linux/workqueue.h>22 23/*24 * common variables25 */26static int maxqlen = SNDRV_SEQ_OSS_MAX_QLEN;27module_param(maxqlen, int, 0444);28MODULE_PARM_DESC(maxqlen, "maximum queue length");29 30static int system_client = -1; /* ALSA sequencer client number */31static int system_port = -1;32 33static int num_clients;34static struct seq_oss_devinfo *client_table[SNDRV_SEQ_OSS_MAX_CLIENTS];35 36 37/*38 * prototypes39 */40static int receive_announce(struct snd_seq_event *ev, int direct, void *private, int atomic, int hop);41static int translate_mode(struct file *file);42static int create_port(struct seq_oss_devinfo *dp);43static int delete_port(struct seq_oss_devinfo *dp);44static int alloc_seq_queue(struct seq_oss_devinfo *dp);45static int delete_seq_queue(int queue);46static void free_devinfo(void *private);47 48#define call_ctl(type,rec) snd_seq_kernel_client_ctl(system_client, type, rec)49 50 51/* call snd_seq_oss_midi_lookup_ports() asynchronously */52static void async_call_lookup_ports(struct work_struct *work)53{54 snd_seq_oss_midi_lookup_ports(system_client);55}56 57static DECLARE_WORK(async_lookup_work, async_call_lookup_ports);58 59/*60 * create sequencer client for OSS sequencer61 */62int __init63snd_seq_oss_create_client(void)64{65 int rc;66 struct snd_seq_port_info *port __free(kfree) = NULL;67 struct snd_seq_port_callback port_callback;68 69 port = kzalloc(sizeof(*port), GFP_KERNEL);70 if (!port)71 return -ENOMEM;72 73 /* create ALSA client */74 rc = snd_seq_create_kernel_client(NULL, SNDRV_SEQ_CLIENT_OSS,75 "OSS sequencer");76 if (rc < 0)77 return rc;78 79 system_client = rc;80 81 /* create announcement receiver port */82 strcpy(port->name, "Receiver");83 port->addr.client = system_client;84 port->capability = SNDRV_SEQ_PORT_CAP_WRITE; /* receive only */85 port->type = 0;86 87 memset(&port_callback, 0, sizeof(port_callback));88 /* don't set port_callback.owner here. otherwise the module counter89 * is incremented and we can no longer release the module..90 */91 port_callback.event_input = receive_announce;92 port->kernel = &port_callback;93 94 if (call_ctl(SNDRV_SEQ_IOCTL_CREATE_PORT, port) >= 0) {95 struct snd_seq_port_subscribe subs;96 97 system_port = port->addr.port;98 memset(&subs, 0, sizeof(subs));99 subs.sender.client = SNDRV_SEQ_CLIENT_SYSTEM;100 subs.sender.port = SNDRV_SEQ_PORT_SYSTEM_ANNOUNCE;101 subs.dest.client = system_client;102 subs.dest.port = system_port;103 call_ctl(SNDRV_SEQ_IOCTL_SUBSCRIBE_PORT, &subs);104 }105 106 /* look up midi devices */107 schedule_work(&async_lookup_work);108 109 return 0;110}111 112 113/*114 * receive annoucement from system port, and check the midi device115 */116static int117receive_announce(struct snd_seq_event *ev, int direct, void *private, int atomic, int hop)118{119 struct snd_seq_port_info pinfo;120 121 if (atomic)122 return 0; /* it must not happen */123 124 switch (ev->type) {125 case SNDRV_SEQ_EVENT_PORT_START:126 case SNDRV_SEQ_EVENT_PORT_CHANGE:127 if (ev->data.addr.client == system_client)128 break; /* ignore myself */129 memset(&pinfo, 0, sizeof(pinfo));130 pinfo.addr = ev->data.addr;131 if (call_ctl(SNDRV_SEQ_IOCTL_GET_PORT_INFO, &pinfo) >= 0)132 snd_seq_oss_midi_check_new_port(&pinfo);133 break;134 135 case SNDRV_SEQ_EVENT_PORT_EXIT:136 if (ev->data.addr.client == system_client)137 break; /* ignore myself */138 snd_seq_oss_midi_check_exit_port(ev->data.addr.client,139 ev->data.addr.port);140 break;141 }142 return 0;143}144 145 146/*147 * delete OSS sequencer client148 */149int150snd_seq_oss_delete_client(void)151{152 cancel_work_sync(&async_lookup_work);153 if (system_client >= 0)154 snd_seq_delete_kernel_client(system_client);155 156 snd_seq_oss_midi_clear_all();157 158 return 0;159}160 161 162/*163 * open sequencer device164 */165int166snd_seq_oss_open(struct file *file, int level)167{168 int i, rc;169 struct seq_oss_devinfo *dp;170 171 dp = kzalloc(sizeof(*dp), GFP_KERNEL);172 if (!dp)173 return -ENOMEM;174 175 dp->cseq = system_client;176 dp->port = -1;177 dp->queue = -1;178 179 for (i = 0; i < SNDRV_SEQ_OSS_MAX_CLIENTS; i++) {180 if (client_table[i] == NULL)181 break;182 }183 184 dp->index = i;185 if (i >= SNDRV_SEQ_OSS_MAX_CLIENTS) {186 pr_debug("ALSA: seq_oss: too many applications\n");187 rc = -ENOMEM;188 goto _error;189 }190 191 /* look up synth and midi devices */192 snd_seq_oss_synth_setup(dp);193 snd_seq_oss_midi_setup(dp);194 195 if (dp->synth_opened == 0 && dp->max_mididev == 0) {196 /* pr_err("ALSA: seq_oss: no device found\n"); */197 rc = -ENODEV;198 goto _error;199 }200 201 /* create port */202 rc = create_port(dp);203 if (rc < 0) {204 pr_err("ALSA: seq_oss: can't create port\n");205 goto _error;206 }207 208 /* allocate queue */209 rc = alloc_seq_queue(dp);210 if (rc < 0)211 goto _error;212 213 /* set address */214 dp->addr.client = dp->cseq;215 dp->addr.port = dp->port;216 /*dp->addr.queue = dp->queue;*/217 /*dp->addr.channel = 0;*/218 219 dp->seq_mode = level;220 221 /* set up file mode */222 dp->file_mode = translate_mode(file);223 224 /* initialize read queue */225 if (is_read_mode(dp->file_mode)) {226 dp->readq = snd_seq_oss_readq_new(dp, maxqlen);227 if (!dp->readq) {228 rc = -ENOMEM;229 goto _error;230 }231 }232 233 /* initialize write queue */234 if (is_write_mode(dp->file_mode)) {235 dp->writeq = snd_seq_oss_writeq_new(dp, maxqlen);236 if (!dp->writeq) {237 rc = -ENOMEM;238 goto _error;239 }240 }241 242 /* initialize timer */243 dp->timer = snd_seq_oss_timer_new(dp);244 if (!dp->timer) {245 pr_err("ALSA: seq_oss: can't alloc timer\n");246 rc = -ENOMEM;247 goto _error;248 }249 250 /* set private data pointer */251 file->private_data = dp;252 253 /* set up for mode2 */254 if (level == SNDRV_SEQ_OSS_MODE_MUSIC)255 snd_seq_oss_synth_setup_midi(dp);256 else if (is_read_mode(dp->file_mode))257 snd_seq_oss_midi_open_all(dp, SNDRV_SEQ_OSS_FILE_READ);258 259 client_table[dp->index] = dp;260 num_clients++;261 262 return 0;263 264 _error:265 snd_seq_oss_synth_cleanup(dp);266 snd_seq_oss_midi_cleanup(dp);267 delete_seq_queue(dp->queue);268 delete_port(dp);269 270 return rc;271}272 273/*274 * translate file flags to private mode275 */276static int277translate_mode(struct file *file)278{279 int file_mode = 0;280 if ((file->f_flags & O_ACCMODE) != O_RDONLY)281 file_mode |= SNDRV_SEQ_OSS_FILE_WRITE;282 if ((file->f_flags & O_ACCMODE) != O_WRONLY)283 file_mode |= SNDRV_SEQ_OSS_FILE_READ;284 if (file->f_flags & O_NONBLOCK)285 file_mode |= SNDRV_SEQ_OSS_FILE_NONBLOCK;286 return file_mode;287}288 289 290/*291 * create sequencer port292 */293static int294create_port(struct seq_oss_devinfo *dp)295{296 int rc;297 struct snd_seq_port_info port;298 struct snd_seq_port_callback callback;299 300 memset(&port, 0, sizeof(port));301 port.addr.client = dp->cseq;302 sprintf(port.name, "Sequencer-%d", dp->index);303 port.capability = SNDRV_SEQ_PORT_CAP_READ|SNDRV_SEQ_PORT_CAP_WRITE; /* no subscription */304 port.type = SNDRV_SEQ_PORT_TYPE_SPECIFIC;305 port.midi_channels = 128;306 port.synth_voices = 128;307 308 memset(&callback, 0, sizeof(callback));309 callback.owner = THIS_MODULE;310 callback.private_data = dp;311 callback.event_input = snd_seq_oss_event_input;312 callback.private_free = free_devinfo;313 port.kernel = &callback;314 315 rc = call_ctl(SNDRV_SEQ_IOCTL_CREATE_PORT, &port);316 if (rc < 0)317 return rc;318 319 dp->port = port.addr.port;320 321 return 0;322}323 324/*325 * delete ALSA port326 */327static int328delete_port(struct seq_oss_devinfo *dp)329{330 if (dp->port < 0) {331 kfree(dp);332 return 0;333 }334 335 return snd_seq_event_port_detach(dp->cseq, dp->port);336}337 338/*339 * allocate a queue340 */341static int342alloc_seq_queue(struct seq_oss_devinfo *dp)343{344 struct snd_seq_queue_info qinfo;345 int rc;346 347 memset(&qinfo, 0, sizeof(qinfo));348 qinfo.owner = system_client;349 qinfo.locked = 1;350 strcpy(qinfo.name, "OSS Sequencer Emulation");351 rc = call_ctl(SNDRV_SEQ_IOCTL_CREATE_QUEUE, &qinfo);352 if (rc < 0)353 return rc;354 dp->queue = qinfo.queue;355 return 0;356}357 358/*359 * release queue360 */361static int362delete_seq_queue(int queue)363{364 struct snd_seq_queue_info qinfo;365 int rc;366 367 if (queue < 0)368 return 0;369 memset(&qinfo, 0, sizeof(qinfo));370 qinfo.queue = queue;371 rc = call_ctl(SNDRV_SEQ_IOCTL_DELETE_QUEUE, &qinfo);372 if (rc < 0)373 pr_err("ALSA: seq_oss: unable to delete queue %d (%d)\n", queue, rc);374 return rc;375}376 377 378/*379 * free device informations - private_free callback of port380 */381static void382free_devinfo(void *private)383{384 struct seq_oss_devinfo *dp = (struct seq_oss_devinfo *)private;385 386 snd_seq_oss_timer_delete(dp->timer);387 388 snd_seq_oss_writeq_delete(dp->writeq);389 390 snd_seq_oss_readq_delete(dp->readq);391 392 kfree(dp);393}394 395 396/*397 * close sequencer device398 */399void400snd_seq_oss_release(struct seq_oss_devinfo *dp)401{402 int queue;403 404 client_table[dp->index] = NULL;405 num_clients--;406 407 snd_seq_oss_reset(dp);408 409 snd_seq_oss_synth_cleanup(dp);410 snd_seq_oss_midi_cleanup(dp);411 412 /* clear slot */413 queue = dp->queue;414 if (dp->port >= 0)415 delete_port(dp);416 delete_seq_queue(queue);417}418 419 420/*421 * reset sequencer devices422 */423void424snd_seq_oss_reset(struct seq_oss_devinfo *dp)425{426 int i;427 428 /* reset all synth devices */429 for (i = 0; i < dp->max_synthdev; i++)430 snd_seq_oss_synth_reset(dp, i);431 432 /* reset all midi devices */433 if (dp->seq_mode != SNDRV_SEQ_OSS_MODE_MUSIC) {434 for (i = 0; i < dp->max_mididev; i++)435 snd_seq_oss_midi_reset(dp, i);436 }437 438 /* remove queues */439 if (dp->readq)440 snd_seq_oss_readq_clear(dp->readq);441 if (dp->writeq)442 snd_seq_oss_writeq_clear(dp->writeq);443 444 /* reset timer */445 snd_seq_oss_timer_stop(dp->timer);446}447 448#ifdef CONFIG_SND_PROC_FS449/*450 * misc. functions for proc interface451 */452char *453enabled_str(bool b)454{455 return b ? "enabled" : "disabled";456}457 458static const char *459filemode_str(int val)460{461 static const char * const str[] = {462 "none", "read", "write", "read/write",463 };464 return str[val & SNDRV_SEQ_OSS_FILE_ACMODE];465}466 467 468/*469 * proc interface470 */471void472snd_seq_oss_system_info_read(struct snd_info_buffer *buf)473{474 int i;475 struct seq_oss_devinfo *dp;476 477 snd_iprintf(buf, "ALSA client number %d\n", system_client);478 snd_iprintf(buf, "ALSA receiver port %d\n", system_port);479 480 snd_iprintf(buf, "\nNumber of applications: %d\n", num_clients);481 for (i = 0; i < num_clients; i++) {482 snd_iprintf(buf, "\nApplication %d: ", i);483 dp = client_table[i];484 if (!dp) {485 snd_iprintf(buf, "*empty*\n");486 continue;487 }488 snd_iprintf(buf, "port %d : queue %d\n", dp->port, dp->queue);489 snd_iprintf(buf, " sequencer mode = %s : file open mode = %s\n",490 (dp->seq_mode ? "music" : "synth"),491 filemode_str(dp->file_mode));492 if (dp->seq_mode)493 snd_iprintf(buf, " timer tempo = %d, timebase = %d\n",494 dp->timer->oss_tempo, dp->timer->oss_timebase);495 snd_iprintf(buf, " max queue length %d\n", maxqlen);496 if (is_read_mode(dp->file_mode) && dp->readq)497 snd_seq_oss_readq_info_read(dp->readq, buf);498 }499}500#endif /* CONFIG_SND_PROC_FS */501