1 /**
2 * @file dsp-ctl.c
3 * @brief CTL External plugin implementation.
4 * <p>
5 * Copyright (C) 2006 Nokia Corporation
6 * <p>
7 * Contact: Eduardo Bezerra Valentin <eduardo.valentin@indt.org.br>
8 *
9 * This library is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Library General Public
11 * License as published by the Free Software Foundation; either
12 * version 2 of the License, or (at your option) any later version.
13 *
14 * This library is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Library General Public License for more details.
18 *
19 * You should have received a copy of the GNU Library General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22 * */
23 #include <stdio.h>
24 #include <sys/ioctl.h>
25 #include <alsa/asoundlib.h>
26 #include <alsa/control_external.h>
27 #include <string.h>
28 #include "list.h"
29 #include "debug.h"
30 #include "dsp-protocol.h"
31 #include "constants.h"
32
33 #define PLAYBACK_VOLUME_CONTROL_NAME "PCM Playback Volume"
34 #define PLAYBACK_MUTE_CONTROL_NAME "PCM Playback Switch"
35 #define RECORDING_CONTROL_NAME "Capture Switch"
36
37 /**
38 * data structure to represent a dsp task device node.
39 */
40 typedef struct {
41 dsp_protocol_t *dsp_protocol;
42 char *name;
43 int channels;
44 struct list_head list;
45 } control_list_t;
46
47 /**
48 * data structure to represent this plugin information.
49 */
50 typedef struct snd_ctl_dsp {
51 snd_ctl_ext_t ext;
52 int num_playbacks;
53 int num_recordings;
54 control_list_t **controls;
55 control_list_t playback_devices;
56 control_list_t recording_devices;
57 } snd_ctl_dsp_t;
58
59 static snd_ctl_dsp_t *free_ref;
60 /**
61 * @param control_list control list to be freed.
62 *
63 * It passes through this control list and frees
64 * all its nodes.
65 *
66 * @return zero. success.
67 */
free_control_list(control_list_t * control_list)68 static int free_control_list(control_list_t * control_list)
69 {
70 struct list_head *pos, *q;
71 control_list_t *tmp;
72 list_for_each_safe(pos, q, &control_list->list) {
73 tmp = list_entry(pos, control_list_t, list);
74 list_del(pos);
75 free(tmp->name);
76 close(tmp->dsp_protocol->fd);
77 dsp_protocol_destroy(&(tmp->dsp_protocol));
78 free(tmp);
79 }
80 return 0;
81 }
82
83 /**
84 * @param ext snd_ctl_ext_t structure.
85 *
86 * It is the close event handler for this plugin.
87 * It frees all the allocated memory.
88 *
89 * @return zero. success.
90 */
dsp_ctl_close(snd_ctl_ext_t * ext)91 static void dsp_ctl_close(snd_ctl_ext_t * ext)
92 {
93 snd_ctl_dsp_t *dsp_ctl = ext->private_data;
94 DENTER();
95 free(dsp_ctl->controls);
96 dsp_ctl->controls = NULL;
97 free_control_list(&(dsp_ctl->playback_devices));
98 free_control_list(&(dsp_ctl->recording_devices));
99 // free(dsp_ctl);
100 DLEAVE(0);
101 }
102
103 /**
104 * @param ext snd_ctl_ext_t structure.
105 *
106 * It returns number of controls to be used by this
107 * plugin. It is based on number of recording and playback
108 * device nodes configured to be handled by this plugin.
109 *
110 * @return number of controls.
111 */
dsp_ctl_elem_count(snd_ctl_ext_t * ext)112 static int dsp_ctl_elem_count(snd_ctl_ext_t * ext)
113 {
114 snd_ctl_dsp_t *dsp_ctl = ext->private_data;
115 int ret = 2 * dsp_ctl->num_playbacks + dsp_ctl->num_recordings;
116 DENTER();
117 DLEAVE(ret);
118 return ret;
119 }
120
121 /**
122 * @param ext snd_ctl_ext_t structure.
123 * @param offset offset of current control.
124 * @param id id of current control element.
125 *
126 * It sets name and index for a control based
127 * of its offset.
128 *
129 * @return zero. success.
130 */
dsp_ctl_elem_list(snd_ctl_ext_t * ext,unsigned int offset,snd_ctl_elem_id_t * id)131 static int dsp_ctl_elem_list(snd_ctl_ext_t * ext, unsigned int offset,
132 snd_ctl_elem_id_t * id)
133 {
134 snd_ctl_dsp_t *dsp_ctl = ext->private_data;
135 int ret = 0;
136
137 DENTER();
138 snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_MIXER);
139 if (offset < 2 * dsp_ctl->num_playbacks) {
140 if (offset % 2 == 1)
141 snd_ctl_elem_id_set_name(id,
142 PLAYBACK_MUTE_CONTROL_NAME);
143 else
144 snd_ctl_elem_id_set_name(id,
145 PLAYBACK_VOLUME_CONTROL_NAME);
146 offset /= 2;
147 } else {
148 offset -= 2 * dsp_ctl->num_playbacks;
149 snd_ctl_elem_id_set_name(id, RECORDING_CONTROL_NAME);
150 }
151 snd_ctl_elem_id_set_index(id, offset);
152 DLEAVE(ret);
153 return ret;
154 }
155
156 /**
157 * @param ext snd_ctl_ext_t structure.
158 * @param id control element id from alsa-lib
159 *
160 * It searches for an control element using
161 * its name and index. If this control can
162 * be found, it returns a key to represent it
163 * for future use. This key is an index of an
164 * array of controls whose is composed of three
165 * types of elements: PCM Volume, PCM Switch and
166 * Capture Switch. This array is organized as
167 * follows:
168 * pv0, ps0, pv1, ps1, ..., pv_n, ps_n, cs0, cs1, ..., cs_m
169 *
170 * where, pvi is the i-th PCM Volume Control
171 * psi is the i-th PCM Switch Control
172 * csi is the i-th Capture Switch Control
173 * n - is the number of Playback devices
174 * m - is the number of Recording devices
175 *
176 * @return if control is not found, returns SND_CTL_EXT_KEY_NOT_FOUND.
177 * Otherwise, returns a key of control.
178 */
dsp_ctl_find_elem(snd_ctl_ext_t * ext,const snd_ctl_elem_id_t * id)179 static snd_ctl_ext_key_t dsp_ctl_find_elem(snd_ctl_ext_t * ext,
180 const snd_ctl_elem_id_t * id)
181 {
182 snd_ctl_dsp_t *dsp_ctl = ext->private_data;
183 snd_ctl_ext_key_t ret = SND_CTL_EXT_KEY_NOT_FOUND;
184 int idx;
185 const char *name;
186
187 DENTER();
188 idx = snd_ctl_elem_id_get_index(id);
189 name = snd_ctl_elem_id_get_name(id);
190 if (strcmp(PLAYBACK_VOLUME_CONTROL_NAME, name) == 0)
191 ret = idx * 2;
192 else if (strcmp(PLAYBACK_MUTE_CONTROL_NAME, name) == 0)
193 ret = idx * 2 + 1;
194 else
195 ret = 2 * dsp_ctl->num_playbacks + idx;
196 if (ret == SND_CTL_EXT_KEY_NOT_FOUND)
197 DPRINT("Not Found name %s, index %d\n",
198 snd_ctl_elem_id_get_name(id), idx);
199 DLEAVE((int)ret);
200 return ret;
201 }
202
203 /**
204 * @param ext snd_ctl_ext_t structure.
205 * @param key current control key to be handled.
206 * @param type type of this control.
207 * @param acc access type of this control.
208 * @param count number of channels of this control.
209 *
210 * It provides information about a control.
211 * Playback device node has an integer and a boolean
212 * control. Recording has only boolean control.
213 *
214 * @return zero. success.
215 */
dsp_ctl_get_attribute(snd_ctl_ext_t * ext,snd_ctl_ext_key_t key,int * type,unsigned int * acc,unsigned int * count)216 static int dsp_ctl_get_attribute(snd_ctl_ext_t * ext, snd_ctl_ext_key_t key,
217 int *type, unsigned int *acc,
218 unsigned int *count)
219 {
220 snd_ctl_dsp_t *dsp_ctl = ext->private_data;
221 int ret = 0;
222 DENTER();
223
224 if (key >= 2 * dsp_ctl->num_playbacks || key % 2 == 1)
225 *type = SND_CTL_ELEM_TYPE_BOOLEAN;
226 else
227 *type = SND_CTL_ELEM_TYPE_INTEGER;
228
229 *count = dsp_ctl->controls[key]->channels;
230 *acc = SND_CTL_EXT_ACCESS_READWRITE;
231 DLEAVE(ret);
232 return ret;
233 }
234
235 /**
236 * @param ext snd_ctl_ext_t structure.
237 * @param key current control key to be handled.
238 * @param imin minimum value of this integer control.
239 * @param imax maximum value of this integer control.
240 * @param istep steps value of this integer control.
241 *
242 * It provides information about integer control. It
243 * consideres both boolean and integer controls.
244 *
245 * @return zero. success.
246 */
dsp_ctl_get_integer_info(snd_ctl_ext_t * ext,snd_ctl_ext_key_t key,long * imin,long * imax,long * istep)247 static int dsp_ctl_get_integer_info(snd_ctl_ext_t * ext, snd_ctl_ext_key_t key,
248 long *imin, long *imax, long *istep)
249 {
250 snd_ctl_dsp_t *dsp_ctl = ext->private_data;
251 DENTER();
252 *imin = 0;
253 if (key >= 2 * dsp_ctl->num_playbacks || key % 2 == 1)
254 *imax = 1;
255 else
256 *imax = 100;
257 *istep = 0;
258 DLEAVE(0);
259 return 0;
260 }
261
262 /**
263 * @param ext snd_ctl_ext_t structure.
264 * @param key current control key to be handled.
265 * @param value return value holder.
266 *
267 * It reads volume information from dsp task node and
268 * fills it into value to alsa-lib.
269 *
270 * @return zero. success.
271 */
dsp_ctl_read_integer(snd_ctl_ext_t * ext,snd_ctl_ext_key_t key,long * value)272 static int dsp_ctl_read_integer(snd_ctl_ext_t * ext, snd_ctl_ext_key_t key,
273 long *value)
274 {
275 int ret = 0;
276 unsigned char left, right;
277 snd_ctl_dsp_t *dsp_ctl = ext->private_data;
278 control_list_t *tmp = dsp_ctl->controls[key];
279
280 DENTER();
281 if (key >= 2 * dsp_ctl->num_playbacks || key % 2 == 1) {
282 ret = dsp_protocol_get_mute(tmp->dsp_protocol);
283 if (ret >= 0) {
284 left = ret == 0 ? 1 : 0;
285 right = left;
286 }
287 } else
288 ret = dsp_protocol_get_volume(tmp->dsp_protocol, &left, &right);
289 if (ret >= 0) {
290 value[0] = left;
291 if (tmp->channels == 2)
292 value[1] = right;
293 } else {
294 value[0] = 0;
295 if (tmp->channels == 2)
296 value[1] = 0;
297 ret = 0;
298 }
299
300 DLEAVE(ret);
301 return ret;
302 }
303
304 /**
305 * @param ext snd_ctl_ext_t structure.
306 * @param key current control key to be handled.
307 * @param value volume value to be written.
308 *
309 * It writes volume information to dsp task node from
310 * value that comes from alsa-lib. It checks if value
311 * coming from alsa-lib is different of value in dsp
312 * side.
313 *
314 * @return zero if not updated. one if updated.
315 */
dsp_ctl_write_integer(snd_ctl_ext_t * ext,snd_ctl_ext_key_t key,long * value)316 static int dsp_ctl_write_integer(snd_ctl_ext_t * ext, snd_ctl_ext_key_t key,
317 long *value)
318 {
319 int ret;
320 unsigned char left, right;
321 snd_ctl_dsp_t *dsp_ctl = ext->private_data;
322 control_list_t *tmp = dsp_ctl->controls[key];
323
324 DENTER();
325 if (key >= 2 * dsp_ctl->num_playbacks || key % 2 == 1) {
326 if ((ret = dsp_protocol_get_mute(tmp->dsp_protocol)) < 0)
327 goto zero;
328 left = ret == 0 ? 1 : 0;
329 right = left;
330 } else
331 if ((ret =
332 dsp_protocol_get_volume(tmp->dsp_protocol, &left, &right)) < 0)
333 goto zero;
334
335 if (tmp->channels == 2) {
336 if (left == value[0] && right == value[1]) {
337 ret = 0;
338 goto out;
339 }
340 right = value[1];
341 left = value[0];
342 } else {
343 if (left == value[0]) {
344 ret = 0;
345 goto out;
346 }
347 right = left = value[0];
348 }
349
350 if (key >= 2 * dsp_ctl->num_playbacks || key % 2 == 1) {
351 if ((ret =
352 dsp_protocol_set_mute(tmp->dsp_protocol,
353 left == 0 ? 1 : 0)) < 0)
354 goto zero;
355 } else
356 if ((ret =
357 dsp_protocol_set_volume(tmp->dsp_protocol, left, right)) < 0)
358 goto zero;
359 ret = 1;
360 goto out;
361 zero:
362 value[0] = 0;
363 if (tmp->channels == 2)
364 value[1] = 0;
365 ret = 0;
366 out:
367 DLEAVE(ret);
368 return ret;
369 }
370
371 /**
372 * @param ext
373 * @param id
374 * @param event_mask
375 *
376 * @return -EIO
377 */
dsp_ctl_read_event(snd_ctl_ext_t * ext,snd_ctl_elem_id_t * id,unsigned int * event_mask)378 static int dsp_ctl_read_event(snd_ctl_ext_t * ext, snd_ctl_elem_id_t * id,
379 unsigned int *event_mask)
380 {
381 return -EIO;
382 }
383
384 /**
385 * @param n configuration file parse tree.
386 * @param device_list list of device files to be filled.
387 *
388 * It searches for device file names in given configuration parse
389 * tree. When one device file name is found, it is filled into device_list.
390 *
391 * @return zero if success, otherwise a negative error code.
392 */
fill_control_list(snd_config_t * n,control_list_t * control_list)393 static int fill_control_list(snd_config_t * n, control_list_t * control_list)
394 {
395 snd_config_iterator_t j, nextj;
396 control_list_t *tmp;
397 long idx = 0;
398 DENTER();
399 INIT_LIST_HEAD(&control_list->list);
400 snd_config_for_each(j, nextj, n) {
401 snd_config_t *s = snd_config_iterator_entry(j);
402 const char *id_number;
403 long k;
404 if (snd_config_get_id(s, &id_number) < 0)
405 continue;
406 if (safe_strtol(id_number, &k) < 0) {
407 SNDERR("id of field %s is not an integer", id_number);
408 idx = -EINVAL;
409 goto out;
410 }
411 if (k == idx) {
412 idx++;
413 /* add to available dsp task nodes */
414 tmp = (control_list_t *) malloc(sizeof(control_list_t));
415 if (snd_config_get_ascii(s, &(tmp->name)) < 0) {
416 SNDERR("invalid ascii string for id %s\n",
417 id_number);
418 idx = -EINVAL;
419 goto out;
420 }
421 list_add(&(tmp->list), &(control_list->list));
422 }
423 }
424 out:
425 DLEAVE((int)idx);
426 return idx;
427 }
428
429 /**
430 * @param dsp_ctl snd_dsp_t structure.
431 *
432 * It probes all dsp tasks configured to be handled by this
433 * plugin. It gets all needed information about volume controling.
434 *
435 * @return zero if success, otherwise a negative error code.
436 */
dsp_ctl_probe_dsp_task(snd_ctl_dsp_t * dsp_ctl)437 static int dsp_ctl_probe_dsp_task(snd_ctl_dsp_t * dsp_ctl)
438 {
439 int err = 0, i;
440 control_list_t *tmp;
441 struct list_head *lists[2] =
442 { &(dsp_ctl->playback_devices.list),
443 &(dsp_ctl->recording_devices.list) };
444 DENTER();
445 DPRINT("Probing dsp device nodes \n");
446 for (i = 0; i < 2; i++) {
447 list_for_each_entry(tmp, lists[i], list) {
448 DPRINT("Trying to use %s\n", tmp->name);
449 /* Initialise the dsp_protocol and create connection */
450 if ((err =
451 dsp_protocol_create(&(tmp->dsp_protocol))) < 0)
452 goto out;
453 if ((tmp->channels =
454 dsp_protocol_probe_node(tmp->dsp_protocol,
455 tmp->name)) < 0) {
456 DPRINT("%s is not available now\n", tmp->name);
457 err = tmp->channels;
458 close(tmp->dsp_protocol->fd); //memory leak?!?
459 goto out;
460 }
461 }
462 }
463 if (err < 0) {
464 DPRINT("No valid dsp task nodes for now. Exiting.\n");
465 }
466 out:
467 DLEAVE(err);
468 return err;
469 }
470
471 /**
472 * @param dsp_ctl snd_dsp_t structure.
473 *
474 * It fills an array of controls to represent PCM Volume,
475 * PCM Switch and Capture Switch controls.
476 *
477 * @return zero if success, otherwise a negative error code.
478 */
dsp_ctl_fill_controls(snd_ctl_dsp_t * dsp_ctl)479 static int dsp_ctl_fill_controls(snd_ctl_dsp_t * dsp_ctl)
480 {
481 int ret = 0;
482 int i;
483 int num_controls = 2 * dsp_ctl->num_playbacks + dsp_ctl->num_recordings;
484 DENTER();
485 control_list_t *tmp;
486 dsp_ctl->controls = calloc(num_controls, sizeof(control_list_t *));
487 if (dsp_ctl->controls == NULL) {
488 ret = -ENOMEM;
489 goto out;
490 }
491 i = 0;
492 /* Each pcm task node should have a PCM Volume and PCM Switch control */
493 list_for_each_entry(tmp, &(dsp_ctl->playback_devices.list), list) {
494 dsp_ctl->controls[i] = tmp;
495 dsp_ctl->controls[i + 1] = tmp;
496 i += 2;
497 }
498 /* Each pcm_rec node should have a Capture Switch control */
499 list_for_each_entry(tmp, &(dsp_ctl->recording_devices.list), list)
500 dsp_ctl->controls[i++] = tmp;
501
502 out:
503 DLEAVE(ret);
504 return ret;
505 }
506
507 /**
508 * Alsa-lib callback structure.
509 */
510 static snd_ctl_ext_callback_t dsp_ctl_ext_callback = {
511 .close = dsp_ctl_close,
512 .elem_count = dsp_ctl_elem_count,
513 .elem_list = dsp_ctl_elem_list,
514 .find_elem = dsp_ctl_find_elem,
515 .get_attribute = dsp_ctl_get_attribute,
516 .get_integer_info = dsp_ctl_get_integer_info,
517 .read_integer = dsp_ctl_read_integer,
518 .write_integer = dsp_ctl_write_integer,
519 .read_event = dsp_ctl_read_event,
520 };
521
522 /**
523 * It initializes the alsa ctl plugin. It reads the parameters and creates the
524 * connection with the pcm device file.
525 *
526 * @return zero if success, otherwise a negative error code.
527 */
SND_CTL_PLUGIN_DEFINE_FUNC(dsp_ctl)528 SND_CTL_PLUGIN_DEFINE_FUNC(dsp_ctl)
529 {
530 snd_config_iterator_t i, next;
531 snd_ctl_dsp_t *dsp_ctl;
532 int err;
533 int ret;
534
535 DENTER();
536 /* Allocate the structure */
537 dsp_ctl = calloc(1, sizeof(*dsp_ctl));
538 if (dsp_ctl == NULL) {
539 ret = -ENOMEM;
540 goto out;
541 }
542 /* Read the configuration searching for configurated devices */
543 snd_config_for_each(i, next, conf) {
544 snd_config_t *n = snd_config_iterator_entry(i);
545 const char *id;
546 if (snd_config_get_id(n, &id) < 0)
547 continue;
548 if (strcmp(id, "comment") == 0 || strcmp(id, "type") == 0 || strcmp(id, "hint") == 0)
549 continue;
550 if (strcmp(id, "playback_devices") == 0) {
551 if (snd_config_get_type(n) == SND_CONFIG_TYPE_COMPOUND){
552 if ((dsp_ctl->num_playbacks =
553 fill_control_list(n,
554 &(dsp_ctl->
555 playback_devices))) < 0) {
556 SNDERR("Could not fill control"
557 " list for playback devices\n");
558 err = -EINVAL;
559 goto error;
560 }
561 } else {
562 SNDERR("Invalid type for %s", id);
563 err = -EINVAL;
564 goto error;
565 }
566 continue;
567 }
568 if (strcmp(id, "recording_devices") == 0) {
569 if (snd_config_get_type(n) == SND_CONFIG_TYPE_COMPOUND){
570 if ((dsp_ctl->num_recordings =
571 fill_control_list(n,
572 &(dsp_ctl->
573 recording_devices))) < 0) {
574 SNDERR("Could not fill string "
575 "list for recording devices\n");
576 err = -EINVAL;
577 goto error;
578 }
579 } else {
580 SNDERR("Invalid type for %s", id);
581 err = -EINVAL;
582 goto error;
583 }
584 continue;
585 }
586 SNDERR("Unknown field %s", id);
587 err = -EINVAL;
588 goto error;
589 }
590
591 if ((err = dsp_ctl_probe_dsp_task(dsp_ctl)) < 0)
592 goto error;
593
594 if ((err = dsp_ctl_fill_controls(dsp_ctl)) < 0)
595 goto error;
596 dsp_ctl->ext.version = SND_CTL_EXT_VERSION;
597 dsp_ctl->ext.card_idx = 0; /* FIXME */
598 strcpy(dsp_ctl->ext.id, "ALSA-DSP-CTL");
599 strcpy(dsp_ctl->ext.name, "Alsa-DSP external ctl plugin");
600 strcpy(dsp_ctl->ext.longname, "External Control Alsa plugin for DSP");
601 strcpy(dsp_ctl->ext.mixername, "ALSA-DSP plugin Mixer");
602 dsp_ctl->ext.callback = &dsp_ctl_ext_callback;
603 dsp_ctl->ext.private_data = dsp_ctl;
604 free_ref = dsp_ctl;
605
606 if ((err = snd_ctl_ext_create(&dsp_ctl->ext, name, mode)) < 0)
607 goto error;
608
609 *handlep = dsp_ctl->ext.handle;
610 ret = 0;
611 goto out;
612 error:
613 ret = err;
614 free(dsp_ctl);
615 out:
616 DLEAVE(ret);
617 return ret;
618 }
619 static void dsp_ctl_descructor(void) __attribute__ ((destructor));
620
dsp_ctl_descructor(void)621 static void dsp_ctl_descructor(void)
622 {
623 DENTER();
624 DPRINT("dsp ctl destructor\n");
625 DPRINT("checking for memories leaks and releasing resources\n");
626 if (free_ref) {
627 if (free_ref->controls)
628 free(free_ref->controls);
629
630 free_control_list(&(free_ref->playback_devices));
631
632 free_control_list(&(free_ref->recording_devices));
633
634 free(free_ref);
635 free_ref = NULL;
636 }
637 DLEAVE(0);
638
639 }
640
641 SND_CTL_PLUGIN_SYMBOL(dsp_ctl);
642