1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * OSS compatible sequencer driver |
4 | * |
5 | * open/close and reset interface |
6 | * |
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 variables |
25 | */ |
26 | static int maxqlen = SNDRV_SEQ_OSS_MAX_QLEN; |
27 | module_param(maxqlen, int, 0444); |
28 | MODULE_PARM_DESC(maxqlen, "maximum queue length" ); |
29 | |
30 | static int system_client = -1; /* ALSA sequencer client number */ |
31 | static int system_port = -1; |
32 | |
33 | static int num_clients; |
34 | static struct seq_oss_devinfo *client_table[SNDRV_SEQ_OSS_MAX_CLIENTS]; |
35 | |
36 | |
37 | /* |
38 | * prototypes |
39 | */ |
40 | static int receive_announce(struct snd_seq_event *ev, int direct, void *private, int atomic, int hop); |
41 | static int translate_mode(struct file *file); |
42 | static int create_port(struct seq_oss_devinfo *dp); |
43 | static int delete_port(struct seq_oss_devinfo *dp); |
44 | static int alloc_seq_queue(struct seq_oss_devinfo *dp); |
45 | static int delete_seq_queue(int queue); |
46 | static 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 */ |
52 | static void async_call_lookup_ports(struct work_struct *work) |
53 | { |
54 | snd_seq_oss_midi_lookup_ports(client: system_client); |
55 | } |
56 | |
57 | static DECLARE_WORK(async_lookup_work, async_call_lookup_ports); |
58 | |
59 | /* |
60 | * create sequencer client for OSS sequencer |
61 | */ |
62 | int __init |
63 | snd_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(size: 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 | name_fmt: "OSS sequencer" ); |
76 | if (rc < 0) |
77 | return rc; |
78 | |
79 | system_client = rc; |
80 | |
81 | /* create announcement receiver port */ |
82 | strcpy(p: port->name, q: "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 counter |
89 | * 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(work: &async_lookup_work); |
108 | |
109 | return 0; |
110 | } |
111 | |
112 | |
113 | /* |
114 | * receive annoucement from system port, and check the midi device |
115 | */ |
116 | static int |
117 | receive_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: &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(client: ev->data.addr.client, |
139 | port: ev->data.addr.port); |
140 | break; |
141 | } |
142 | return 0; |
143 | } |
144 | |
145 | |
146 | /* |
147 | * delete OSS sequencer client |
148 | */ |
149 | int |
150 | snd_seq_oss_delete_client(void) |
151 | { |
152 | cancel_work_sync(work: &async_lookup_work); |
153 | if (system_client >= 0) |
154 | snd_seq_delete_kernel_client(client: system_client); |
155 | |
156 | snd_seq_oss_midi_clear_all(); |
157 | |
158 | return 0; |
159 | } |
160 | |
161 | |
162 | /* |
163 | * open sequencer device |
164 | */ |
165 | int |
166 | snd_seq_oss_open(struct file *file, int level) |
167 | { |
168 | int i, rc; |
169 | struct seq_oss_devinfo *dp; |
170 | |
171 | dp = kzalloc(size: 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, maxlen: 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, maxlen: 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(queue: dp->queue); |
268 | delete_port(dp); |
269 | |
270 | return rc; |
271 | } |
272 | |
273 | /* |
274 | * translate file flags to private mode |
275 | */ |
276 | static int |
277 | translate_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 port |
292 | */ |
293 | static int |
294 | create_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(buf: port.name, fmt: "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 port |
326 | */ |
327 | static int |
328 | delete_port(struct seq_oss_devinfo *dp) |
329 | { |
330 | if (dp->port < 0) { |
331 | kfree(objp: dp); |
332 | return 0; |
333 | } |
334 | |
335 | return snd_seq_event_port_detach(client: dp->cseq, port: dp->port); |
336 | } |
337 | |
338 | /* |
339 | * allocate a queue |
340 | */ |
341 | static int |
342 | alloc_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(p: qinfo.name, q: "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 queue |
360 | */ |
361 | static int |
362 | delete_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 port |
380 | */ |
381 | static void |
382 | free_devinfo(void *private) |
383 | { |
384 | struct seq_oss_devinfo *dp = (struct seq_oss_devinfo *)private; |
385 | |
386 | snd_seq_oss_timer_delete(dp: dp->timer); |
387 | |
388 | snd_seq_oss_writeq_delete(q: dp->writeq); |
389 | |
390 | snd_seq_oss_readq_delete(q: dp->readq); |
391 | |
392 | kfree(objp: dp); |
393 | } |
394 | |
395 | |
396 | /* |
397 | * close sequencer device |
398 | */ |
399 | void |
400 | snd_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 devices |
422 | */ |
423 | void |
424 | snd_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, dev: 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, dev: i); |
436 | } |
437 | |
438 | /* remove queues */ |
439 | if (dp->readq) |
440 | snd_seq_oss_readq_clear(readq: dp->readq); |
441 | if (dp->writeq) |
442 | snd_seq_oss_writeq_clear(q: dp->writeq); |
443 | |
444 | /* reset timer */ |
445 | snd_seq_oss_timer_stop(timer: dp->timer); |
446 | } |
447 | |
448 | #ifdef CONFIG_SND_PROC_FS |
449 | /* |
450 | * misc. functions for proc interface |
451 | */ |
452 | char * |
453 | enabled_str(bool b) |
454 | { |
455 | return b ? "enabled" : "disabled" ; |
456 | } |
457 | |
458 | static const char * |
459 | filemode_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 interface |
470 | */ |
471 | void |
472 | snd_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(q: dp->readq, buf); |
498 | } |
499 | } |
500 | #endif /* CONFIG_SND_PROC_FS */ |
501 | |