| /* | |
| * OSS compatible sequencer driver | |
| * | |
| * open/close and reset interface | |
| * | |
| * Copyright (C) 1998-1999 Takashi Iwai <tiwai@suse.de> | |
| * | |
| * This program is free software; you can redistribute it and/or modify | |
| * it under the terms of the GNU General Public License as published by | |
| * the Free Software Foundation; either version 2 of the License, or | |
| * (at your option) any later version. | |
| * | |
| * This program is distributed in the hope that it will be useful, | |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| * GNU General Public License for more details. | |
| * | |
| * You should have received a copy of the GNU General Public License | |
| * along with this program; if not, write to the Free Software | |
| * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |
| */ | |
| #include "seq_oss_device.h" | |
| #include "seq_oss_synth.h" | |
| #include "seq_oss_midi.h" | |
| #include "seq_oss_writeq.h" | |
| #include "seq_oss_readq.h" | |
| #include "seq_oss_timer.h" | |
| #include "seq_oss_event.h" | |
| #include <linux/init.h> | |
| #include <linux/moduleparam.h> | |
| #include <linux/slab.h> | |
| /* | |
| * common variables | |
| */ | |
| static int maxqlen = SNDRV_SEQ_OSS_MAX_QLEN; | |
| module_param(maxqlen, int, 0444); | |
| MODULE_PARM_DESC(maxqlen, "maximum queue length"); | |
| static int system_client = -1; /* ALSA sequencer client number */ | |
| static int system_port = -1; | |
| static int num_clients; | |
| static struct seq_oss_devinfo *client_table[SNDRV_SEQ_OSS_MAX_CLIENTS]; | |
| /* | |
| * prototypes | |
| */ | |
| static int receive_announce(struct snd_seq_event *ev, int direct, void *private, int atomic, int hop); | |
| static int translate_mode(struct file *file); | |
| static int create_port(struct seq_oss_devinfo *dp); | |
| static int delete_port(struct seq_oss_devinfo *dp); | |
| static int alloc_seq_queue(struct seq_oss_devinfo *dp); | |
| static int delete_seq_queue(int queue); | |
| static void free_devinfo(void *private); | |
| #define call_ctl(type,rec) snd_seq_kernel_client_ctl(system_client, type, rec) | |
| /* | |
| * create sequencer client for OSS sequencer | |
| */ | |
| int __init | |
| snd_seq_oss_create_client(void) | |
| { | |
| int rc; | |
| struct snd_seq_port_info *port; | |
| struct snd_seq_port_callback port_callback; | |
| port = kmalloc(sizeof(*port), GFP_KERNEL); | |
| if (!port) { | |
| rc = -ENOMEM; | |
| goto __error; | |
| } | |
| /* create ALSA client */ | |
| rc = snd_seq_create_kernel_client(NULL, SNDRV_SEQ_CLIENT_OSS, | |
| "OSS sequencer"); | |
| if (rc < 0) | |
| goto __error; | |
| system_client = rc; | |
| debug_printk(("new client = %d\n", rc)); | |
| /* look up midi devices */ | |
| snd_seq_oss_midi_lookup_ports(system_client); | |
| /* create annoucement receiver port */ | |
| memset(port, 0, sizeof(*port)); | |
| strcpy(port->name, "Receiver"); | |
| port->addr.client = system_client; | |
| port->capability = SNDRV_SEQ_PORT_CAP_WRITE; /* receive only */ | |
| port->type = 0; | |
| memset(&port_callback, 0, sizeof(port_callback)); | |
| /* don't set port_callback.owner here. otherwise the module counter | |
| * is incremented and we can no longer release the module.. | |
| */ | |
| port_callback.event_input = receive_announce; | |
| port->kernel = &port_callback; | |
| call_ctl(SNDRV_SEQ_IOCTL_CREATE_PORT, port); | |
| if ((system_port = port->addr.port) >= 0) { | |
| struct snd_seq_port_subscribe subs; | |
| memset(&subs, 0, sizeof(subs)); | |
| subs.sender.client = SNDRV_SEQ_CLIENT_SYSTEM; | |
| subs.sender.port = SNDRV_SEQ_PORT_SYSTEM_ANNOUNCE; | |
| subs.dest.client = system_client; | |
| subs.dest.port = system_port; | |
| call_ctl(SNDRV_SEQ_IOCTL_SUBSCRIBE_PORT, &subs); | |
| } | |
| rc = 0; | |
| __error: | |
| kfree(port); | |
| return rc; | |
| } | |
| /* | |
| * receive annoucement from system port, and check the midi device | |
| */ | |
| static int | |
| receive_announce(struct snd_seq_event *ev, int direct, void *private, int atomic, int hop) | |
| { | |
| struct snd_seq_port_info pinfo; | |
| if (atomic) | |
| return 0; /* it must not happen */ | |
| switch (ev->type) { | |
| case SNDRV_SEQ_EVENT_PORT_START: | |
| case SNDRV_SEQ_EVENT_PORT_CHANGE: | |
| if (ev->data.addr.client == system_client) | |
| break; /* ignore myself */ | |
| memset(&pinfo, 0, sizeof(pinfo)); | |
| pinfo.addr = ev->data.addr; | |
| if (call_ctl(SNDRV_SEQ_IOCTL_GET_PORT_INFO, &pinfo) >= 0) | |
| snd_seq_oss_midi_check_new_port(&pinfo); | |
| break; | |
| case SNDRV_SEQ_EVENT_PORT_EXIT: | |
| if (ev->data.addr.client == system_client) | |
| break; /* ignore myself */ | |
| snd_seq_oss_midi_check_exit_port(ev->data.addr.client, | |
| ev->data.addr.port); | |
| break; | |
| } | |
| return 0; | |
| } | |
| /* | |
| * delete OSS sequencer client | |
| */ | |
| int | |
| snd_seq_oss_delete_client(void) | |
| { | |
| if (system_client >= 0) | |
| snd_seq_delete_kernel_client(system_client); | |
| snd_seq_oss_midi_clear_all(); | |
| return 0; | |
| } | |
| /* | |
| * open sequencer device | |
| */ | |
| int | |
| snd_seq_oss_open(struct file *file, int level) | |
| { | |
| int i, rc; | |
| struct seq_oss_devinfo *dp; | |
| dp = kzalloc(sizeof(*dp), GFP_KERNEL); | |
| if (!dp) { | |
| snd_printk(KERN_ERR "can't malloc device info\n"); | |
| return -ENOMEM; | |
| } | |
| debug_printk(("oss_open: dp = %p\n", dp)); | |
| dp->cseq = system_client; | |
| dp->port = -1; | |
| dp->queue = -1; | |
| for (i = 0; i < SNDRV_SEQ_OSS_MAX_CLIENTS; i++) { | |
| if (client_table[i] == NULL) | |
| break; | |
| } | |
| dp->index = i; | |
| if (i >= SNDRV_SEQ_OSS_MAX_CLIENTS) { | |
| snd_printk(KERN_ERR "too many applications\n"); | |
| rc = -ENOMEM; | |
| goto _error; | |
| } | |
| /* look up synth and midi devices */ | |
| snd_seq_oss_synth_setup(dp); | |
| snd_seq_oss_midi_setup(dp); | |
| if (dp->synth_opened == 0 && dp->max_mididev == 0) { | |
| /* snd_printk(KERN_ERR "no device found\n"); */ | |
| rc = -ENODEV; | |
| goto _error; | |
| } | |
| /* create port */ | |
| debug_printk(("create new port\n")); | |
| rc = create_port(dp); | |
| if (rc < 0) { | |
| snd_printk(KERN_ERR "can't create port\n"); | |
| goto _error; | |
| } | |
| /* allocate queue */ | |
| debug_printk(("allocate queue\n")); | |
| rc = alloc_seq_queue(dp); | |
| if (rc < 0) | |
| goto _error; | |
| /* set address */ | |
| dp->addr.client = dp->cseq; | |
| dp->addr.port = dp->port; | |
| /*dp->addr.queue = dp->queue;*/ | |
| /*dp->addr.channel = 0;*/ | |
| dp->seq_mode = level; | |
| /* set up file mode */ | |
| dp->file_mode = translate_mode(file); | |
| /* initialize read queue */ | |
| debug_printk(("initialize read queue\n")); | |
| if (is_read_mode(dp->file_mode)) { | |
| dp->readq = snd_seq_oss_readq_new(dp, maxqlen); | |
| if (!dp->readq) { | |
| rc = -ENOMEM; | |
| goto _error; | |
| } | |
| } | |
| /* initialize write queue */ | |
| debug_printk(("initialize write queue\n")); | |
| if (is_write_mode(dp->file_mode)) { | |
| dp->writeq = snd_seq_oss_writeq_new(dp, maxqlen); | |
| if (!dp->writeq) { | |
| rc = -ENOMEM; | |
| goto _error; | |
| } | |
| } | |
| /* initialize timer */ | |
| debug_printk(("initialize timer\n")); | |
| dp->timer = snd_seq_oss_timer_new(dp); | |
| if (!dp->timer) { | |
| snd_printk(KERN_ERR "can't alloc timer\n"); | |
| rc = -ENOMEM; | |
| goto _error; | |
| } | |
| debug_printk(("timer initialized\n")); | |
| /* set private data pointer */ | |
| file->private_data = dp; | |
| /* set up for mode2 */ | |
| if (level == SNDRV_SEQ_OSS_MODE_MUSIC) | |
| snd_seq_oss_synth_setup_midi(dp); | |
| else if (is_read_mode(dp->file_mode)) | |
| snd_seq_oss_midi_open_all(dp, SNDRV_SEQ_OSS_FILE_READ); | |
| client_table[dp->index] = dp; | |
| num_clients++; | |
| debug_printk(("open done\n")); | |
| return 0; | |
| _error: | |
| snd_seq_oss_synth_cleanup(dp); | |
| snd_seq_oss_midi_cleanup(dp); | |
| delete_seq_queue(dp->queue); | |
| delete_port(dp); | |
| return rc; | |
| } | |
| /* | |
| * translate file flags to private mode | |
| */ | |
| static int | |
| translate_mode(struct file *file) | |
| { | |
| int file_mode = 0; | |
| if ((file->f_flags & O_ACCMODE) != O_RDONLY) | |
| file_mode |= SNDRV_SEQ_OSS_FILE_WRITE; | |
| if ((file->f_flags & O_ACCMODE) != O_WRONLY) | |
| file_mode |= SNDRV_SEQ_OSS_FILE_READ; | |
| if (file->f_flags & O_NONBLOCK) | |
| file_mode |= SNDRV_SEQ_OSS_FILE_NONBLOCK; | |
| return file_mode; | |
| } | |
| /* | |
| * create sequencer port | |
| */ | |
| static int | |
| create_port(struct seq_oss_devinfo *dp) | |
| { | |
| int rc; | |
| struct snd_seq_port_info port; | |
| struct snd_seq_port_callback callback; | |
| memset(&port, 0, sizeof(port)); | |
| port.addr.client = dp->cseq; | |
| sprintf(port.name, "Sequencer-%d", dp->index); | |
| port.capability = SNDRV_SEQ_PORT_CAP_READ|SNDRV_SEQ_PORT_CAP_WRITE; /* no subscription */ | |
| port.type = SNDRV_SEQ_PORT_TYPE_SPECIFIC; | |
| port.midi_channels = 128; | |
| port.synth_voices = 128; | |
| memset(&callback, 0, sizeof(callback)); | |
| callback.owner = THIS_MODULE; | |
| callback.private_data = dp; | |
| callback.event_input = snd_seq_oss_event_input; | |
| callback.private_free = free_devinfo; | |
| port.kernel = &callback; | |
| rc = call_ctl(SNDRV_SEQ_IOCTL_CREATE_PORT, &port); | |
| if (rc < 0) | |
| return rc; | |
| dp->port = port.addr.port; | |
| debug_printk(("new port = %d\n", port.addr.port)); | |
| return 0; | |
| } | |
| /* | |
| * delete ALSA port | |
| */ | |
| static int | |
| delete_port(struct seq_oss_devinfo *dp) | |
| { | |
| if (dp->port < 0) { | |
| kfree(dp); | |
| return 0; | |
| } | |
| debug_printk(("delete_port %i\n", dp->port)); | |
| return snd_seq_event_port_detach(dp->cseq, dp->port); | |
| } | |
| /* | |
| * allocate a queue | |
| */ | |
| static int | |
| alloc_seq_queue(struct seq_oss_devinfo *dp) | |
| { | |
| struct snd_seq_queue_info qinfo; | |
| int rc; | |
| memset(&qinfo, 0, sizeof(qinfo)); | |
| qinfo.owner = system_client; | |
| qinfo.locked = 1; | |
| strcpy(qinfo.name, "OSS Sequencer Emulation"); | |
| if ((rc = call_ctl(SNDRV_SEQ_IOCTL_CREATE_QUEUE, &qinfo)) < 0) | |
| return rc; | |
| dp->queue = qinfo.queue; | |
| return 0; | |
| } | |
| /* | |
| * release queue | |
| */ | |
| static int | |
| delete_seq_queue(int queue) | |
| { | |
| struct snd_seq_queue_info qinfo; | |
| int rc; | |
| if (queue < 0) | |
| return 0; | |
| memset(&qinfo, 0, sizeof(qinfo)); | |
| qinfo.queue = queue; | |
| rc = call_ctl(SNDRV_SEQ_IOCTL_DELETE_QUEUE, &qinfo); | |
| if (rc < 0) | |
| printk(KERN_ERR "seq-oss: unable to delete queue %d (%d)\n", queue, rc); | |
| return rc; | |
| } | |
| /* | |
| * free device informations - private_free callback of port | |
| */ | |
| static void | |
| free_devinfo(void *private) | |
| { | |
| struct seq_oss_devinfo *dp = (struct seq_oss_devinfo *)private; | |
| if (dp->timer) | |
| snd_seq_oss_timer_delete(dp->timer); | |
| if (dp->writeq) | |
| snd_seq_oss_writeq_delete(dp->writeq); | |
| if (dp->readq) | |
| snd_seq_oss_readq_delete(dp->readq); | |
| kfree(dp); | |
| } | |
| /* | |
| * close sequencer device | |
| */ | |
| void | |
| snd_seq_oss_release(struct seq_oss_devinfo *dp) | |
| { | |
| int queue; | |
| client_table[dp->index] = NULL; | |
| num_clients--; | |
| debug_printk(("resetting..\n")); | |
| snd_seq_oss_reset(dp); | |
| debug_printk(("cleaning up..\n")); | |
| snd_seq_oss_synth_cleanup(dp); | |
| snd_seq_oss_midi_cleanup(dp); | |
| /* clear slot */ | |
| debug_printk(("releasing resource..\n")); | |
| queue = dp->queue; | |
| if (dp->port >= 0) | |
| delete_port(dp); | |
| delete_seq_queue(queue); | |
| debug_printk(("release done\n")); | |
| } | |
| /* | |
| * Wait until the queue is empty (if we don't have nonblock) | |
| */ | |
| void | |
| snd_seq_oss_drain_write(struct seq_oss_devinfo *dp) | |
| { | |
| if (! dp->timer->running) | |
| return; | |
| if (is_write_mode(dp->file_mode) && !is_nonblock_mode(dp->file_mode) && | |
| dp->writeq) { | |
| debug_printk(("syncing..\n")); | |
| while (snd_seq_oss_writeq_sync(dp->writeq)) | |
| ; | |
| } | |
| } | |
| /* | |
| * reset sequencer devices | |
| */ | |
| void | |
| snd_seq_oss_reset(struct seq_oss_devinfo *dp) | |
| { | |
| int i; | |
| /* reset all synth devices */ | |
| for (i = 0; i < dp->max_synthdev; i++) | |
| snd_seq_oss_synth_reset(dp, i); | |
| /* reset all midi devices */ | |
| if (dp->seq_mode != SNDRV_SEQ_OSS_MODE_MUSIC) { | |
| for (i = 0; i < dp->max_mididev; i++) | |
| snd_seq_oss_midi_reset(dp, i); | |
| } | |
| /* remove queues */ | |
| if (dp->readq) | |
| snd_seq_oss_readq_clear(dp->readq); | |
| if (dp->writeq) | |
| snd_seq_oss_writeq_clear(dp->writeq); | |
| /* reset timer */ | |
| snd_seq_oss_timer_stop(dp->timer); | |
| } | |
| #ifdef CONFIG_PROC_FS | |
| /* | |
| * misc. functions for proc interface | |
| */ | |
| char * | |
| enabled_str(int bool) | |
| { | |
| return bool ? "enabled" : "disabled"; | |
| } | |
| static char * | |
| filemode_str(int val) | |
| { | |
| static char *str[] = { | |
| "none", "read", "write", "read/write", | |
| }; | |
| return str[val & SNDRV_SEQ_OSS_FILE_ACMODE]; | |
| } | |
| /* | |
| * proc interface | |
| */ | |
| void | |
| snd_seq_oss_system_info_read(struct snd_info_buffer *buf) | |
| { | |
| int i; | |
| struct seq_oss_devinfo *dp; | |
| snd_iprintf(buf, "ALSA client number %d\n", system_client); | |
| snd_iprintf(buf, "ALSA receiver port %d\n", system_port); | |
| snd_iprintf(buf, "\nNumber of applications: %d\n", num_clients); | |
| for (i = 0; i < num_clients; i++) { | |
| snd_iprintf(buf, "\nApplication %d: ", i); | |
| if ((dp = client_table[i]) == NULL) { | |
| snd_iprintf(buf, "*empty*\n"); | |
| continue; | |
| } | |
| snd_iprintf(buf, "port %d : queue %d\n", dp->port, dp->queue); | |
| snd_iprintf(buf, " sequencer mode = %s : file open mode = %s\n", | |
| (dp->seq_mode ? "music" : "synth"), | |
| filemode_str(dp->file_mode)); | |
| if (dp->seq_mode) | |
| snd_iprintf(buf, " timer tempo = %d, timebase = %d\n", | |
| dp->timer->oss_tempo, dp->timer->oss_timebase); | |
| snd_iprintf(buf, " max queue length %d\n", maxqlen); | |
| if (is_read_mode(dp->file_mode) && dp->readq) | |
| snd_seq_oss_readq_info_read(dp->readq, buf); | |
| } | |
| } | |
| #endif /* CONFIG_PROC_FS */ |