1/* Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
2 * Use of this source code is governed by a BSD-style license that can be
3 * found in the LICENSE file.
4 */
5
6#include <stdlib.h>
7#include <syslog.h>
8#include "cras_dsp_ini.h"
9
10#define MAX_INI_KEY_LENGTH 64  /* names like "output_source:output_0" */
11#define MAX_NR_PORT 128	/* the max number of ports for a plugin */
12#define MAX_PORT_NAME_LENGTH 20 /* names like "output_32" */
13
14/* Format of the ini file (See dsp.ini.sample for an example).
15
16- Each section in the ini file specifies a plugin. The section name is
17  just an identifier. The "library" and "label" attributes in a
18  section must be defined. The "library" attribute is the name of the
19  shared library from which this plugin will be loaded, or a special
20  value "builtin" for built-in plugins. The "label" attribute specify
21  which plugin inside the shared library should be loaded.
22
23- Built-in plugins have an attribute "label" which has value "source"
24  or "sink". It defines where the audio data flows into and flows out
25  of the pipeline.  Built-in plugins also have a attribute "purpose"
26  which has the value "playback" or "capture". It defines which
27  pipeline these plugins belong to.
28
29- Each plugin can have an optional "disable expression", which defines
30  under which conditions the plugin is disabled.
31
32- Each plugin have some ports which specify the parameters for the
33  plugin or to specify connections to other plugins. The ports in each
34  plugin are numbered from 0. Each port is either an input port or an
35  output port, and each port is either an audio port or a control
36  port. The connections between two ports are expressed by giving the
37  same value to both ports. For audio ports, the value should be
38  "{identifier}". For control ports, the value shoule be
39  "<identifier>". For example, the following fragment
40
41  [plugin1]
42  ...
43  output_4={audio_left}
44  output_5={audio_right}
45
46  [plugin2]
47  ...
48  input_0={audio_left}
49
50  [plugin3]
51  ...
52  input_2={audio_right}
53
54  specifies these connections:
55  port 4 of plugin1 --> port 0 of plugin2
56  port 5 of plugin1 --> port 2 of plugin3
57
58*/
59
60static const char *getstring(struct ini *ini, const char *sec_name,
61			     const char *key)
62{
63	char full_key[MAX_INI_KEY_LENGTH];
64	snprintf(full_key, sizeof(full_key), "%s:%s", sec_name, key);
65	return iniparser_getstring(ini->dict, full_key, NULL);
66}
67
68static int lookup_flow(struct ini *ini, const char *name)
69{
70	int i;
71	const struct flow *flow;
72
73	FOR_ARRAY_ELEMENT(&ini->flows, i, flow) {
74		if (strcmp(flow->name, name) == 0)
75			return i;
76	}
77
78	return -1;
79}
80
81static int lookup_or_add_flow(struct ini *ini, const char *name)
82{
83	struct flow *flow;
84	int i = lookup_flow(ini, name);
85	if (i != -1)
86		return i;
87	i = ARRAY_COUNT(&ini->flows);
88	flow = ARRAY_APPEND_ZERO(&ini->flows);
89	flow->name = name;
90	return i;
91}
92
93static int parse_ports(struct ini *ini, const char *sec_name,
94		       struct plugin *plugin)
95{
96	char key[MAX_PORT_NAME_LENGTH];
97	const char *str;
98	int i;
99	struct port *p;
100	int direction;
101
102	for (i = 0; i < MAX_NR_PORT; i++) {
103		direction = PORT_INPUT;
104		snprintf(key, sizeof(key), "input_%d", i);
105		str = getstring(ini, sec_name, key);
106		if (str == NULL)  {
107			direction = PORT_OUTPUT;
108			snprintf(key, sizeof(key), "output_%d", i);
109			str = getstring(ini, sec_name, key);
110			if (str == NULL)
111				break; /* no more ports */
112		}
113
114		if (*str == '\0') {
115			syslog(LOG_ERR, "empty value for %s:%s", sec_name, key);
116			return -1;
117		}
118
119		if (str[0] == '<' || str[0] == '{') {
120			p = ARRAY_APPEND_ZERO(&plugin->ports);
121			p->type = (str[0] == '<') ? PORT_CONTROL : PORT_AUDIO;
122			p->flow_id = lookup_or_add_flow(ini, str);
123			p->init_value = 0;
124		} else {
125			char *endptr;
126			float init_value = strtof(str, &endptr);
127			if (endptr == str) {
128				syslog(LOG_ERR, "cannot parse number from '%s'",
129				       str);
130			}
131			p = ARRAY_APPEND_ZERO(&plugin->ports);
132			p->type = PORT_CONTROL;
133			p->flow_id = INVALID_FLOW_ID;
134			p->init_value = init_value;
135		}
136		p->direction = direction;
137	}
138
139	return 0;
140}
141
142static int parse_plugin_section(struct ini *ini, const char *sec_name,
143				struct plugin *p)
144{
145	p->title = sec_name;
146	p->library = getstring(ini, sec_name, "library");
147	p->label = getstring(ini, sec_name, "label");
148	p->purpose = getstring(ini, sec_name, "purpose");
149	p->disable_expr = cras_expr_expression_parse(
150		getstring(ini, sec_name, "disable"));
151
152	if (p->library == NULL || p->label == NULL) {
153		syslog(LOG_ERR, "A plugin must have library and label: %s",
154		       sec_name);
155		return -1;
156	}
157
158	if (parse_ports(ini, sec_name, p) < 0) {
159		syslog(LOG_ERR, "Failed to parse ports: %s", sec_name);
160		return -1;
161	}
162
163	return 0;
164}
165
166static void fill_flow_info(struct ini *ini)
167{
168	int i, j;
169	struct plugin *plugin;
170	struct port *port;
171	struct flow *flow;
172	struct plugin **pplugin;
173	int *pport;
174
175	FOR_ARRAY_ELEMENT(&ini->plugins, i, plugin) {
176		FOR_ARRAY_ELEMENT(&plugin->ports, j, port) {
177			int flow_id = port->flow_id;
178			if (flow_id == INVALID_FLOW_ID)
179				continue;
180			flow = ARRAY_ELEMENT(&ini->flows, flow_id);
181			flow->type = port->type;
182			if (port->direction == PORT_INPUT) {
183				pplugin = &flow->to;
184				pport = &flow->to_port;
185			} else {
186				pplugin = &flow->from;
187				pport = &flow->from_port;
188			}
189			*pplugin = plugin;
190			*pport = j;
191		}
192	}
193}
194
195
196struct ini *cras_dsp_ini_create(const char *ini_filename)
197{
198	struct ini *ini;
199	dictionary *dict;
200	int nsec, i;
201	const char *sec_name;
202	struct plugin *plugin;
203
204	ini = calloc(1, sizeof(struct ini));
205	if (!ini) {
206		syslog(LOG_ERR, "no memory for ini struct");
207		return NULL;
208	}
209
210	dict = iniparser_load((char *)ini_filename);
211	if (!dict) {
212		syslog(LOG_ERR, "no ini file %s", ini_filename);
213		goto bail;
214	}
215	ini->dict = dict;
216
217	/* Parse the plugin sections */
218	nsec = iniparser_getnsec(dict);
219	for (i = 0; i < nsec; i++) {
220		sec_name = iniparser_getsecname(dict, i);
221		plugin = ARRAY_APPEND_ZERO(&ini->plugins);
222		if (parse_plugin_section(ini, sec_name, plugin) < 0)
223			goto bail;
224	}
225
226	/* Fill flow info now because now the plugin array won't change */
227	fill_flow_info(ini);
228
229	return ini;
230bail:
231	cras_dsp_ini_free(ini);
232	return NULL;
233}
234
235void cras_dsp_ini_free(struct ini *ini)
236{
237	struct plugin *p;
238	int i;
239
240	/* free plugins */
241	FOR_ARRAY_ELEMENT(&ini->plugins, i, p) {
242		cras_expr_expression_free(p->disable_expr);
243		ARRAY_FREE(&p->ports);
244	}
245	ARRAY_FREE(&ini->plugins);
246	ARRAY_FREE(&ini->flows);
247
248	if (ini->dict) {
249		iniparser_freedict(ini->dict);
250		ini->dict = NULL;
251	}
252
253	free(ini);
254}
255
256static const char *port_direction_str(enum port_direction port_direction)
257{
258	switch (port_direction) {
259	case PORT_INPUT: return "input";
260	case PORT_OUTPUT: return "output";
261	default: return "unknown";
262	}
263}
264
265static const char *port_type_str(enum port_type port_type)
266{
267	switch (port_type) {
268	case PORT_CONTROL: return "control";
269	case PORT_AUDIO: return "audio";
270	default: return "unknown";
271	}
272}
273
274static const char *plugin_title(struct plugin *plugin)
275{
276	if (plugin == NULL)
277		return "(null)";
278	return plugin->title;
279}
280