1 /* Copyright 2020 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 <alsa/asoundlib.h>
7 #include <alsa/use-case.h>
8 #include <stdio.h>
9 #include <sys/select.h>
10 #include <syslog.h>
11
12 #include "cras_alsa_io.h"
13 #include "cras_alsa_jack.h"
14 #include "cras_alsa_mixer.h"
15 #include "cras_alsa_ucm.h"
16 #include "cras_iodev.h"
17 #include "cras_system_state.h"
18 #include "iniparser_wrapper.h"
19 #include "utlist.h"
20
21 #define PLUGINS_INI "plugins.ini"
22 #define PLUGIN_KEY_CTL "ctl"
23 #define PLUGIN_KEY_DIR "dir"
24 #define PLUGIN_KEY_PCM "pcm"
25 #define PLUGIN_KEY_CARD "card"
26
27 #define NULL_USB_VID 0x00
28 #define NULL_USB_PID 0x00
29 #define NULL_USB_SERIAL_NUMBER "serial-number-not-used"
30
31 struct hctl_poll_fd {
32 int fd;
33 struct hctl_poll_fd *prev, *next;
34 };
35
36 struct alsa_plugin {
37 snd_hctl_t *hctl;
38 struct cras_alsa_mixer *mixer;
39 struct hctl_poll_fd *hctl_poll_fds;
40 struct cras_use_case_mgr *ucm;
41 struct cras_iodev *iodev;
42 struct alsa_plugin *next, *prev;
43 };
44
45 static struct alsa_plugin *plugins;
46
47 static char ini_name[MAX_INI_NAME_LENGTH + 1];
48 static char key_name[MAX_INI_NAME_LENGTH + 1];
49 static dictionary *plugins_ini = NULL;
50
hctl_event_pending(void * arg,int revents)51 static void hctl_event_pending(void *arg, int revents)
52 {
53 struct alsa_plugin *plugin;
54
55 plugin = (struct alsa_plugin *)arg;
56 if (plugin->hctl == NULL)
57 return;
58
59 /* handle_events will trigger the callback registered with each control
60 * that has changed. */
61 snd_hctl_handle_events(plugin->hctl);
62 }
63
64 /* hctl poll descritpor */
collect_poll_descriptors(struct alsa_plugin * plugin)65 static void collect_poll_descriptors(struct alsa_plugin *plugin)
66 {
67 struct hctl_poll_fd *registered_fd;
68 struct pollfd *pollfds;
69 int i, n, rc;
70
71 n = snd_hctl_poll_descriptors_count(plugin->hctl);
72 if (n == 0) {
73 syslog(LOG_DEBUG, "No hctl descritpor to poll");
74 return;
75 }
76
77 pollfds = malloc(n * sizeof(*pollfds));
78 if (pollfds == NULL)
79 return;
80
81 n = snd_hctl_poll_descriptors(plugin->hctl, pollfds, n);
82 for (i = 0; i < n; i++) {
83 registered_fd = calloc(1, sizeof(*registered_fd));
84 if (registered_fd == NULL) {
85 free(pollfds);
86 return;
87 }
88 registered_fd->fd = pollfds[i].fd;
89 DL_APPEND(plugin->hctl_poll_fds, registered_fd);
90 rc = cras_system_add_select_fd(
91 registered_fd->fd, hctl_event_pending, plugin, POLLIN);
92 if (rc < 0) {
93 DL_DELETE(plugin->hctl_poll_fds, registered_fd);
94 free(pollfds);
95 return;
96 }
97 }
98 free(pollfds);
99 }
100
cleanup_poll_descriptors(struct alsa_plugin * plugin)101 static void cleanup_poll_descriptors(struct alsa_plugin *plugin)
102 {
103 struct hctl_poll_fd *poll_fd;
104 DL_FOREACH (plugin->hctl_poll_fds, poll_fd) {
105 cras_system_rm_select_fd(poll_fd->fd);
106 DL_DELETE(plugin->hctl_poll_fds, poll_fd);
107 free(poll_fd);
108 }
109 }
110
111 static void destroy_plugin(struct alsa_plugin *plugin);
112
alsa_plugin_io_create(enum CRAS_STREAM_DIRECTION direction,const char * pcm_name,const char * ctl_name,const char * card_name)113 void alsa_plugin_io_create(enum CRAS_STREAM_DIRECTION direction,
114 const char *pcm_name, const char *ctl_name,
115 const char *card_name)
116 {
117 struct alsa_plugin *plugin;
118 struct ucm_section *section;
119 struct ucm_section *ucm_sections;
120 int rc;
121
122 plugin = (struct alsa_plugin *)calloc(1, sizeof(*plugin));
123 if (!plugin) {
124 syslog(LOG_ERR, "No memory to create alsa plugin");
125 return;
126 }
127
128 rc = snd_hctl_open(&plugin->hctl, ctl_name, SND_CTL_NONBLOCK);
129 if (rc < 0) {
130 syslog(LOG_ERR, "open hctl fail for plugin %s", ctl_name);
131 goto cleanup;
132 }
133
134 rc = snd_hctl_nonblock(plugin->hctl, 1);
135 if (rc < 0) {
136 syslog(LOG_ERR, "Failed to nonblock hctl for %s", ctl_name);
137 goto cleanup;
138 }
139 rc = snd_hctl_load(plugin->hctl);
140 if (rc < 0) {
141 syslog(LOG_ERR, "Failed to load hctl for %s", ctl_name);
142 goto cleanup;
143 }
144 collect_poll_descriptors(plugin);
145
146 plugin->mixer = cras_alsa_mixer_create(ctl_name);
147
148 plugin->ucm = ucm_create(card_name);
149
150 DL_APPEND(plugins, plugin);
151
152 ucm_sections = ucm_get_sections(plugin->ucm);
153 DL_FOREACH (ucm_sections, section) {
154 rc = cras_alsa_mixer_add_controls_in_section(plugin->mixer,
155 section);
156 if (rc)
157 syslog(LOG_ERR,
158 "Failed adding control to plugin,"
159 "section %s mixer_name %s",
160 section->name, section->mixer_name);
161 }
162 plugin->iodev = alsa_iodev_create(0, card_name, 0, pcm_name, "", "",
163 ALSA_CARD_TYPE_USB, 1, /* is first */
164 plugin->mixer, NULL, plugin->ucm,
165 plugin->hctl, direction, NULL_USB_VID,
166 NULL_USB_PID, NULL_USB_SERIAL_NUMBER);
167
168 DL_FOREACH (ucm_sections, section) {
169 if (section->dir != plugin->iodev->direction)
170 continue;
171 section->dev_idx = 0;
172 alsa_iodev_ucm_add_nodes_and_jacks(plugin->iodev, section);
173 }
174
175 alsa_iodev_ucm_complete_init(plugin->iodev);
176
177 return;
178 cleanup:
179 if (plugin)
180 destroy_plugin(plugin);
181 }
182
destroy_plugin(struct alsa_plugin * plugin)183 static void destroy_plugin(struct alsa_plugin *plugin)
184 {
185 cleanup_poll_descriptors(plugin);
186 if (plugin->hctl)
187 snd_hctl_close(plugin->hctl);
188 if (plugin->iodev)
189 alsa_iodev_destroy(plugin->iodev);
190 if (plugin->mixer)
191 cras_alsa_mixer_destroy(plugin->mixer);
192
193 free(plugin);
194 }
195
alsa_pluigin_io_destroy_all()196 void alsa_pluigin_io_destroy_all()
197 {
198 struct alsa_plugin *plugin;
199
200 DL_FOREACH (plugins, plugin)
201 destroy_plugin(plugin);
202 }
203
cras_alsa_plugin_io_init(const char * device_config_dir)204 void cras_alsa_plugin_io_init(const char *device_config_dir)
205 {
206 int nsec, i;
207 enum CRAS_STREAM_DIRECTION direction;
208 const char *sec_name;
209 const char *tmp, *pcm_name, *ctl_name, *card_name;
210
211 snprintf(ini_name, MAX_INI_NAME_LENGTH, "%s/%s", device_config_dir,
212 PLUGINS_INI);
213 ini_name[MAX_INI_NAME_LENGTH] = '\0';
214
215 plugins_ini = iniparser_load_wrapper(ini_name);
216 if (!plugins_ini)
217 return;
218
219 nsec = iniparser_getnsec(plugins_ini);
220 for (i = 0; i < nsec; i++) {
221 sec_name = iniparser_getsecname(plugins_ini, i);
222
223 /* Parse dir=output or dir=input */
224 snprintf(key_name, MAX_INI_NAME_LENGTH, "%s:%s", sec_name,
225 PLUGIN_KEY_DIR);
226 tmp = iniparser_getstring(plugins_ini, key_name, NULL);
227 if (strcmp(tmp, "output") == 0)
228 direction = CRAS_STREAM_OUTPUT;
229 else if (strcmp(tmp, "input") == 0)
230 direction = CRAS_STREAM_INPUT;
231 else
232 continue;
233
234 /* pcm=<plugin-pcm-name> this name will be used with
235 * snd_pcm_open. */
236 snprintf(key_name, MAX_INI_NAME_LENGTH, "%s:%s", sec_name,
237 PLUGIN_KEY_PCM);
238 pcm_name = iniparser_getstring(plugins_ini, key_name, NULL);
239 if (!pcm_name)
240 continue;
241
242 /* ctl=<plugin-ctl-name> this name will be used with
243 * snd_hctl_open. */
244 snprintf(key_name, MAX_INI_NAME_LENGTH, "%s:%s", sec_name,
245 PLUGIN_KEY_CTL);
246 ctl_name = iniparser_getstring(plugins_ini, key_name, NULL);
247 if (!ctl_name)
248 continue;
249
250 /* card=<card-name> this name will be used with
251 * snd_use_case_mgr_open. */
252 snprintf(key_name, MAX_INI_NAME_LENGTH, "%s:%s", sec_name,
253 PLUGIN_KEY_CARD);
254 card_name = iniparser_getstring(plugins_ini, key_name, NULL);
255 if (!card_name)
256 continue;
257
258 syslog(LOG_DEBUG,
259 "Creating plugin for direction %s, pcm %s, ctl %s, card %s",
260 direction == CRAS_STREAM_OUTPUT ? "output" : "input",
261 pcm_name, ctl_name, card_name);
262
263 alsa_plugin_io_create(direction, pcm_name, ctl_name, card_name);
264 }
265 }
266