]> code.delx.au - pulseaudio/blob - src/modules/alsa/alsa-ucm.c
core: Internally deprecate pa_port_available_t to use pa_available_t
[pulseaudio] / src / modules / alsa / alsa-ucm.c
1 /***
2 This file is part of PulseAudio.
3
4 Copyright 2011 Wolfson Microelectronics PLC
5 Author Margarita Olaya <magi@slimlogic.co.uk>
6 Copyright 2012 Feng Wei <wei.feng@freescale.com>, Freescale Ltd.
7
8 PulseAudio is free software; you can redistribute it and/or modify
9 it under the terms of the GNU Lesser General Public License as published
10 by the Free Software Foundation; either version 2.1 of the License,
11 or (at your option) any later version.
12
13 PulseAudio is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 General Public License for more details.
17
18 You should have received a copy of the GNU Lesser General Public License
19 along with PulseAudio; if not, write to the Free Software
20 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
21 USA.
22
23 ***/
24
25 #ifdef HAVE_CONFIG_H
26 #include <config.h>
27 #endif
28
29 #include <ctype.h>
30 #include <sys/types.h>
31 #include <limits.h>
32 #include <asoundlib.h>
33
34 #ifdef HAVE_VALGRIND_MEMCHECK_H
35 #include <valgrind/memcheck.h>
36 #endif
37
38 #include <pulse/sample.h>
39 #include <pulse/xmalloc.h>
40 #include <pulse/timeval.h>
41 #include <pulse/util.h>
42
43 #include <pulsecore/log.h>
44 #include <pulsecore/macro.h>
45 #include <pulsecore/core-util.h>
46 #include <pulsecore/atomic.h>
47 #include <pulsecore/core-error.h>
48 #include <pulsecore/once.h>
49 #include <pulsecore/thread.h>
50 #include <pulsecore/conf-parser.h>
51 #include <pulsecore/strbuf.h>
52
53 #include "alsa-mixer.h"
54 #include "alsa-util.h"
55 #include "alsa-ucm.h"
56
57 #define PA_UCM_PRE_TAG_OUTPUT "[Out] "
58 #define PA_UCM_PRE_TAG_INPUT "[In] "
59
60 #define PA_UCM_PLAYBACK_PRIORITY_UNSET(device) ((device)->playback_channels && !(device)->playback_priority)
61 #define PA_UCM_CAPTURE_PRIORITY_UNSET(device) ((device)->capture_channels && !(device)->capture_priority)
62 #define PA_UCM_DEVICE_PRIORITY_SET(device, priority) \
63 do { \
64 if (PA_UCM_PLAYBACK_PRIORITY_UNSET(device)) (device)->playback_priority = (priority); \
65 if (PA_UCM_CAPTURE_PRIORITY_UNSET(device)) (device)->capture_priority = (priority); \
66 } while (0)
67 #define PA_UCM_IS_MODIFIER_MAPPING(m) ((pa_proplist_gets((m)->proplist, PA_ALSA_PROP_UCM_MODIFIER)) != NULL)
68
69 struct ucm_items {
70 const char *id;
71 const char *property;
72 };
73
74 struct ucm_info {
75 const char *id;
76 unsigned priority;
77 };
78
79 static struct ucm_items item[] = {
80 {"PlaybackPCM", PA_ALSA_PROP_UCM_SINK},
81 {"CapturePCM", PA_ALSA_PROP_UCM_SOURCE},
82 {"PlaybackVolume", PA_ALSA_PROP_UCM_PLAYBACK_VOLUME},
83 {"PlaybackSwitch", PA_ALSA_PROP_UCM_PLAYBACK_SWITCH},
84 {"PlaybackPriority", PA_ALSA_PROP_UCM_PLAYBACK_PRIORITY},
85 {"PlaybackChannels", PA_ALSA_PROP_UCM_PLAYBACK_CHANNELS},
86 {"CaptureVolume", PA_ALSA_PROP_UCM_CAPTURE_VOLUME},
87 {"CaptureSwitch", PA_ALSA_PROP_UCM_CAPTURE_SWITCH},
88 {"CapturePriority", PA_ALSA_PROP_UCM_CAPTURE_PRIORITY},
89 {"CaptureChannels", PA_ALSA_PROP_UCM_CAPTURE_CHANNELS},
90 {"TQ", PA_ALSA_PROP_UCM_QOS},
91 {NULL, NULL},
92 };
93
94 /* UCM verb info - this should eventually be part of policy manangement */
95 static struct ucm_info verb_info[] = {
96 {SND_USE_CASE_VERB_INACTIVE, 0},
97 {SND_USE_CASE_VERB_HIFI, 8000},
98 {SND_USE_CASE_VERB_HIFI_LOW_POWER, 7000},
99 {SND_USE_CASE_VERB_VOICE, 6000},
100 {SND_USE_CASE_VERB_VOICE_LOW_POWER, 5000},
101 {SND_USE_CASE_VERB_VOICECALL, 4000},
102 {SND_USE_CASE_VERB_IP_VOICECALL, 4000},
103 {SND_USE_CASE_VERB_ANALOG_RADIO, 3000},
104 {SND_USE_CASE_VERB_DIGITAL_RADIO, 3000},
105 {NULL, 0}
106 };
107
108 /* UCM device info - should be overwritten by ucm property */
109 static struct ucm_info dev_info[] = {
110 {SND_USE_CASE_DEV_SPEAKER, 100},
111 {SND_USE_CASE_DEV_LINE, 100},
112 {SND_USE_CASE_DEV_HEADPHONES, 100},
113 {SND_USE_CASE_DEV_HEADSET, 300},
114 {SND_USE_CASE_DEV_HANDSET, 200},
115 {SND_USE_CASE_DEV_BLUETOOTH, 400},
116 {SND_USE_CASE_DEV_EARPIECE, 100},
117 {SND_USE_CASE_DEV_SPDIF, 100},
118 {SND_USE_CASE_DEV_HDMI, 100},
119 {SND_USE_CASE_DEV_NONE, 100},
120 {NULL, 0}
121 };
122
123 /* UCM profile properties - The verb data is store so it can be used to fill
124 * the new profiles properties */
125 static int ucm_get_property(pa_alsa_ucm_verb *verb, snd_use_case_mgr_t *uc_mgr, const char *verb_name) {
126 const char *value;
127 char *id;
128 int i;
129
130 for (i = 0; item[i].id; i++) {
131 int err;
132
133 id = pa_sprintf_malloc("=%s//%s", item[i].id, verb_name);
134 err = snd_use_case_get(uc_mgr, id, &value);
135 pa_xfree(id);
136 if (err < 0 )
137 continue;
138
139 pa_log_debug("Got %s for verb %s: %s", item[i].id, verb_name, value);
140 pa_proplist_sets(verb->proplist, item[i].property, value);
141 free((void*)value);
142 }
143
144 return 0;
145 };
146
147 static int ucm_device_exists(pa_idxset *idxset, pa_alsa_ucm_device *dev) {
148 pa_alsa_ucm_device *d;
149 uint32_t idx;
150
151 PA_IDXSET_FOREACH(d, idxset, idx)
152 if (d == dev)
153 return 1;
154
155 return 0;
156 }
157
158 static void ucm_add_devices_to_idxset(
159 pa_idxset *idxset,
160 pa_alsa_ucm_device *me,
161 pa_alsa_ucm_device *devices,
162 const char **dev_names,
163 int n) {
164
165 pa_alsa_ucm_device *d;
166
167 PA_LLIST_FOREACH(d, devices) {
168 const char *name;
169 int i;
170
171 if (d == me)
172 continue;
173
174 name = pa_proplist_gets(d->proplist, PA_ALSA_PROP_UCM_NAME);
175
176 for (i = 0; i < n; i++)
177 if (pa_streq(dev_names[i], name))
178 pa_idxset_put(idxset, d, NULL);
179 }
180 }
181
182 /* Create a property list for this ucm device */
183 static int ucm_get_device_property(
184 pa_alsa_ucm_device *device,
185 snd_use_case_mgr_t *uc_mgr,
186 pa_alsa_ucm_verb *verb,
187 const char *device_name) {
188
189 const char *value;
190 const char **devices;
191 char *id;
192 int i;
193 int err;
194 uint32_t ui;
195 int n_confdev, n_suppdev;
196
197 for (i = 0; item[i].id; i++) {
198 id = pa_sprintf_malloc("=%s/%s", item[i].id, device_name);
199 err = snd_use_case_get(uc_mgr, id, &value);
200 pa_xfree(id);
201 if (err < 0)
202 continue;
203
204 pa_log_debug("Got %s for device %s: %s", item[i].id, device_name, value);
205 pa_proplist_sets(device->proplist, item[i].property, value);
206 free((void*)value);
207 }
208
209 /* get direction and channels */
210 value = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_PLAYBACK_CHANNELS);
211 if (value) { /* output */
212 /* get channels */
213 if (pa_atou(value, &ui) == 0 && ui < PA_CHANNELS_MAX)
214 device->playback_channels = ui;
215 else
216 pa_log("UCM playback channels %s for device %s out of range", value, device_name);
217
218 /* get pcm */
219 value = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_SINK);
220 if (!value) { /* take pcm from verb playback default */
221 value = pa_proplist_gets(verb->proplist, PA_ALSA_PROP_UCM_SINK);
222 if (value) {
223 pa_log_debug("UCM playback device %s fetch pcm from verb default %s", device_name, value);
224 pa_proplist_sets(device->proplist, PA_ALSA_PROP_UCM_SINK, value);
225 } else
226 pa_log("UCM playback device %s fetch pcm failed", device_name);
227 }
228 }
229
230 value = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_CAPTURE_CHANNELS);
231 if (value) { /* input */
232 /* get channels */
233 if (pa_atou(value, &ui) == 0 && ui < PA_CHANNELS_MAX)
234 device->capture_channels = ui;
235 else
236 pa_log("UCM capture channels %s for device %s out of range", value, device_name);
237
238 /* get pcm */
239 value = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_SOURCE);
240 if (!value) { /* take pcm from verb capture default */
241 value = pa_proplist_gets(verb->proplist, PA_ALSA_PROP_UCM_SOURCE);
242 if (value) {
243 pa_log_debug("UCM capture device %s fetch pcm from verb default %s", device_name, value);
244 pa_proplist_sets(device->proplist, PA_ALSA_PROP_UCM_SOURCE, value);
245 } else
246 pa_log("UCM capture device %s fetch pcm failed", device_name);
247 }
248 }
249
250 if (device->playback_channels == 0 && device->capture_channels == 0) {
251 pa_log_warn("UCM file does not specify 'PlaybackChannels' or 'CaptureChannels'"
252 "for device %s, assuming stereo duplex.", device_name);
253 device->playback_channels = 2;
254 device->capture_channels = 2;
255 }
256
257 /* get priority of device */
258 if (device->playback_channels) { /* sink device */
259 value = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_PLAYBACK_PRIORITY);
260 if (value) {
261 /* get priority from ucm config */
262 if (pa_atou(value, &ui) == 0)
263 device->playback_priority = ui;
264 else
265 pa_log_debug("UCM playback priority %s for device %s error", value, device_name);
266 }
267 }
268
269 if (device->capture_channels) { /* source device */
270 value = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_CAPTURE_PRIORITY);
271 if (value) {
272 /* get priority from ucm config */
273 if (pa_atou(value, &ui) == 0)
274 device->capture_priority = ui;
275 else
276 pa_log_debug("UCM capture priority %s for device %s error", value, device_name);
277 }
278 }
279
280 if (PA_UCM_PLAYBACK_PRIORITY_UNSET(device) || PA_UCM_CAPTURE_PRIORITY_UNSET(device)) {
281 /* get priority from static table */
282 for (i = 0; dev_info[i].id; i++) {
283 if (strcasecmp(dev_info[i].id, device_name) == 0) {
284 PA_UCM_DEVICE_PRIORITY_SET(device, dev_info[i].priority);
285 break;
286 }
287 }
288 }
289
290 if (PA_UCM_PLAYBACK_PRIORITY_UNSET(device)) {
291 /* fall through to default priority */
292 device->playback_priority = 100;
293 }
294
295 if (PA_UCM_CAPTURE_PRIORITY_UNSET(device)) {
296 /* fall through to default priority */
297 device->capture_priority = 100;
298 }
299
300 id = pa_sprintf_malloc("%s/%s", "_conflictingdevs", device_name);
301 n_confdev = snd_use_case_get_list(uc_mgr, id, &devices);
302 pa_xfree(id);
303
304 if (n_confdev <= 0)
305 pa_log_debug("No %s for device %s", "_conflictingdevs", device_name);
306 else {
307 device->conflicting_devices = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
308 ucm_add_devices_to_idxset(device->conflicting_devices, device, verb->devices, devices, n_confdev);
309 snd_use_case_free_list(devices, n_confdev);
310 }
311
312 id = pa_sprintf_malloc("%s/%s", "_supporteddevs", device_name);
313 n_suppdev = snd_use_case_get_list(uc_mgr, id, &devices);
314 pa_xfree(id);
315
316 if (n_suppdev <= 0)
317 pa_log_debug("No %s for device %s", "_supporteddevs", device_name);
318 else {
319 device->supported_devices = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
320 ucm_add_devices_to_idxset(device->supported_devices, device, verb->devices, devices, n_suppdev);
321 snd_use_case_free_list(devices, n_suppdev);
322 }
323
324 return 0;
325 };
326
327 /* Create a property list for this ucm modifier */
328 static int ucm_get_modifier_property(pa_alsa_ucm_modifier *modifier, snd_use_case_mgr_t *uc_mgr, const char *modifier_name) {
329 const char *value;
330 char *id;
331 int i;
332
333 for (i = 0; item[i].id; i++) {
334 int err;
335
336 id = pa_sprintf_malloc("=%s/%s", item[i].id, modifier_name);
337 err = snd_use_case_get(uc_mgr, id, &value);
338 pa_xfree(id);
339 if (err < 0 )
340 continue;
341
342 pa_log_debug("Got %s for modifier %s: %s", item[i].id, modifier_name, value);
343 pa_proplist_sets(modifier->proplist, item[i].property, value);
344 free((void*)value);
345 }
346
347 id = pa_sprintf_malloc("%s/%s", "_conflictingdevs", modifier_name);
348 modifier->n_confdev = snd_use_case_get_list(uc_mgr, id, &modifier->conflicting_devices);
349 pa_xfree(id);
350 if (modifier->n_confdev < 0)
351 pa_log_debug("No %s for modifier %s", "_conflictingdevs", modifier_name);
352
353 id = pa_sprintf_malloc("%s/%s", "_supporteddevs", modifier_name);
354 modifier->n_suppdev = snd_use_case_get_list(uc_mgr, id, &modifier->supported_devices);
355 pa_xfree(id);
356 if (modifier->n_suppdev < 0)
357 pa_log_debug("No %s for modifier %s", "_supporteddevs", modifier_name);
358
359 return 0;
360 };
361
362 /* Create a list of devices for this verb */
363 static int ucm_get_devices(pa_alsa_ucm_verb *verb, snd_use_case_mgr_t *uc_mgr) {
364 const char **dev_list;
365 int num_dev, i;
366
367 num_dev = snd_use_case_get_list(uc_mgr, "_devices", &dev_list);
368 if (num_dev < 0)
369 return num_dev;
370
371 for (i = 0; i < num_dev; i += 2) {
372 pa_alsa_ucm_device *d = pa_xnew0(pa_alsa_ucm_device, 1);
373
374 d->proplist = pa_proplist_new();
375 pa_proplist_sets(d->proplist, PA_ALSA_PROP_UCM_NAME, pa_strnull(dev_list[i]));
376 pa_proplist_sets(d->proplist, PA_ALSA_PROP_UCM_DESCRIPTION, pa_strna(dev_list[i + 1]));
377
378 PA_LLIST_PREPEND(pa_alsa_ucm_device, verb->devices, d);
379 }
380
381 snd_use_case_free_list(dev_list, num_dev);
382
383 return 0;
384 };
385
386 static int ucm_get_modifiers(pa_alsa_ucm_verb *verb, snd_use_case_mgr_t *uc_mgr) {
387 const char **mod_list;
388 int num_mod, i;
389
390 num_mod = snd_use_case_get_list(uc_mgr, "_modifiers", &mod_list);
391 if (num_mod < 0)
392 return num_mod;
393
394 for (i = 0; i < num_mod; i += 2) {
395 pa_alsa_ucm_modifier *m;
396
397 if (!mod_list[i]) {
398 pa_log_warn("Got a modifier with a null name. Skipping.");
399 continue;
400 }
401
402 m = pa_xnew0(pa_alsa_ucm_modifier, 1);
403 m->proplist = pa_proplist_new();
404
405 pa_proplist_sets(m->proplist, PA_ALSA_PROP_UCM_NAME, mod_list[i]);
406 pa_proplist_sets(m->proplist, PA_ALSA_PROP_UCM_DESCRIPTION, pa_strna(mod_list[i + 1]));
407
408 PA_LLIST_PREPEND(pa_alsa_ucm_modifier, verb->modifiers, m);
409 }
410
411 snd_use_case_free_list(mod_list, num_mod);
412
413 return 0;
414 };
415
416 static void add_role_to_device(pa_alsa_ucm_device *dev, const char *dev_name, const char *role_name, const char *role) {
417 const char *cur = pa_proplist_gets(dev->proplist, role_name);
418
419 if (!cur)
420 pa_proplist_sets(dev->proplist, role_name, role);
421 else if (!pa_str_in_list_spaces(cur, role)) { /* does not exist */
422 char *value = pa_sprintf_malloc("%s %s", cur, role);
423
424 pa_proplist_sets(dev->proplist, role_name, value);
425 pa_xfree(value);
426 }
427
428 pa_log_info("Add role %s to device %s(%s), result %s", role, dev_name, role_name, pa_proplist_gets(dev->proplist,
429 role_name));
430 }
431
432 static void add_media_role(const char *name, pa_alsa_ucm_device *list, const char *role_name, const char *role, bool is_sink) {
433 pa_alsa_ucm_device *d;
434
435 PA_LLIST_FOREACH(d, list) {
436 const char *dev_name = pa_proplist_gets(d->proplist, PA_ALSA_PROP_UCM_NAME);
437
438 if (pa_streq(dev_name, name)) {
439 const char *sink = pa_proplist_gets(d->proplist, PA_ALSA_PROP_UCM_SINK);
440 const char *source = pa_proplist_gets(d->proplist, PA_ALSA_PROP_UCM_SOURCE);
441
442 if (is_sink && sink)
443 add_role_to_device(d, dev_name, role_name, role);
444 else if (!is_sink && source)
445 add_role_to_device(d, dev_name, role_name, role);
446 break;
447 }
448 }
449 }
450
451 static char *modifier_name_to_role(const char *mod_name, bool *is_sink) {
452 char *sub = NULL, *tmp;
453
454 *is_sink = FALSE;
455
456 if (pa_startswith(mod_name, "Play")) {
457 *is_sink = TRUE;
458 sub = pa_xstrdup(mod_name + 4);
459 } else if (pa_startswith(mod_name, "Capture"))
460 sub = pa_xstrdup(mod_name + 7);
461
462 if (!sub || !*sub) {
463 pa_xfree(sub);
464 pa_log_warn("Can't match media roles for modifer %s", mod_name);
465 return NULL;
466 }
467
468 tmp = sub;
469
470 do {
471 *tmp = tolower(*tmp);
472 } while (*(++tmp));
473
474 return sub;
475 }
476
477 static void ucm_set_media_roles(pa_alsa_ucm_modifier *modifier, pa_alsa_ucm_device *list, const char *mod_name) {
478 int i;
479 bool is_sink = FALSE;
480 char *sub = NULL;
481 const char *role_name;
482
483 sub = modifier_name_to_role(mod_name, &is_sink);
484 if (!sub)
485 return;
486
487 modifier->action_direction = is_sink ? PA_DIRECTION_OUTPUT : PA_DIRECTION_INPUT;
488 modifier->media_role = sub;
489
490 role_name = is_sink ? PA_ALSA_PROP_UCM_PLAYBACK_ROLES : PA_ALSA_PROP_UCM_CAPTURE_ROLES;
491 for (i = 0; i < modifier->n_suppdev; i++) {
492 /* if modifier has no specific pcm, we add role intent to its supported devices */
493 if (!pa_proplist_gets(modifier->proplist, PA_ALSA_PROP_UCM_SINK) &&
494 !pa_proplist_gets(modifier->proplist, PA_ALSA_PROP_UCM_SOURCE))
495 add_media_role(modifier->supported_devices[i], list, role_name, sub, is_sink);
496 }
497 }
498
499 static void append_lost_relationship(pa_alsa_ucm_device *dev) {
500 uint32_t idx;
501 pa_alsa_ucm_device *d;
502
503 if (dev->conflicting_devices) {
504 PA_IDXSET_FOREACH(d, dev->conflicting_devices, idx) {
505 if (!d->conflicting_devices)
506 d->conflicting_devices = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
507
508 if (pa_idxset_put(d->conflicting_devices, dev, NULL) == 0)
509 pa_log_warn("Add lost conflicting device %s to %s",
510 pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_NAME),
511 pa_proplist_gets(d->proplist, PA_ALSA_PROP_UCM_NAME));
512 }
513 }
514
515 if (dev->supported_devices) {
516 PA_IDXSET_FOREACH(d, dev->supported_devices, idx) {
517 if (!d->supported_devices)
518 d->supported_devices = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
519
520 if (pa_idxset_put(d->supported_devices, dev, NULL) == 0)
521 pa_log_warn("Add lost supported device %s to %s",
522 pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_NAME),
523 pa_proplist_gets(d->proplist, PA_ALSA_PROP_UCM_NAME));
524 }
525 }
526 }
527
528 int pa_alsa_ucm_query_profiles(pa_alsa_ucm_config *ucm, int card_index)
529 {
530 char *card_name;
531 const char **verb_list;
532 int num_verbs, i, err = 0;
533
534 /* is UCM available for this card ? */
535 err = snd_card_get_name(card_index, &card_name);
536 if (err < 0) {
537 pa_log("Card can't get card_name from card_index %d", card_index);
538 goto name_fail;
539 }
540
541 err = snd_use_case_mgr_open(&ucm->ucm_mgr, card_name);
542 if (err < 0) {
543 pa_log_info("UCM not available for card %s", card_name);
544 goto ucm_mgr_fail;
545 }
546
547 pa_log_info("UCM available for card %s", card_name);
548
549 /* get a list of all UCM verbs (profiles) for this card */
550 num_verbs = snd_use_case_verb_list(ucm->ucm_mgr, &verb_list);
551 if (num_verbs < 0) {
552 pa_log("UCM verb list not found for %s", card_name);
553 goto ucm_verb_fail;
554 }
555
556 /* get the properties of each UCM verb */
557 for (i = 0; i < num_verbs; i += 2) {
558 pa_alsa_ucm_verb *verb;
559
560 /* Get devices and modifiers for each verb */
561 err = pa_alsa_ucm_get_verb(ucm->ucm_mgr, verb_list[i], verb_list[i+1], &verb);
562 if (err < 0) {
563 pa_log("Failed to get the verb %s", verb_list[i]);
564 continue;
565 }
566
567 PA_LLIST_PREPEND(pa_alsa_ucm_verb, ucm->verbs, verb);
568 }
569
570 if (!ucm->verbs) {
571 pa_log("No UCM verb is valid for %s", card_name);
572 err = -1;
573 }
574
575 snd_use_case_free_list(verb_list, num_verbs);
576
577 ucm_verb_fail:
578 if (err < 0) {
579 snd_use_case_mgr_close(ucm->ucm_mgr);
580 ucm->ucm_mgr = NULL;
581 }
582
583 ucm_mgr_fail:
584 free(card_name);
585
586 name_fail:
587 return err;
588 }
589
590 int pa_alsa_ucm_get_verb(snd_use_case_mgr_t *uc_mgr, const char *verb_name, const char *verb_desc, pa_alsa_ucm_verb **p_verb) {
591 pa_alsa_ucm_device *d;
592 pa_alsa_ucm_modifier *mod;
593 pa_alsa_ucm_verb *verb;
594 int err = 0;
595
596 *p_verb = NULL;
597 pa_log_info("Set UCM verb to %s", verb_name);
598 err = snd_use_case_set(uc_mgr, "_verb", verb_name);
599 if (err < 0)
600 return err;
601
602 verb = pa_xnew0(pa_alsa_ucm_verb, 1);
603 verb->proplist = pa_proplist_new();
604
605 pa_proplist_sets(verb->proplist, PA_ALSA_PROP_UCM_NAME, pa_strnull(verb_name));
606 pa_proplist_sets(verb->proplist, PA_ALSA_PROP_UCM_DESCRIPTION, pa_strna(verb_desc));
607
608 err = ucm_get_devices(verb, uc_mgr);
609 if (err < 0)
610 pa_log("No UCM devices for verb %s", verb_name);
611
612 err = ucm_get_modifiers(verb, uc_mgr);
613 if (err < 0)
614 pa_log("No UCM modifiers for verb %s", verb_name);
615
616 /* Verb properties */
617 ucm_get_property(verb, uc_mgr, verb_name);
618
619 PA_LLIST_FOREACH(d, verb->devices) {
620 const char *dev_name = pa_proplist_gets(d->proplist, PA_ALSA_PROP_UCM_NAME);
621
622 /* Devices properties */
623 ucm_get_device_property(d, uc_mgr, verb, dev_name);
624 }
625 /* make conflicting or supported device mutual */
626 PA_LLIST_FOREACH(d, verb->devices)
627 append_lost_relationship(d);
628
629 PA_LLIST_FOREACH(mod, verb->modifiers) {
630 const char *mod_name = pa_proplist_gets(mod->proplist, PA_ALSA_PROP_UCM_NAME);
631
632 /* Modifier properties */
633 ucm_get_modifier_property(mod, uc_mgr, mod_name);
634
635 /* Set PA_PROP_DEVICE_INTENDED_ROLES property to devices */
636 pa_log_debug("Set media roles for verb %s, modifier %s", verb_name, mod_name);
637 ucm_set_media_roles(mod, verb->devices, mod_name);
638 }
639
640 *p_verb = verb;
641 return 0;
642 }
643
644 static void ucm_add_port_combination(
645 pa_hashmap *hash,
646 pa_alsa_ucm_mapping_context *context,
647 bool is_sink,
648 pa_alsa_ucm_device **pdevices,
649 int num,
650 pa_hashmap *ports,
651 pa_card_profile *cp,
652 pa_core *core) {
653
654 pa_device_port *port;
655 int i;
656 unsigned priority;
657 char *name, *desc;
658 const char *dev_name;
659 const char *direction;
660 pa_alsa_ucm_device *dev;
661
662 dev = pdevices[0];
663 dev_name = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_NAME);
664
665 name = pa_sprintf_malloc("%s%s", is_sink ? PA_UCM_PRE_TAG_OUTPUT : PA_UCM_PRE_TAG_INPUT, dev_name);
666 desc = num == 1 ? pa_xstrdup(pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_DESCRIPTION))
667 : pa_sprintf_malloc("Combination port for %s", dev_name);
668
669 priority = is_sink ? dev->playback_priority : dev->capture_priority;
670
671 for (i = 1; i < num; i++) {
672 char *tmp;
673
674 dev = pdevices[i];
675 dev_name = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_NAME);
676
677 tmp = pa_sprintf_malloc("%s+%s", name, dev_name);
678 pa_xfree(name);
679 name = tmp;
680
681 tmp = pa_sprintf_malloc("%s,%s", desc, dev_name);
682 pa_xfree(desc);
683 desc = tmp;
684
685 /* FIXME: Is this true? */
686 priority += (is_sink ? dev->playback_priority : dev->capture_priority);
687 }
688
689 port = pa_hashmap_get(ports, name);
690 if (!port) {
691 port = pa_device_port_new(core, pa_strna(name), desc, 0);
692 pa_assert(port);
693
694 pa_hashmap_put(ports, port->name, port);
695 pa_log_debug("Add port %s: %s", port->name, port->description);
696 port->profiles = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
697 }
698
699 port->priority = priority;
700 if (is_sink)
701 port->is_output = TRUE;
702 else
703 port->is_input = TRUE;
704
705 pa_xfree(name);
706 pa_xfree(desc);
707
708 direction = is_sink ? "output" : "input";
709 pa_log_debug("Port %s direction %s, priority %d", port->name, direction, priority);
710
711 if (cp) {
712 pa_log_debug("Adding port %s to profile %s", port->name, cp->name);
713 pa_hashmap_put(port->profiles, cp->name, cp);
714 }
715
716 if (hash) {
717 pa_hashmap_put(hash, port->name, port);
718 pa_device_port_ref(port);
719 }
720 }
721
722 static int ucm_port_contains(const char *port_name, const char *dev_name, bool is_sink) {
723 int ret = 0;
724 const char *r;
725 const char *state = NULL;
726 int len;
727
728 if (!port_name || !dev_name)
729 return FALSE;
730
731 port_name += is_sink ? strlen(PA_UCM_PRE_TAG_OUTPUT) : strlen(PA_UCM_PRE_TAG_INPUT);
732
733 while ((r = pa_split_in_place(port_name, "+", &len, &state))) {
734 if (!strncmp(r, dev_name, len)) {
735 ret = 1;
736 break;
737 }
738 }
739
740 return ret;
741 }
742
743 static int ucm_check_conformance(
744 pa_alsa_ucm_mapping_context *context,
745 pa_alsa_ucm_device **pdevices,
746 int dev_num,
747 pa_alsa_ucm_device *dev) {
748
749 uint32_t idx;
750 pa_alsa_ucm_device *d;
751 int i;
752
753 pa_assert(dev);
754
755 pa_log_debug("Check device %s conformance with %d other devices",
756 pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_NAME), dev_num);
757 if (dev_num == 0) {
758 pa_log_debug("First device in combination, number 1");
759 return 1;
760 }
761
762 if (dev->conflicting_devices) { /* the device defines conflicting devices */
763 PA_IDXSET_FOREACH(d, dev->conflicting_devices, idx) {
764 for (i = 0; i < dev_num; i++) {
765 if (pdevices[i] == d) {
766 pa_log_debug("Conflicting device found");
767 return 0;
768 }
769 }
770 }
771 } else if (dev->supported_devices) { /* the device defines supported devices */
772 for (i = 0; i < dev_num; i++) {
773 if (!ucm_device_exists(dev->supported_devices, pdevices[i])) {
774 pa_log_debug("Supported device not found");
775 return 0;
776 }
777 }
778 } else { /* not support any other devices */
779 pa_log_debug("Not support any other devices");
780 return 0;
781 }
782
783 pa_log_debug("Device added to combination, number %d", dev_num + 1);
784 return 1;
785 }
786
787 static inline pa_alsa_ucm_device *get_next_device(pa_idxset *idxset, uint32_t *idx) {
788 pa_alsa_ucm_device *dev;
789
790 if (*idx == PA_IDXSET_INVALID)
791 dev = pa_idxset_first(idxset, idx);
792 else
793 dev = pa_idxset_next(idxset, idx);
794
795 return dev;
796 }
797
798 static void ucm_add_ports_combination(
799 pa_hashmap *hash,
800 pa_alsa_ucm_mapping_context *context,
801 bool is_sink,
802 pa_alsa_ucm_device **pdevices,
803 int dev_num,
804 uint32_t map_index,
805 pa_hashmap *ports,
806 pa_card_profile *cp,
807 pa_core *core) {
808
809 pa_alsa_ucm_device *dev;
810 uint32_t idx = map_index;
811
812 if ((dev = get_next_device(context->ucm_devices, &idx)) == NULL)
813 return;
814
815 /* check if device at map_index can combine with existing devices combination */
816 if (ucm_check_conformance(context, pdevices, dev_num, dev)) {
817 /* add device at map_index to devices combination */
818 pdevices[dev_num] = dev;
819 /* add current devices combination as a new port */
820 ucm_add_port_combination(hash, context, is_sink, pdevices, dev_num + 1, ports, cp, core);
821 /* try more elements combination */
822 ucm_add_ports_combination(hash, context, is_sink, pdevices, dev_num + 1, idx, ports, cp, core);
823 }
824
825 /* try other device with current elements number */
826 ucm_add_ports_combination(hash, context, is_sink, pdevices, dev_num, idx, ports, cp, core);
827 }
828
829 static char* merge_roles(const char *cur, const char *add) {
830 char *r, *ret;
831 const char *state = NULL;
832
833 if (add == NULL)
834 return pa_xstrdup(cur);
835 else if (cur == NULL)
836 return pa_xstrdup(add);
837
838 ret = pa_xstrdup(cur);
839
840 while ((r = pa_split_spaces(add, &state))) {
841 char *value;
842
843 if (!pa_str_in_list_spaces(ret, r))
844 value = pa_sprintf_malloc("%s %s", ret, r);
845 else {
846 pa_xfree(r);
847 continue;
848 }
849
850 pa_xfree(ret);
851 ret = value;
852 pa_xfree(r);
853 }
854
855 return ret;
856 }
857
858 void pa_alsa_ucm_add_ports_combination(
859 pa_hashmap *p,
860 pa_alsa_ucm_mapping_context *context,
861 bool is_sink,
862 pa_hashmap *ports,
863 pa_card_profile *cp,
864 pa_core *core) {
865
866 pa_alsa_ucm_device **pdevices;
867
868 pa_assert(context->ucm_devices);
869
870 if (pa_idxset_size(context->ucm_devices) > 0) {
871 pdevices = pa_xnew(pa_alsa_ucm_device *, pa_idxset_size(context->ucm_devices));
872 ucm_add_ports_combination(p, context, is_sink, pdevices, 0, PA_IDXSET_INVALID, ports, cp, core);
873 pa_xfree(pdevices);
874 }
875 }
876
877 void pa_alsa_ucm_add_ports(
878 pa_hashmap **p,
879 pa_proplist *proplist,
880 pa_alsa_ucm_mapping_context *context,
881 bool is_sink,
882 pa_card *card) {
883
884 uint32_t idx;
885 char *merged_roles;
886 const char *role_name = is_sink ? PA_ALSA_PROP_UCM_PLAYBACK_ROLES : PA_ALSA_PROP_UCM_CAPTURE_ROLES;
887 pa_alsa_ucm_device *dev;
888 pa_alsa_ucm_modifier *mod;
889 char *tmp;
890
891 pa_assert(p);
892 pa_assert(*p);
893
894 /* add ports first */
895 pa_alsa_ucm_add_ports_combination(*p, context, is_sink, card->ports, NULL, card->core);
896
897 /* then set property PA_PROP_DEVICE_INTENDED_ROLES */
898 merged_roles = pa_xstrdup(pa_proplist_gets(proplist, PA_PROP_DEVICE_INTENDED_ROLES));
899 PA_IDXSET_FOREACH(dev, context->ucm_devices, idx) {
900 const char *roles = pa_proplist_gets(dev->proplist, role_name);
901 tmp = merge_roles(merged_roles, roles);
902 pa_xfree(merged_roles);
903 merged_roles = tmp;
904 }
905
906 if (context->ucm_modifiers)
907 PA_IDXSET_FOREACH(mod, context->ucm_modifiers, idx) {
908 tmp = merge_roles(merged_roles, mod->media_role);
909 pa_xfree(merged_roles);
910 merged_roles = tmp;
911 }
912
913 if (merged_roles)
914 pa_proplist_sets(proplist, PA_PROP_DEVICE_INTENDED_ROLES, merged_roles);
915
916 pa_log_info("ALSA device %s roles: %s", pa_proplist_gets(proplist, PA_PROP_DEVICE_STRING), pa_strnull(merged_roles));
917 pa_xfree(merged_roles);
918 }
919
920 /* Change UCM verb and device to match selected card profile */
921 int pa_alsa_ucm_set_profile(pa_alsa_ucm_config *ucm, const char *new_profile, const char *old_profile) {
922 int ret = 0;
923 const char *profile;
924 pa_alsa_ucm_verb *verb;
925
926 if (new_profile == old_profile)
927 return ret;
928 else if (new_profile == NULL || old_profile == NULL)
929 profile = new_profile ? new_profile : SND_USE_CASE_VERB_INACTIVE;
930 else if (!pa_streq(new_profile, old_profile))
931 profile = new_profile;
932 else
933 return ret;
934
935 /* change verb */
936 pa_log_info("Set UCM verb to %s", profile);
937 if ((snd_use_case_set(ucm->ucm_mgr, "_verb", profile)) < 0) {
938 pa_log("Failed to set verb %s", profile);
939 ret = -1;
940 }
941
942 /* find active verb */
943 ucm->active_verb = NULL;
944 PA_LLIST_FOREACH(verb, ucm->verbs) {
945 const char *verb_name;
946 verb_name = pa_proplist_gets(verb->proplist, PA_ALSA_PROP_UCM_NAME);
947 if (pa_streq(verb_name, profile)) {
948 ucm->active_verb = verb;
949 break;
950 }
951 }
952
953 return ret;
954 }
955
956 int pa_alsa_ucm_set_port(pa_alsa_ucm_mapping_context *context, pa_device_port *port, bool is_sink) {
957 int i;
958 int ret = 0;
959 pa_alsa_ucm_config *ucm;
960 const char **enable_devs;
961 int enable_num = 0;
962 uint32_t idx;
963 pa_alsa_ucm_device *dev;
964
965 pa_assert(context && context->ucm);
966
967 ucm = context->ucm;
968 pa_assert(ucm->ucm_mgr);
969
970 enable_devs = pa_xnew(const char *, pa_idxset_size(context->ucm_devices));
971
972 /* first disable then enable */
973 PA_IDXSET_FOREACH(dev, context->ucm_devices, idx) {
974 const char *dev_name = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_NAME);
975
976 if (ucm_port_contains(port->name, dev_name, is_sink))
977 enable_devs[enable_num++] = dev_name;
978 else {
979 pa_log_debug("Disable ucm device %s", dev_name);
980 if (snd_use_case_set(ucm->ucm_mgr, "_disdev", dev_name) > 0) {
981 pa_log("Failed to disable ucm device %s", dev_name);
982 ret = -1;
983 break;
984 }
985 }
986 }
987
988 for (i = 0; i < enable_num; i++) {
989 pa_log_debug("Enable ucm device %s", enable_devs[i]);
990 if (snd_use_case_set(ucm->ucm_mgr, "_enadev", enable_devs[i]) < 0) {
991 pa_log("Failed to enable ucm device %s", enable_devs[i]);
992 ret = -1;
993 break;
994 }
995 }
996
997 pa_xfree(enable_devs);
998
999 return ret;
1000 }
1001
1002 static void ucm_add_mapping(pa_alsa_profile *p, pa_alsa_mapping *m) {
1003
1004 switch (m->direction) {
1005 case PA_ALSA_DIRECTION_ANY:
1006 pa_idxset_put(p->output_mappings, m, NULL);
1007 pa_idxset_put(p->input_mappings, m, NULL);
1008 break;
1009 case PA_ALSA_DIRECTION_OUTPUT:
1010 pa_idxset_put(p->output_mappings, m, NULL);
1011 break;
1012 case PA_ALSA_DIRECTION_INPUT:
1013 pa_idxset_put(p->input_mappings, m, NULL);
1014 break;
1015 }
1016 }
1017
1018 static void alsa_mapping_add_ucm_device(pa_alsa_mapping *m, pa_alsa_ucm_device *device) {
1019 char *cur_desc;
1020 const char *new_desc;
1021
1022 pa_idxset_put(m->ucm_context.ucm_devices, device, NULL);
1023
1024 new_desc = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_DESCRIPTION);
1025 cur_desc = m->description;
1026 if (cur_desc)
1027 m->description = pa_sprintf_malloc("%s + %s", cur_desc, new_desc);
1028 else
1029 m->description = pa_xstrdup(new_desc);
1030 pa_xfree(cur_desc);
1031
1032 /* walk around null case */
1033 m->description = m->description ? m->description : pa_xstrdup("");
1034
1035 /* save mapping to ucm device */
1036 if (m->direction == PA_ALSA_DIRECTION_OUTPUT)
1037 device->playback_mapping = m;
1038 else
1039 device->capture_mapping = m;
1040 }
1041
1042 static void alsa_mapping_add_ucm_modifier(pa_alsa_mapping *m, pa_alsa_ucm_modifier *modifier) {
1043 char *cur_desc;
1044 const char *new_desc, *mod_name, *channel_str;
1045 uint32_t channels = 0;
1046
1047 pa_idxset_put(m->ucm_context.ucm_modifiers, modifier, NULL);
1048
1049 new_desc = pa_proplist_gets(modifier->proplist, PA_ALSA_PROP_UCM_DESCRIPTION);
1050 cur_desc = m->description;
1051 if (cur_desc)
1052 m->description = pa_sprintf_malloc("%s + %s", cur_desc, new_desc);
1053 else
1054 m->description = pa_xstrdup(new_desc);
1055 pa_xfree(cur_desc);
1056
1057 if (!m->description)
1058 pa_xstrdup("");
1059
1060 /* Modifier sinks should not be routed to by default */
1061 m->priority = 0;
1062
1063 mod_name = pa_proplist_gets(modifier->proplist, PA_ALSA_PROP_UCM_NAME);
1064 pa_proplist_sets(m->proplist, PA_ALSA_PROP_UCM_MODIFIER, mod_name);
1065
1066 /* save mapping to ucm modifier */
1067 if (m->direction == PA_ALSA_DIRECTION_OUTPUT) {
1068 modifier->playback_mapping = m;
1069 channel_str = pa_proplist_gets(modifier->proplist, PA_ALSA_PROP_UCM_PLAYBACK_CHANNELS);
1070 } else {
1071 modifier->capture_mapping = m;
1072 channel_str = pa_proplist_gets(modifier->proplist, PA_ALSA_PROP_UCM_CAPTURE_CHANNELS);
1073 }
1074
1075 if (channel_str) {
1076 pa_assert_se(pa_atou(channel_str, &channels) == 0 && channels < PA_CHANNELS_MAX);
1077 pa_log_debug("Got channel count %" PRIu32 " for modifier", channels);
1078 }
1079
1080 if (channels)
1081 pa_channel_map_init_extend(&m->channel_map, channels, PA_CHANNEL_MAP_ALSA);
1082 else
1083 pa_channel_map_init(&m->channel_map);
1084 }
1085
1086 static int ucm_create_mapping_direction(
1087 pa_alsa_ucm_config *ucm,
1088 pa_alsa_profile_set *ps,
1089 pa_alsa_profile *p,
1090 pa_alsa_ucm_device *device,
1091 const char *verb_name,
1092 const char *device_name,
1093 const char *device_str,
1094 bool is_sink) {
1095
1096 pa_alsa_mapping *m;
1097 char *mapping_name;
1098 unsigned priority, channels;
1099
1100 mapping_name = pa_sprintf_malloc("Mapping %s: %s: %s", verb_name, device_str, is_sink ? "sink" : "source");
1101
1102 m = pa_alsa_mapping_get(ps, mapping_name);
1103 if (!m) {
1104 pa_log("No mapping for %s", mapping_name);
1105 pa_xfree(mapping_name);
1106 return -1;
1107 }
1108 pa_log_debug("UCM mapping: %s dev %s", mapping_name, device_name);
1109 pa_xfree(mapping_name);
1110
1111 priority = is_sink ? device->playback_priority : device->capture_priority;
1112 channels = is_sink ? device->playback_channels : device->capture_channels;
1113
1114 if (!m->ucm_context.ucm_devices) { /* new mapping */
1115 m->ucm_context.ucm_devices = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
1116 m->ucm_context.ucm = ucm;
1117 m->ucm_context.direction = is_sink ? PA_DIRECTION_OUTPUT : PA_DIRECTION_INPUT;
1118
1119 m->device_strings = pa_xnew0(char*, 2);
1120 m->device_strings[0] = pa_xstrdup(device_str);
1121 m->direction = is_sink ? PA_ALSA_DIRECTION_OUTPUT : PA_ALSA_DIRECTION_INPUT;
1122
1123 ucm_add_mapping(p, m);
1124 pa_channel_map_init_extend(&m->channel_map, channels, PA_CHANNEL_MAP_ALSA);
1125 }
1126
1127 /* mapping priority is the highest one of ucm devices */
1128 if (priority > m->priority)
1129 m->priority = priority;
1130
1131 /* mapping channels is the lowest one of ucm devices */
1132 if (channels < m->channel_map.channels)
1133 pa_channel_map_init_extend(&m->channel_map, channels, PA_CHANNEL_MAP_ALSA);
1134
1135 alsa_mapping_add_ucm_device(m, device);
1136
1137 return 0;
1138 }
1139
1140 static int ucm_create_mapping_for_modifier(
1141 pa_alsa_ucm_config *ucm,
1142 pa_alsa_profile_set *ps,
1143 pa_alsa_profile *p,
1144 pa_alsa_ucm_modifier *modifier,
1145 const char *verb_name,
1146 const char *mod_name,
1147 const char *device_str,
1148 bool is_sink) {
1149
1150 pa_alsa_mapping *m;
1151 char *mapping_name;
1152
1153 mapping_name = pa_sprintf_malloc("Mapping %s: %s: %s", verb_name, device_str, is_sink ? "sink" : "source");
1154
1155 m = pa_alsa_mapping_get(ps, mapping_name);
1156 if (!m) {
1157 pa_log("no mapping for %s", mapping_name);
1158 pa_xfree(mapping_name);
1159 return -1;
1160 }
1161 pa_log_info("ucm mapping: %s modifier %s", mapping_name, mod_name);
1162 pa_xfree(mapping_name);
1163
1164 if (!m->ucm_context.ucm_devices && !m->ucm_context.ucm_modifiers) { /* new mapping */
1165 m->ucm_context.ucm_devices = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
1166 m->ucm_context.ucm_modifiers = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
1167 m->ucm_context.ucm = ucm;
1168 m->ucm_context.direction = is_sink ? PA_DIRECTION_OUTPUT : PA_DIRECTION_INPUT;
1169
1170 m->device_strings = pa_xnew0(char*, 2);
1171 m->device_strings[0] = pa_xstrdup(device_str);
1172 m->direction = is_sink ? PA_ALSA_DIRECTION_OUTPUT : PA_ALSA_DIRECTION_INPUT;
1173 /* Modifier sinks should not be routed to by default */
1174 m->priority = 0;
1175
1176 ucm_add_mapping(p, m);
1177 } else if (!m->ucm_context.ucm_modifiers) /* share pcm with device */
1178 m->ucm_context.ucm_modifiers = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
1179
1180 alsa_mapping_add_ucm_modifier(m, modifier);
1181
1182 return 0;
1183 }
1184
1185 static int ucm_create_mapping(
1186 pa_alsa_ucm_config *ucm,
1187 pa_alsa_profile_set *ps,
1188 pa_alsa_profile *p,
1189 pa_alsa_ucm_device *device,
1190 const char *verb_name,
1191 const char *device_name,
1192 const char *sink,
1193 const char *source) {
1194
1195 int ret = 0;
1196
1197 if (!sink && !source) {
1198 pa_log("No sink and source at %s: %s", verb_name, device_name);
1199 return -1;
1200 }
1201
1202 if (sink)
1203 ret = ucm_create_mapping_direction(ucm, ps, p, device, verb_name, device_name, sink, TRUE);
1204 if (ret == 0 && source)
1205 ret = ucm_create_mapping_direction(ucm, ps, p, device, verb_name, device_name, source, FALSE);
1206
1207 return ret;
1208 }
1209
1210 static pa_alsa_jack* ucm_get_jack(pa_alsa_ucm_config *ucm, const char *dev_name, const char *pre_tag) {
1211 pa_alsa_jack *j;
1212 char *name = pa_sprintf_malloc("%s%s", pre_tag, dev_name);
1213
1214 PA_LLIST_FOREACH(j, ucm->jacks)
1215 if (pa_streq(j->name, name))
1216 goto out;
1217
1218 j = pa_xnew0(pa_alsa_jack, 1);
1219 j->state_unplugged = PA_AVAILABLE_NO;
1220 j->state_plugged = PA_AVAILABLE_YES;
1221 j->name = pa_xstrdup(name);
1222 j->alsa_name = pa_sprintf_malloc("%s Jack", dev_name);
1223
1224 PA_LLIST_PREPEND(pa_alsa_jack, ucm->jacks, j);
1225
1226 out:
1227 pa_xfree(name);
1228 return j;
1229 }
1230
1231 static int ucm_create_profile(
1232 pa_alsa_ucm_config *ucm,
1233 pa_alsa_profile_set *ps,
1234 pa_alsa_ucm_verb *verb,
1235 const char *verb_name,
1236 const char *verb_desc) {
1237
1238 pa_alsa_profile *p;
1239 pa_alsa_ucm_device *dev;
1240 pa_alsa_ucm_modifier *mod;
1241 int i = 0;
1242 const char *name, *sink, *source;
1243 char *verb_cmp, *c;
1244
1245 pa_assert(ps);
1246
1247 if (pa_hashmap_get(ps->profiles, verb_name)) {
1248 pa_log("Verb %s already exists", verb_name);
1249 return -1;
1250 }
1251
1252 p = pa_xnew0(pa_alsa_profile, 1);
1253 p->profile_set = ps;
1254 p->name = pa_xstrdup(verb_name);
1255 p->description = pa_xstrdup(verb_desc);
1256
1257 p->output_mappings = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
1258 p->input_mappings = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
1259
1260 p->supported = TRUE;
1261 pa_hashmap_put(ps->profiles, p->name, p);
1262
1263 /* TODO: get profile priority from ucm info or policy management */
1264 c = verb_cmp = pa_xstrdup(verb_name);
1265 while (*c) {
1266 if (*c == '_') *c = ' ';
1267 c++;
1268 }
1269
1270 for (i = 0; verb_info[i].id; i++) {
1271 if (strcasecmp(verb_info[i].id, verb_cmp) == 0) {
1272 p->priority = verb_info[i].priority;
1273 break;
1274 }
1275 }
1276
1277 pa_xfree(verb_cmp);
1278
1279 if (verb_info[i].id == NULL)
1280 p->priority = 1000;
1281
1282 PA_LLIST_FOREACH(dev, verb->devices) {
1283 name = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_NAME);
1284
1285 sink = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_SINK);
1286 source = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_SOURCE);
1287
1288 ucm_create_mapping(ucm, ps, p, dev, verb_name, name, sink, source);
1289
1290 if (sink)
1291 dev->output_jack = ucm_get_jack(ucm, name, PA_UCM_PRE_TAG_OUTPUT);
1292 if (source)
1293 dev->input_jack = ucm_get_jack(ucm, name, PA_UCM_PRE_TAG_INPUT);
1294 }
1295
1296 /* Now find modifiers that have their own PlaybackPCM and create
1297 * separate sinks for them. */
1298 PA_LLIST_FOREACH(mod, verb->modifiers) {
1299 name = pa_proplist_gets(mod->proplist, PA_ALSA_PROP_UCM_NAME);
1300
1301 sink = pa_proplist_gets(mod->proplist, PA_ALSA_PROP_UCM_SINK);
1302 source = pa_proplist_gets(mod->proplist, PA_ALSA_PROP_UCM_SOURCE);
1303
1304 if (sink)
1305 ucm_create_mapping_for_modifier(ucm, ps, p, mod, verb_name, name, sink, TRUE);
1306 else if (source)
1307 ucm_create_mapping_for_modifier(ucm, ps, p, mod, verb_name, name, source, FALSE);
1308 }
1309
1310 pa_alsa_profile_dump(p);
1311
1312 return 0;
1313 }
1314
1315 static snd_pcm_t* mapping_open_pcm(pa_alsa_ucm_config *ucm, pa_alsa_mapping *m, int mode) {
1316 snd_pcm_t* pcm;
1317 pa_sample_spec try_ss = ucm->core->default_sample_spec;
1318 pa_channel_map try_map;
1319 snd_pcm_uframes_t try_period_size, try_buffer_size;
1320 bool exact_channels = m->channel_map.channels > 0;
1321
1322 if (exact_channels) {
1323 try_map = m->channel_map;
1324 try_ss.channels = try_map.channels;
1325 } else
1326 pa_channel_map_init_extend(&try_map, try_ss.channels, PA_CHANNEL_MAP_ALSA);
1327
1328 try_period_size =
1329 pa_usec_to_bytes(ucm->core->default_fragment_size_msec * PA_USEC_PER_MSEC, &try_ss) /
1330 pa_frame_size(&try_ss);
1331 try_buffer_size = ucm->core->default_n_fragments * try_period_size;
1332
1333 pcm = pa_alsa_open_by_device_string(m->device_strings[0], NULL, &try_ss,
1334 &try_map, mode, &try_period_size, &try_buffer_size, 0, NULL, NULL, exact_channels);
1335
1336 if (pcm && !exact_channels)
1337 m->channel_map = try_map;
1338
1339 return pcm;
1340 }
1341
1342 static void profile_finalize_probing(pa_alsa_profile *p) {
1343 pa_alsa_mapping *m;
1344 uint32_t idx;
1345
1346 PA_IDXSET_FOREACH(m, p->output_mappings, idx) {
1347 if (p->supported)
1348 m->supported++;
1349
1350 if (!m->output_pcm)
1351 continue;
1352
1353 snd_pcm_close(m->output_pcm);
1354 m->output_pcm = NULL;
1355 }
1356
1357 PA_IDXSET_FOREACH(m, p->input_mappings, idx) {
1358 if (p->supported)
1359 m->supported++;
1360
1361 if (!m->input_pcm)
1362 continue;
1363
1364 snd_pcm_close(m->input_pcm);
1365 m->input_pcm = NULL;
1366 }
1367 }
1368
1369 static void ucm_mapping_jack_probe(pa_alsa_mapping *m) {
1370 snd_pcm_t *pcm_handle;
1371 snd_mixer_t *mixer_handle;
1372 snd_hctl_t *hctl_handle;
1373 pa_alsa_ucm_mapping_context *context = &m->ucm_context;
1374 pa_alsa_ucm_device *dev;
1375 uint32_t idx;
1376
1377 pcm_handle = m->direction == PA_ALSA_DIRECTION_OUTPUT ? m->output_pcm : m->input_pcm;
1378 mixer_handle = pa_alsa_open_mixer_for_pcm(pcm_handle, NULL, &hctl_handle);
1379 if (!mixer_handle || !hctl_handle)
1380 return;
1381
1382 PA_IDXSET_FOREACH(dev, context->ucm_devices, idx) {
1383 pa_alsa_jack *jack;
1384 jack = m->direction == PA_ALSA_DIRECTION_OUTPUT ? dev->output_jack : dev->input_jack;
1385 pa_assert (jack);
1386 jack->has_control = pa_alsa_find_jack(hctl_handle, jack->alsa_name) != NULL;
1387 pa_log_info("UCM jack %s has_control=%d", jack->name, jack->has_control);
1388 }
1389
1390 snd_mixer_close(mixer_handle);
1391 }
1392
1393 static void ucm_probe_profile_set(pa_alsa_ucm_config *ucm, pa_alsa_profile_set *ps) {
1394 void *state;
1395 pa_alsa_profile *p;
1396 pa_alsa_mapping *m;
1397 uint32_t idx;
1398
1399 PA_HASHMAP_FOREACH(p, ps->profiles, state) {
1400 /* change verb */
1401 pa_log_info("Set ucm verb to %s", p->name);
1402
1403 if ((snd_use_case_set(ucm->ucm_mgr, "_verb", p->name)) < 0) {
1404 pa_log("Failed to set verb %s", p->name);
1405 p->supported = FALSE;
1406 continue;
1407 }
1408
1409 PA_IDXSET_FOREACH(m, p->output_mappings, idx) {
1410 if (PA_UCM_IS_MODIFIER_MAPPING(m)) {
1411 /* Skip jack probing on modifier PCMs since we expect this to
1412 * only be controlled on the main device/verb PCM. */
1413 continue;
1414 }
1415
1416 m->output_pcm = mapping_open_pcm(ucm, m, SND_PCM_STREAM_PLAYBACK);
1417 if (!m->output_pcm) {
1418 p->supported = FALSE;
1419 break;
1420 }
1421 }
1422
1423 if (p->supported) {
1424 PA_IDXSET_FOREACH(m, p->input_mappings, idx) {
1425 if (PA_UCM_IS_MODIFIER_MAPPING(m)) {
1426 /* Skip jack probing on modifier PCMs since we expect this to
1427 * only be controlled on the main device/verb PCM. */
1428 continue;
1429 }
1430
1431 m->input_pcm = mapping_open_pcm(ucm, m, SND_PCM_STREAM_CAPTURE);
1432 if (!m->input_pcm) {
1433 p->supported = FALSE;
1434 break;
1435 }
1436 }
1437 }
1438
1439 if (!p->supported) {
1440 profile_finalize_probing(p);
1441 continue;
1442 }
1443
1444 pa_log_debug("Profile %s supported.", p->name);
1445
1446 PA_IDXSET_FOREACH(m, p->output_mappings, idx)
1447 if (!PA_UCM_IS_MODIFIER_MAPPING(m))
1448 ucm_mapping_jack_probe(m);
1449
1450 PA_IDXSET_FOREACH(m, p->input_mappings, idx)
1451 if (!PA_UCM_IS_MODIFIER_MAPPING(m))
1452 ucm_mapping_jack_probe(m);
1453
1454 profile_finalize_probing(p);
1455 }
1456
1457 /* restore ucm state */
1458 snd_use_case_set(ucm->ucm_mgr, "_verb", SND_USE_CASE_VERB_INACTIVE);
1459
1460 pa_alsa_profile_set_drop_unsupported(ps);
1461 }
1462
1463 pa_alsa_profile_set* pa_alsa_ucm_add_profile_set(pa_alsa_ucm_config *ucm, pa_channel_map *default_channel_map) {
1464 pa_alsa_ucm_verb *verb;
1465 pa_alsa_profile_set *ps;
1466
1467 ps = pa_xnew0(pa_alsa_profile_set, 1);
1468 ps->mappings = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
1469 ps->profiles = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
1470 ps->decibel_fixes = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
1471
1472 /* create a profile for each verb */
1473 PA_LLIST_FOREACH(verb, ucm->verbs) {
1474 const char *verb_name;
1475 const char *verb_desc;
1476
1477 verb_name = pa_proplist_gets(verb->proplist, PA_ALSA_PROP_UCM_NAME);
1478 verb_desc = pa_proplist_gets(verb->proplist, PA_ALSA_PROP_UCM_DESCRIPTION);
1479 if (verb_name == NULL) {
1480 pa_log("Verb with no name");
1481 continue;
1482 }
1483
1484 ucm_create_profile(ucm, ps, verb, verb_name, verb_desc);
1485 }
1486
1487 ucm_probe_profile_set(ucm, ps);
1488 ps->probed = TRUE;
1489
1490 return ps;
1491 }
1492
1493 static void free_verb(pa_alsa_ucm_verb *verb) {
1494 pa_alsa_ucm_device *di, *dn;
1495 pa_alsa_ucm_modifier *mi, *mn;
1496
1497 PA_LLIST_FOREACH_SAFE(di, dn, verb->devices) {
1498 PA_LLIST_REMOVE(pa_alsa_ucm_device, verb->devices, di);
1499 pa_proplist_free(di->proplist);
1500 if (di->conflicting_devices)
1501 pa_idxset_free(di->conflicting_devices, NULL);
1502 if (di->supported_devices)
1503 pa_idxset_free(di->supported_devices, NULL);
1504 pa_xfree(di);
1505 }
1506
1507 PA_LLIST_FOREACH_SAFE(mi, mn, verb->modifiers) {
1508 PA_LLIST_REMOVE(pa_alsa_ucm_modifier, verb->modifiers, mi);
1509 pa_proplist_free(mi->proplist);
1510 if (mi->n_suppdev > 0)
1511 snd_use_case_free_list(mi->supported_devices, mi->n_suppdev);
1512 if (mi->n_confdev > 0)
1513 snd_use_case_free_list(mi->conflicting_devices, mi->n_confdev);
1514 pa_xfree(mi->media_role);
1515 pa_xfree(mi);
1516 }
1517 pa_proplist_free(verb->proplist);
1518 pa_xfree(verb);
1519 }
1520
1521 void pa_alsa_ucm_free(pa_alsa_ucm_config *ucm) {
1522 pa_alsa_ucm_verb *vi, *vn;
1523 pa_alsa_jack *ji, *jn;
1524
1525 PA_LLIST_FOREACH_SAFE(vi, vn, ucm->verbs) {
1526 PA_LLIST_REMOVE(pa_alsa_ucm_verb, ucm->verbs, vi);
1527 free_verb(vi);
1528 }
1529 PA_LLIST_FOREACH_SAFE(ji, jn, ucm->jacks) {
1530 PA_LLIST_REMOVE(pa_alsa_jack, ucm->jacks, ji);
1531 pa_xfree(ji->alsa_name);
1532 pa_xfree(ji->name);
1533 pa_xfree(ji);
1534 }
1535 if (ucm->ucm_mgr) {
1536 snd_use_case_mgr_close(ucm->ucm_mgr);
1537 ucm->ucm_mgr = NULL;
1538 }
1539 }
1540
1541 void pa_alsa_ucm_mapping_context_free(pa_alsa_ucm_mapping_context *context) {
1542 pa_alsa_ucm_device *dev;
1543 pa_alsa_ucm_modifier *mod;
1544 uint32_t idx;
1545
1546 if (context->ucm_devices) {
1547 /* clear ucm device pointer to mapping */
1548 PA_IDXSET_FOREACH(dev, context->ucm_devices, idx) {
1549 if (context->direction == PA_DIRECTION_OUTPUT)
1550 dev->playback_mapping = NULL;
1551 else
1552 dev->capture_mapping = NULL;
1553 }
1554
1555 pa_idxset_free(context->ucm_devices, NULL);
1556 }
1557
1558 if (context->ucm_modifiers) {
1559 PA_IDXSET_FOREACH(mod, context->ucm_modifiers, idx) {
1560 if (context->direction == PA_DIRECTION_OUTPUT)
1561 mod->playback_mapping = NULL;
1562 else
1563 mod->capture_mapping = NULL;
1564 }
1565
1566 pa_idxset_free(context->ucm_modifiers, NULL);
1567 }
1568 }
1569
1570 /* Enable the modifier when the first stream with matched role starts */
1571 void pa_alsa_ucm_roled_stream_begin(pa_alsa_ucm_config *ucm, const char *role, pa_direction_t dir) {
1572 pa_alsa_ucm_modifier *mod;
1573
1574 if (!ucm->active_verb)
1575 return;
1576
1577 PA_LLIST_FOREACH(mod, ucm->active_verb->modifiers) {
1578 if ((mod->action_direction == dir) && (pa_streq(mod->media_role, role))) {
1579 if (mod->enabled_counter == 0) {
1580 const char *mod_name = pa_proplist_gets(mod->proplist, PA_ALSA_PROP_UCM_NAME);
1581
1582 pa_log_info("Enable ucm modifier %s", mod_name);
1583 if (snd_use_case_set(ucm->ucm_mgr, "_enamod", mod_name) < 0) {
1584 pa_log("Failed to enable ucm modifier %s", mod_name);
1585 }
1586 }
1587
1588 mod->enabled_counter++;
1589 break;
1590 }
1591 }
1592 }
1593
1594 /* Disable the modifier when the last stream with matched role ends */
1595 void pa_alsa_ucm_roled_stream_end(pa_alsa_ucm_config *ucm, const char *role, pa_direction_t dir) {
1596 pa_alsa_ucm_modifier *mod;
1597
1598 if (!ucm->active_verb)
1599 return;
1600
1601 PA_LLIST_FOREACH(mod, ucm->active_verb->modifiers) {
1602 if ((mod->action_direction == dir) && (pa_streq(mod->media_role, role))) {
1603
1604 mod->enabled_counter--;
1605 if (mod->enabled_counter == 0) {
1606 const char *mod_name = pa_proplist_gets(mod->proplist, PA_ALSA_PROP_UCM_NAME);
1607
1608 pa_log_info("Disable ucm modifier %s", mod_name);
1609 if (snd_use_case_set(ucm->ucm_mgr, "_dismod", mod_name) < 0) {
1610 pa_log("Failed to disable ucm modifier %s", mod_name);
1611 }
1612 }
1613
1614 break;
1615 }
1616 }
1617 }