Commit | Line | Data |
---|---|---|
1da177e4 LT |
1 | /* |
2 | * OSS compatible sequencer driver | |
3 | * | |
4 | * open/close and reset interface | |
5 | * | |
6 | * Copyright (C) 1998-1999 Takashi Iwai <tiwai@suse.de> | |
7 | * | |
8 | * This program is free software; you can redistribute it and/or modify | |
9 | * it under the terms of the GNU General Public License as published by | |
10 | * the Free Software Foundation; either version 2 of the License, or | |
11 | * (at your option) any later version. | |
12 | * | |
13 | * This program is distributed in the hope that it will be useful, | |
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
16 | * GNU General Public License for more details. | |
17 | * | |
18 | * You should have received a copy of the GNU General Public License | |
19 | * along with this program; if not, write to the Free Software | |
20 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |
21 | */ | |
22 | ||
23 | #include "seq_oss_device.h" | |
24 | #include "seq_oss_synth.h" | |
25 | #include "seq_oss_midi.h" | |
26 | #include "seq_oss_writeq.h" | |
27 | #include "seq_oss_readq.h" | |
28 | #include "seq_oss_timer.h" | |
29 | #include "seq_oss_event.h" | |
30 | #include <linux/init.h> | |
d81a6d71 | 31 | #include <linux/export.h> |
1da177e4 | 32 | #include <linux/moduleparam.h> |
5a0e3ad6 | 33 | #include <linux/slab.h> |
256ca9c3 | 34 | #include <linux/workqueue.h> |
1da177e4 LT |
35 | |
36 | /* | |
37 | * common variables | |
38 | */ | |
39 | static int maxqlen = SNDRV_SEQ_OSS_MAX_QLEN; | |
40 | module_param(maxqlen, int, 0444); | |
41 | MODULE_PARM_DESC(maxqlen, "maximum queue length"); | |
42 | ||
43 | static int system_client = -1; /* ALSA sequencer client number */ | |
44 | static int system_port = -1; | |
45 | ||
46 | static int num_clients; | |
080dece3 | 47 | static struct seq_oss_devinfo *client_table[SNDRV_SEQ_OSS_MAX_CLIENTS]; |
1da177e4 LT |
48 | |
49 | ||
50 | /* | |
51 | * prototypes | |
52 | */ | |
080dece3 | 53 | static int receive_announce(struct snd_seq_event *ev, int direct, void *private, int atomic, int hop); |
1da177e4 | 54 | static int translate_mode(struct file *file); |
080dece3 TI |
55 | static int create_port(struct seq_oss_devinfo *dp); |
56 | static int delete_port(struct seq_oss_devinfo *dp); | |
57 | static int alloc_seq_queue(struct seq_oss_devinfo *dp); | |
1da177e4 LT |
58 | static int delete_seq_queue(int queue); |
59 | static void free_devinfo(void *private); | |
60 | ||
61 | #define call_ctl(type,rec) snd_seq_kernel_client_ctl(system_client, type, rec) | |
62 | ||
63 | ||
256ca9c3 TI |
64 | /* call snd_seq_oss_midi_lookup_ports() asynchronously */ |
65 | static void async_call_lookup_ports(struct work_struct *work) | |
66 | { | |
67 | snd_seq_oss_midi_lookup_ports(system_client); | |
68 | } | |
69 | ||
70 | static DECLARE_WORK(async_lookup_work, async_call_lookup_ports); | |
71 | ||
1da177e4 LT |
72 | /* |
73 | * create sequencer client for OSS sequencer | |
74 | */ | |
75 | int __init | |
76 | snd_seq_oss_create_client(void) | |
77 | { | |
78 | int rc; | |
080dece3 TI |
79 | struct snd_seq_port_info *port; |
80 | struct snd_seq_port_callback port_callback; | |
1da177e4 | 81 | |
1da177e4 | 82 | port = kmalloc(sizeof(*port), GFP_KERNEL); |
7b6d9245 | 83 | if (!port) { |
1da177e4 LT |
84 | rc = -ENOMEM; |
85 | goto __error; | |
86 | } | |
87 | ||
88 | /* create ALSA client */ | |
7b6d9245 CL |
89 | rc = snd_seq_create_kernel_client(NULL, SNDRV_SEQ_CLIENT_OSS, |
90 | "OSS sequencer"); | |
1da177e4 LT |
91 | if (rc < 0) |
92 | goto __error; | |
93 | ||
94 | system_client = rc; | |
95 | debug_printk(("new client = %d\n", rc)); | |
96 | ||
1da177e4 LT |
97 | /* create annoucement receiver port */ |
98 | memset(port, 0, sizeof(*port)); | |
99 | strcpy(port->name, "Receiver"); | |
100 | port->addr.client = system_client; | |
101 | port->capability = SNDRV_SEQ_PORT_CAP_WRITE; /* receive only */ | |
102 | port->type = 0; | |
103 | ||
104 | memset(&port_callback, 0, sizeof(port_callback)); | |
105 | /* don't set port_callback.owner here. otherwise the module counter | |
106 | * is incremented and we can no longer release the module.. | |
107 | */ | |
108 | port_callback.event_input = receive_announce; | |
109 | port->kernel = &port_callback; | |
110 | ||
111 | call_ctl(SNDRV_SEQ_IOCTL_CREATE_PORT, port); | |
112 | if ((system_port = port->addr.port) >= 0) { | |
080dece3 | 113 | struct snd_seq_port_subscribe subs; |
1da177e4 LT |
114 | |
115 | memset(&subs, 0, sizeof(subs)); | |
116 | subs.sender.client = SNDRV_SEQ_CLIENT_SYSTEM; | |
117 | subs.sender.port = SNDRV_SEQ_PORT_SYSTEM_ANNOUNCE; | |
118 | subs.dest.client = system_client; | |
119 | subs.dest.port = system_port; | |
120 | call_ctl(SNDRV_SEQ_IOCTL_SUBSCRIBE_PORT, &subs); | |
121 | } | |
122 | rc = 0; | |
123 | ||
256ca9c3 TI |
124 | /* look up midi devices */ |
125 | schedule_work(&async_lookup_work); | |
126 | ||
1da177e4 LT |
127 | __error: |
128 | kfree(port); | |
1da177e4 LT |
129 | return rc; |
130 | } | |
131 | ||
132 | ||
133 | /* | |
134 | * receive annoucement from system port, and check the midi device | |
135 | */ | |
136 | static int | |
080dece3 | 137 | receive_announce(struct snd_seq_event *ev, int direct, void *private, int atomic, int hop) |
1da177e4 | 138 | { |
080dece3 | 139 | struct snd_seq_port_info pinfo; |
1da177e4 LT |
140 | |
141 | if (atomic) | |
142 | return 0; /* it must not happen */ | |
143 | ||
144 | switch (ev->type) { | |
145 | case SNDRV_SEQ_EVENT_PORT_START: | |
146 | case SNDRV_SEQ_EVENT_PORT_CHANGE: | |
147 | if (ev->data.addr.client == system_client) | |
148 | break; /* ignore myself */ | |
149 | memset(&pinfo, 0, sizeof(pinfo)); | |
150 | pinfo.addr = ev->data.addr; | |
151 | if (call_ctl(SNDRV_SEQ_IOCTL_GET_PORT_INFO, &pinfo) >= 0) | |
152 | snd_seq_oss_midi_check_new_port(&pinfo); | |
153 | break; | |
154 | ||
155 | case SNDRV_SEQ_EVENT_PORT_EXIT: | |
156 | if (ev->data.addr.client == system_client) | |
157 | break; /* ignore myself */ | |
158 | snd_seq_oss_midi_check_exit_port(ev->data.addr.client, | |
159 | ev->data.addr.port); | |
160 | break; | |
161 | } | |
162 | return 0; | |
163 | } | |
164 | ||
165 | ||
166 | /* | |
167 | * delete OSS sequencer client | |
168 | */ | |
169 | int | |
170 | snd_seq_oss_delete_client(void) | |
171 | { | |
256ca9c3 | 172 | cancel_work_sync(&async_lookup_work); |
1da177e4 LT |
173 | if (system_client >= 0) |
174 | snd_seq_delete_kernel_client(system_client); | |
175 | ||
176 | snd_seq_oss_midi_clear_all(); | |
177 | ||
178 | return 0; | |
179 | } | |
180 | ||
181 | ||
182 | /* | |
183 | * open sequencer device | |
184 | */ | |
185 | int | |
186 | snd_seq_oss_open(struct file *file, int level) | |
187 | { | |
188 | int i, rc; | |
080dece3 | 189 | struct seq_oss_devinfo *dp; |
1da177e4 | 190 | |
7034632d ET |
191 | dp = kzalloc(sizeof(*dp), GFP_KERNEL); |
192 | if (!dp) { | |
1da177e4 LT |
193 | snd_printk(KERN_ERR "can't malloc device info\n"); |
194 | return -ENOMEM; | |
195 | } | |
196 | debug_printk(("oss_open: dp = %p\n", dp)); | |
197 | ||
7034632d ET |
198 | dp->cseq = system_client; |
199 | dp->port = -1; | |
200 | dp->queue = -1; | |
201 | ||
1da177e4 LT |
202 | for (i = 0; i < SNDRV_SEQ_OSS_MAX_CLIENTS; i++) { |
203 | if (client_table[i] == NULL) | |
204 | break; | |
205 | } | |
7034632d ET |
206 | |
207 | dp->index = i; | |
1da177e4 LT |
208 | if (i >= SNDRV_SEQ_OSS_MAX_CLIENTS) { |
209 | snd_printk(KERN_ERR "too many applications\n"); | |
7034632d ET |
210 | rc = -ENOMEM; |
211 | goto _error; | |
1da177e4 LT |
212 | } |
213 | ||
1da177e4 LT |
214 | /* look up synth and midi devices */ |
215 | snd_seq_oss_synth_setup(dp); | |
216 | snd_seq_oss_midi_setup(dp); | |
217 | ||
218 | if (dp->synth_opened == 0 && dp->max_mididev == 0) { | |
219 | /* snd_printk(KERN_ERR "no device found\n"); */ | |
220 | rc = -ENODEV; | |
221 | goto _error; | |
222 | } | |
223 | ||
224 | /* create port */ | |
225 | debug_printk(("create new port\n")); | |
7034632d ET |
226 | rc = create_port(dp); |
227 | if (rc < 0) { | |
1da177e4 LT |
228 | snd_printk(KERN_ERR "can't create port\n"); |
229 | goto _error; | |
230 | } | |
231 | ||
232 | /* allocate queue */ | |
233 | debug_printk(("allocate queue\n")); | |
7034632d ET |
234 | rc = alloc_seq_queue(dp); |
235 | if (rc < 0) | |
1da177e4 LT |
236 | goto _error; |
237 | ||
238 | /* set address */ | |
239 | dp->addr.client = dp->cseq; | |
240 | dp->addr.port = dp->port; | |
241 | /*dp->addr.queue = dp->queue;*/ | |
242 | /*dp->addr.channel = 0;*/ | |
243 | ||
244 | dp->seq_mode = level; | |
245 | ||
246 | /* set up file mode */ | |
247 | dp->file_mode = translate_mode(file); | |
248 | ||
249 | /* initialize read queue */ | |
250 | debug_printk(("initialize read queue\n")); | |
251 | if (is_read_mode(dp->file_mode)) { | |
7034632d ET |
252 | dp->readq = snd_seq_oss_readq_new(dp, maxqlen); |
253 | if (!dp->readq) { | |
1da177e4 LT |
254 | rc = -ENOMEM; |
255 | goto _error; | |
256 | } | |
257 | } | |
258 | ||
259 | /* initialize write queue */ | |
260 | debug_printk(("initialize write queue\n")); | |
261 | if (is_write_mode(dp->file_mode)) { | |
262 | dp->writeq = snd_seq_oss_writeq_new(dp, maxqlen); | |
7034632d | 263 | if (!dp->writeq) { |
1da177e4 LT |
264 | rc = -ENOMEM; |
265 | goto _error; | |
266 | } | |
267 | } | |
268 | ||
269 | /* initialize timer */ | |
270 | debug_printk(("initialize timer\n")); | |
7034632d ET |
271 | dp->timer = snd_seq_oss_timer_new(dp); |
272 | if (!dp->timer) { | |
1da177e4 LT |
273 | snd_printk(KERN_ERR "can't alloc timer\n"); |
274 | rc = -ENOMEM; | |
275 | goto _error; | |
276 | } | |
277 | debug_printk(("timer initialized\n")); | |
278 | ||
279 | /* set private data pointer */ | |
280 | file->private_data = dp; | |
281 | ||
282 | /* set up for mode2 */ | |
283 | if (level == SNDRV_SEQ_OSS_MODE_MUSIC) | |
284 | snd_seq_oss_synth_setup_midi(dp); | |
285 | else if (is_read_mode(dp->file_mode)) | |
286 | snd_seq_oss_midi_open_all(dp, SNDRV_SEQ_OSS_FILE_READ); | |
287 | ||
288 | client_table[dp->index] = dp; | |
289 | num_clients++; | |
290 | ||
291 | debug_printk(("open done\n")); | |
292 | return 0; | |
293 | ||
294 | _error: | |
295 | snd_seq_oss_synth_cleanup(dp); | |
296 | snd_seq_oss_midi_cleanup(dp); | |
7034632d | 297 | delete_seq_queue(dp->queue); |
27f7ad53 | 298 | delete_port(dp); |
1da177e4 LT |
299 | |
300 | return rc; | |
301 | } | |
302 | ||
303 | /* | |
304 | * translate file flags to private mode | |
305 | */ | |
306 | static int | |
307 | translate_mode(struct file *file) | |
308 | { | |
309 | int file_mode = 0; | |
310 | if ((file->f_flags & O_ACCMODE) != O_RDONLY) | |
311 | file_mode |= SNDRV_SEQ_OSS_FILE_WRITE; | |
312 | if ((file->f_flags & O_ACCMODE) != O_WRONLY) | |
313 | file_mode |= SNDRV_SEQ_OSS_FILE_READ; | |
314 | if (file->f_flags & O_NONBLOCK) | |
315 | file_mode |= SNDRV_SEQ_OSS_FILE_NONBLOCK; | |
316 | return file_mode; | |
317 | } | |
318 | ||
319 | ||
320 | /* | |
321 | * create sequencer port | |
322 | */ | |
323 | static int | |
080dece3 | 324 | create_port(struct seq_oss_devinfo *dp) |
1da177e4 LT |
325 | { |
326 | int rc; | |
080dece3 TI |
327 | struct snd_seq_port_info port; |
328 | struct snd_seq_port_callback callback; | |
1da177e4 LT |
329 | |
330 | memset(&port, 0, sizeof(port)); | |
331 | port.addr.client = dp->cseq; | |
332 | sprintf(port.name, "Sequencer-%d", dp->index); | |
333 | port.capability = SNDRV_SEQ_PORT_CAP_READ|SNDRV_SEQ_PORT_CAP_WRITE; /* no subscription */ | |
334 | port.type = SNDRV_SEQ_PORT_TYPE_SPECIFIC; | |
335 | port.midi_channels = 128; | |
336 | port.synth_voices = 128; | |
337 | ||
338 | memset(&callback, 0, sizeof(callback)); | |
339 | callback.owner = THIS_MODULE; | |
340 | callback.private_data = dp; | |
341 | callback.event_input = snd_seq_oss_event_input; | |
342 | callback.private_free = free_devinfo; | |
343 | port.kernel = &callback; | |
344 | ||
345 | rc = call_ctl(SNDRV_SEQ_IOCTL_CREATE_PORT, &port); | |
346 | if (rc < 0) | |
347 | return rc; | |
348 | ||
349 | dp->port = port.addr.port; | |
350 | debug_printk(("new port = %d\n", port.addr.port)); | |
351 | ||
352 | return 0; | |
353 | } | |
354 | ||
355 | /* | |
356 | * delete ALSA port | |
357 | */ | |
358 | static int | |
080dece3 | 359 | delete_port(struct seq_oss_devinfo *dp) |
1da177e4 | 360 | { |
27f7ad53 TI |
361 | if (dp->port < 0) { |
362 | kfree(dp); | |
1da177e4 | 363 | return 0; |
27f7ad53 | 364 | } |
1da177e4 LT |
365 | |
366 | debug_printk(("delete_port %i\n", dp->port)); | |
367 | return snd_seq_event_port_detach(dp->cseq, dp->port); | |
368 | } | |
369 | ||
370 | /* | |
371 | * allocate a queue | |
372 | */ | |
373 | static int | |
080dece3 | 374 | alloc_seq_queue(struct seq_oss_devinfo *dp) |
1da177e4 | 375 | { |
080dece3 | 376 | struct snd_seq_queue_info qinfo; |
1da177e4 LT |
377 | int rc; |
378 | ||
379 | memset(&qinfo, 0, sizeof(qinfo)); | |
380 | qinfo.owner = system_client; | |
381 | qinfo.locked = 1; | |
382 | strcpy(qinfo.name, "OSS Sequencer Emulation"); | |
383 | if ((rc = call_ctl(SNDRV_SEQ_IOCTL_CREATE_QUEUE, &qinfo)) < 0) | |
384 | return rc; | |
385 | dp->queue = qinfo.queue; | |
386 | return 0; | |
387 | } | |
388 | ||
389 | /* | |
390 | * release queue | |
391 | */ | |
392 | static int | |
393 | delete_seq_queue(int queue) | |
394 | { | |
080dece3 | 395 | struct snd_seq_queue_info qinfo; |
1da177e4 LT |
396 | int rc; |
397 | ||
398 | if (queue < 0) | |
399 | return 0; | |
400 | memset(&qinfo, 0, sizeof(qinfo)); | |
401 | qinfo.queue = queue; | |
402 | rc = call_ctl(SNDRV_SEQ_IOCTL_DELETE_QUEUE, &qinfo); | |
403 | if (rc < 0) | |
404 | printk(KERN_ERR "seq-oss: unable to delete queue %d (%d)\n", queue, rc); | |
405 | return rc; | |
406 | } | |
407 | ||
408 | ||
409 | /* | |
410 | * free device informations - private_free callback of port | |
411 | */ | |
412 | static void | |
413 | free_devinfo(void *private) | |
414 | { | |
080dece3 | 415 | struct seq_oss_devinfo *dp = (struct seq_oss_devinfo *)private; |
1da177e4 LT |
416 | |
417 | if (dp->timer) | |
418 | snd_seq_oss_timer_delete(dp->timer); | |
419 | ||
420 | if (dp->writeq) | |
421 | snd_seq_oss_writeq_delete(dp->writeq); | |
422 | ||
423 | if (dp->readq) | |
424 | snd_seq_oss_readq_delete(dp->readq); | |
425 | ||
426 | kfree(dp); | |
427 | } | |
428 | ||
429 | ||
430 | /* | |
431 | * close sequencer device | |
432 | */ | |
433 | void | |
080dece3 | 434 | snd_seq_oss_release(struct seq_oss_devinfo *dp) |
1da177e4 LT |
435 | { |
436 | int queue; | |
437 | ||
438 | client_table[dp->index] = NULL; | |
439 | num_clients--; | |
440 | ||
441 | debug_printk(("resetting..\n")); | |
442 | snd_seq_oss_reset(dp); | |
443 | ||
444 | debug_printk(("cleaning up..\n")); | |
445 | snd_seq_oss_synth_cleanup(dp); | |
446 | snd_seq_oss_midi_cleanup(dp); | |
447 | ||
448 | /* clear slot */ | |
449 | debug_printk(("releasing resource..\n")); | |
450 | queue = dp->queue; | |
451 | if (dp->port >= 0) | |
452 | delete_port(dp); | |
453 | delete_seq_queue(queue); | |
454 | ||
455 | debug_printk(("release done\n")); | |
456 | } | |
457 | ||
458 | ||
459 | /* | |
460 | * Wait until the queue is empty (if we don't have nonblock) | |
461 | */ | |
462 | void | |
080dece3 | 463 | snd_seq_oss_drain_write(struct seq_oss_devinfo *dp) |
1da177e4 LT |
464 | { |
465 | if (! dp->timer->running) | |
466 | return; | |
467 | if (is_write_mode(dp->file_mode) && !is_nonblock_mode(dp->file_mode) && | |
468 | dp->writeq) { | |
469 | debug_printk(("syncing..\n")); | |
470 | while (snd_seq_oss_writeq_sync(dp->writeq)) | |
471 | ; | |
472 | } | |
473 | } | |
474 | ||
475 | ||
476 | /* | |
477 | * reset sequencer devices | |
478 | */ | |
479 | void | |
080dece3 | 480 | snd_seq_oss_reset(struct seq_oss_devinfo *dp) |
1da177e4 LT |
481 | { |
482 | int i; | |
483 | ||
484 | /* reset all synth devices */ | |
485 | for (i = 0; i < dp->max_synthdev; i++) | |
486 | snd_seq_oss_synth_reset(dp, i); | |
487 | ||
488 | /* reset all midi devices */ | |
489 | if (dp->seq_mode != SNDRV_SEQ_OSS_MODE_MUSIC) { | |
490 | for (i = 0; i < dp->max_mididev; i++) | |
491 | snd_seq_oss_midi_reset(dp, i); | |
492 | } | |
493 | ||
494 | /* remove queues */ | |
495 | if (dp->readq) | |
496 | snd_seq_oss_readq_clear(dp->readq); | |
497 | if (dp->writeq) | |
498 | snd_seq_oss_writeq_clear(dp->writeq); | |
499 | ||
500 | /* reset timer */ | |
501 | snd_seq_oss_timer_stop(dp->timer); | |
502 | } | |
503 | ||
504 | ||
04f141a8 | 505 | #ifdef CONFIG_PROC_FS |
1da177e4 LT |
506 | /* |
507 | * misc. functions for proc interface | |
508 | */ | |
509 | char * | |
510 | enabled_str(int bool) | |
511 | { | |
512 | return bool ? "enabled" : "disabled"; | |
513 | } | |
514 | ||
515 | static char * | |
516 | filemode_str(int val) | |
517 | { | |
518 | static char *str[] = { | |
519 | "none", "read", "write", "read/write", | |
520 | }; | |
521 | return str[val & SNDRV_SEQ_OSS_FILE_ACMODE]; | |
522 | } | |
523 | ||
524 | ||
525 | /* | |
526 | * proc interface | |
527 | */ | |
528 | void | |
080dece3 | 529 | snd_seq_oss_system_info_read(struct snd_info_buffer *buf) |
1da177e4 LT |
530 | { |
531 | int i; | |
080dece3 | 532 | struct seq_oss_devinfo *dp; |
1da177e4 LT |
533 | |
534 | snd_iprintf(buf, "ALSA client number %d\n", system_client); | |
535 | snd_iprintf(buf, "ALSA receiver port %d\n", system_port); | |
536 | ||
537 | snd_iprintf(buf, "\nNumber of applications: %d\n", num_clients); | |
538 | for (i = 0; i < num_clients; i++) { | |
539 | snd_iprintf(buf, "\nApplication %d: ", i); | |
540 | if ((dp = client_table[i]) == NULL) { | |
541 | snd_iprintf(buf, "*empty*\n"); | |
542 | continue; | |
543 | } | |
544 | snd_iprintf(buf, "port %d : queue %d\n", dp->port, dp->queue); | |
545 | snd_iprintf(buf, " sequencer mode = %s : file open mode = %s\n", | |
546 | (dp->seq_mode ? "music" : "synth"), | |
547 | filemode_str(dp->file_mode)); | |
548 | if (dp->seq_mode) | |
549 | snd_iprintf(buf, " timer tempo = %d, timebase = %d\n", | |
550 | dp->timer->oss_tempo, dp->timer->oss_timebase); | |
551 | snd_iprintf(buf, " max queue length %d\n", maxqlen); | |
552 | if (is_read_mode(dp->file_mode) && dp->readq) | |
553 | snd_seq_oss_readq_info_read(dp->readq, buf); | |
554 | } | |
555 | } | |
04f141a8 | 556 | #endif /* CONFIG_PROC_FS */ |