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