]> code.delx.au - pulseaudio/blob - src/modules/alsa/alsa-ucm.c
677db9947a3ee4c925a157fc71b51fd6aaa95add
[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 char *card_name;
530 const char **verb_list;
531 int num_verbs, i, err = 0;
532
533 /* is UCM available for this card ? */
534 err = snd_card_get_name(card_index, &card_name);
535 if (err < 0) {
536 pa_log("Card can't get card_name from card_index %d", card_index);
537 goto name_fail;
538 }
539
540 err = snd_use_case_mgr_open(&ucm->ucm_mgr, card_name);
541 if (err < 0) {
542 pa_log_info("UCM not available for card %s", card_name);
543 goto ucm_mgr_fail;
544 }
545
546 pa_log_info("UCM available for card %s", card_name);
547
548 /* get a list of all UCM verbs (profiles) for this card */
549 num_verbs = snd_use_case_verb_list(ucm->ucm_mgr, &verb_list);
550 if (num_verbs < 0) {
551 pa_log("UCM verb list not found for %s", card_name);
552 goto ucm_verb_fail;
553 }
554
555 /* get the properties of each UCM verb */
556 for (i = 0; i < num_verbs; i += 2) {
557 pa_alsa_ucm_verb *verb;
558
559 /* Get devices and modifiers for each verb */
560 err = pa_alsa_ucm_get_verb(ucm->ucm_mgr, verb_list[i], verb_list[i+1], &verb);
561 if (err < 0) {
562 pa_log("Failed to get the verb %s", verb_list[i]);
563 continue;
564 }
565
566 PA_LLIST_PREPEND(pa_alsa_ucm_verb, ucm->verbs, verb);
567 }
568
569 if (!ucm->verbs) {
570 pa_log("No UCM verb is valid for %s", card_name);
571 err = -1;
572 }
573
574 snd_use_case_free_list(verb_list, num_verbs);
575
576 ucm_verb_fail:
577 if (err < 0) {
578 snd_use_case_mgr_close(ucm->ucm_mgr);
579 ucm->ucm_mgr = NULL;
580 }
581
582 ucm_mgr_fail:
583 free(card_name);
584
585 name_fail:
586 return err;
587 }
588
589 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) {
590 pa_alsa_ucm_device *d;
591 pa_alsa_ucm_modifier *mod;
592 pa_alsa_ucm_verb *verb;
593 int err = 0;
594
595 *p_verb = NULL;
596 pa_log_info("Set UCM verb to %s", verb_name);
597 err = snd_use_case_set(uc_mgr, "_verb", verb_name);
598 if (err < 0)
599 return err;
600
601 verb = pa_xnew0(pa_alsa_ucm_verb, 1);
602 verb->proplist = pa_proplist_new();
603
604 pa_proplist_sets(verb->proplist, PA_ALSA_PROP_UCM_NAME, pa_strnull(verb_name));
605 pa_proplist_sets(verb->proplist, PA_ALSA_PROP_UCM_DESCRIPTION, pa_strna(verb_desc));
606
607 err = ucm_get_devices(verb, uc_mgr);
608 if (err < 0)
609 pa_log("No UCM devices for verb %s", verb_name);
610
611 err = ucm_get_modifiers(verb, uc_mgr);
612 if (err < 0)
613 pa_log("No UCM modifiers for verb %s", verb_name);
614
615 /* Verb properties */
616 ucm_get_property(verb, uc_mgr, verb_name);
617
618 PA_LLIST_FOREACH(d, verb->devices) {
619 const char *dev_name = pa_proplist_gets(d->proplist, PA_ALSA_PROP_UCM_NAME);
620
621 /* Devices properties */
622 ucm_get_device_property(d, uc_mgr, verb, dev_name);
623 }
624 /* make conflicting or supported device mutual */
625 PA_LLIST_FOREACH(d, verb->devices)
626 append_lost_relationship(d);
627
628 PA_LLIST_FOREACH(mod, verb->modifiers) {
629 const char *mod_name = pa_proplist_gets(mod->proplist, PA_ALSA_PROP_UCM_NAME);
630
631 /* Modifier properties */
632 ucm_get_modifier_property(mod, uc_mgr, mod_name);
633
634 /* Set PA_PROP_DEVICE_INTENDED_ROLES property to devices */
635 pa_log_debug("Set media roles for verb %s, modifier %s", verb_name, mod_name);
636 ucm_set_media_roles(mod, verb->devices, mod_name);
637 }
638
639 *p_verb = verb;
640 return 0;
641 }
642
643 static void ucm_add_port_combination(
644 pa_hashmap *hash,
645 pa_alsa_ucm_mapping_context *context,
646 bool is_sink,
647 pa_alsa_ucm_device **pdevices,
648 int num,
649 pa_hashmap *ports,
650 pa_card_profile *cp,
651 pa_core *core) {
652
653 pa_device_port *port;
654 int i;
655 unsigned priority;
656 char *name, *desc;
657 const char *dev_name;
658 const char *direction;
659 pa_alsa_ucm_device *dev;
660
661 dev = pdevices[0];
662 dev_name = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_NAME);
663
664 name = pa_sprintf_malloc("%s%s", is_sink ? PA_UCM_PRE_TAG_OUTPUT : PA_UCM_PRE_TAG_INPUT, dev_name);
665 desc = num == 1 ? pa_xstrdup(pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_DESCRIPTION))
666 : pa_sprintf_malloc("Combination port for %s", dev_name);
667
668 priority = is_sink ? dev->playback_priority : dev->capture_priority;
669
670 for (i = 1; i < num; i++) {
671 char *tmp;
672
673 dev = pdevices[i];
674 dev_name = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_NAME);
675
676 tmp = pa_sprintf_malloc("%s+%s", name, dev_name);
677 pa_xfree(name);
678 name = tmp;
679
680 tmp = pa_sprintf_malloc("%s,%s", desc, dev_name);
681 pa_xfree(desc);
682 desc = tmp;
683
684 /* FIXME: Is this true? */
685 priority += (is_sink ? dev->playback_priority : dev->capture_priority);
686 }
687
688 port = pa_hashmap_get(ports, name);
689 if (!port) {
690 pa_device_port_new_data port_data;
691
692 pa_device_port_new_data_init(&port_data);
693 pa_device_port_new_data_set_name(&port_data, name);
694 pa_device_port_new_data_set_description(&port_data, desc);
695 pa_device_port_new_data_set_direction(&port_data, is_sink ? PA_DIRECTION_OUTPUT : PA_DIRECTION_INPUT);
696
697 port = pa_device_port_new(core, &port_data, 0);
698 pa_device_port_new_data_done(&port_data);
699 pa_assert(port);
700
701 pa_hashmap_put(ports, port->name, port);
702 pa_log_debug("Add port %s: %s", port->name, port->description);
703 port->profiles = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
704 }
705
706 port->priority = priority;
707
708 pa_xfree(name);
709 pa_xfree(desc);
710
711 direction = is_sink ? "output" : "input";
712 pa_log_debug("Port %s direction %s, priority %d", port->name, direction, priority);
713
714 if (cp) {
715 pa_log_debug("Adding port %s to profile %s", port->name, cp->name);
716 pa_hashmap_put(port->profiles, cp->name, cp);
717 }
718
719 if (hash) {
720 pa_hashmap_put(hash, port->name, port);
721 pa_device_port_ref(port);
722 }
723 }
724
725 static int ucm_port_contains(const char *port_name, const char *dev_name, bool is_sink) {
726 int ret = 0;
727 const char *r;
728 const char *state = NULL;
729 int len;
730
731 if (!port_name || !dev_name)
732 return false;
733
734 port_name += is_sink ? strlen(PA_UCM_PRE_TAG_OUTPUT) : strlen(PA_UCM_PRE_TAG_INPUT);
735
736 while ((r = pa_split_in_place(port_name, "+", &len, &state))) {
737 if (!strncmp(r, dev_name, len)) {
738 ret = 1;
739 break;
740 }
741 }
742
743 return ret;
744 }
745
746 static int ucm_check_conformance(
747 pa_alsa_ucm_mapping_context *context,
748 pa_alsa_ucm_device **pdevices,
749 int dev_num,
750 pa_alsa_ucm_device *dev) {
751
752 uint32_t idx;
753 pa_alsa_ucm_device *d;
754 int i;
755
756 pa_assert(dev);
757
758 pa_log_debug("Check device %s conformance with %d other devices",
759 pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_NAME), dev_num);
760 if (dev_num == 0) {
761 pa_log_debug("First device in combination, number 1");
762 return 1;
763 }
764
765 if (dev->conflicting_devices) { /* the device defines conflicting devices */
766 PA_IDXSET_FOREACH(d, dev->conflicting_devices, idx) {
767 for (i = 0; i < dev_num; i++) {
768 if (pdevices[i] == d) {
769 pa_log_debug("Conflicting device found");
770 return 0;
771 }
772 }
773 }
774 } else if (dev->supported_devices) { /* the device defines supported devices */
775 for (i = 0; i < dev_num; i++) {
776 if (!ucm_device_exists(dev->supported_devices, pdevices[i])) {
777 pa_log_debug("Supported device not found");
778 return 0;
779 }
780 }
781 } else { /* not support any other devices */
782 pa_log_debug("Not support any other devices");
783 return 0;
784 }
785
786 pa_log_debug("Device added to combination, number %d", dev_num + 1);
787 return 1;
788 }
789
790 static inline pa_alsa_ucm_device *get_next_device(pa_idxset *idxset, uint32_t *idx) {
791 pa_alsa_ucm_device *dev;
792
793 if (*idx == PA_IDXSET_INVALID)
794 dev = pa_idxset_first(idxset, idx);
795 else
796 dev = pa_idxset_next(idxset, idx);
797
798 return dev;
799 }
800
801 static void ucm_add_ports_combination(
802 pa_hashmap *hash,
803 pa_alsa_ucm_mapping_context *context,
804 bool is_sink,
805 pa_alsa_ucm_device **pdevices,
806 int dev_num,
807 uint32_t map_index,
808 pa_hashmap *ports,
809 pa_card_profile *cp,
810 pa_core *core) {
811
812 pa_alsa_ucm_device *dev;
813 uint32_t idx = map_index;
814
815 if ((dev = get_next_device(context->ucm_devices, &idx)) == NULL)
816 return;
817
818 /* check if device at map_index can combine with existing devices combination */
819 if (ucm_check_conformance(context, pdevices, dev_num, dev)) {
820 /* add device at map_index to devices combination */
821 pdevices[dev_num] = dev;
822 /* add current devices combination as a new port */
823 ucm_add_port_combination(hash, context, is_sink, pdevices, dev_num + 1, ports, cp, core);
824 /* try more elements combination */
825 ucm_add_ports_combination(hash, context, is_sink, pdevices, dev_num + 1, idx, ports, cp, core);
826 }
827
828 /* try other device with current elements number */
829 ucm_add_ports_combination(hash, context, is_sink, pdevices, dev_num, idx, ports, cp, core);
830 }
831
832 static char* merge_roles(const char *cur, const char *add) {
833 char *r, *ret;
834 const char *state = NULL;
835
836 if (add == NULL)
837 return pa_xstrdup(cur);
838 else if (cur == NULL)
839 return pa_xstrdup(add);
840
841 ret = pa_xstrdup(cur);
842
843 while ((r = pa_split_spaces(add, &state))) {
844 char *value;
845
846 if (!pa_str_in_list_spaces(ret, r))
847 value = pa_sprintf_malloc("%s %s", ret, r);
848 else {
849 pa_xfree(r);
850 continue;
851 }
852
853 pa_xfree(ret);
854 ret = value;
855 pa_xfree(r);
856 }
857
858 return ret;
859 }
860
861 void pa_alsa_ucm_add_ports_combination(
862 pa_hashmap *p,
863 pa_alsa_ucm_mapping_context *context,
864 bool is_sink,
865 pa_hashmap *ports,
866 pa_card_profile *cp,
867 pa_core *core) {
868
869 pa_alsa_ucm_device **pdevices;
870
871 pa_assert(context->ucm_devices);
872
873 if (pa_idxset_size(context->ucm_devices) > 0) {
874 pdevices = pa_xnew(pa_alsa_ucm_device *, pa_idxset_size(context->ucm_devices));
875 ucm_add_ports_combination(p, context, is_sink, pdevices, 0, PA_IDXSET_INVALID, ports, cp, core);
876 pa_xfree(pdevices);
877 }
878 }
879
880 void pa_alsa_ucm_add_ports(
881 pa_hashmap **p,
882 pa_proplist *proplist,
883 pa_alsa_ucm_mapping_context *context,
884 bool is_sink,
885 pa_card *card) {
886
887 uint32_t idx;
888 char *merged_roles;
889 const char *role_name = is_sink ? PA_ALSA_PROP_UCM_PLAYBACK_ROLES : PA_ALSA_PROP_UCM_CAPTURE_ROLES;
890 pa_alsa_ucm_device *dev;
891 pa_alsa_ucm_modifier *mod;
892 char *tmp;
893
894 pa_assert(p);
895 pa_assert(*p);
896
897 /* add ports first */
898 pa_alsa_ucm_add_ports_combination(*p, context, is_sink, card->ports, NULL, card->core);
899
900 /* then set property PA_PROP_DEVICE_INTENDED_ROLES */
901 merged_roles = pa_xstrdup(pa_proplist_gets(proplist, PA_PROP_DEVICE_INTENDED_ROLES));
902 PA_IDXSET_FOREACH(dev, context->ucm_devices, idx) {
903 const char *roles = pa_proplist_gets(dev->proplist, role_name);
904 tmp = merge_roles(merged_roles, roles);
905 pa_xfree(merged_roles);
906 merged_roles = tmp;
907 }
908
909 if (context->ucm_modifiers)
910 PA_IDXSET_FOREACH(mod, context->ucm_modifiers, idx) {
911 tmp = merge_roles(merged_roles, mod->media_role);
912 pa_xfree(merged_roles);
913 merged_roles = tmp;
914 }
915
916 if (merged_roles)
917 pa_proplist_sets(proplist, PA_PROP_DEVICE_INTENDED_ROLES, merged_roles);
918
919 pa_log_info("ALSA device %s roles: %s", pa_proplist_gets(proplist, PA_PROP_DEVICE_STRING), pa_strnull(merged_roles));
920 pa_xfree(merged_roles);
921 }
922
923 /* Change UCM verb and device to match selected card profile */
924 int pa_alsa_ucm_set_profile(pa_alsa_ucm_config *ucm, const char *new_profile, const char *old_profile) {
925 int ret = 0;
926 const char *profile;
927 pa_alsa_ucm_verb *verb;
928
929 if (new_profile == old_profile)
930 return ret;
931 else if (new_profile == NULL || old_profile == NULL)
932 profile = new_profile ? new_profile : SND_USE_CASE_VERB_INACTIVE;
933 else if (!pa_streq(new_profile, old_profile))
934 profile = new_profile;
935 else
936 return ret;
937
938 /* change verb */
939 pa_log_info("Set UCM verb to %s", profile);
940 if ((snd_use_case_set(ucm->ucm_mgr, "_verb", profile)) < 0) {
941 pa_log("Failed to set verb %s", profile);
942 ret = -1;
943 }
944
945 /* find active verb */
946 ucm->active_verb = NULL;
947 PA_LLIST_FOREACH(verb, ucm->verbs) {
948 const char *verb_name;
949 verb_name = pa_proplist_gets(verb->proplist, PA_ALSA_PROP_UCM_NAME);
950 if (pa_streq(verb_name, profile)) {
951 ucm->active_verb = verb;
952 break;
953 }
954 }
955
956 return ret;
957 }
958
959 int pa_alsa_ucm_set_port(pa_alsa_ucm_mapping_context *context, pa_device_port *port, bool is_sink) {
960 int i;
961 int ret = 0;
962 pa_alsa_ucm_config *ucm;
963 const char **enable_devs;
964 int enable_num = 0;
965 uint32_t idx;
966 pa_alsa_ucm_device *dev;
967
968 pa_assert(context && context->ucm);
969
970 ucm = context->ucm;
971 pa_assert(ucm->ucm_mgr);
972
973 enable_devs = pa_xnew(const char *, pa_idxset_size(context->ucm_devices));
974
975 /* first disable then enable */
976 PA_IDXSET_FOREACH(dev, context->ucm_devices, idx) {
977 const char *dev_name = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_NAME);
978
979 if (ucm_port_contains(port->name, dev_name, is_sink))
980 enable_devs[enable_num++] = dev_name;
981 else {
982 pa_log_debug("Disable ucm device %s", dev_name);
983 if (snd_use_case_set(ucm->ucm_mgr, "_disdev", dev_name) > 0) {
984 pa_log("Failed to disable ucm device %s", dev_name);
985 ret = -1;
986 break;
987 }
988 }
989 }
990
991 for (i = 0; i < enable_num; i++) {
992 pa_log_debug("Enable ucm device %s", enable_devs[i]);
993 if (snd_use_case_set(ucm->ucm_mgr, "_enadev", enable_devs[i]) < 0) {
994 pa_log("Failed to enable ucm device %s", enable_devs[i]);
995 ret = -1;
996 break;
997 }
998 }
999
1000 pa_xfree(enable_devs);
1001
1002 return ret;
1003 }
1004
1005 static void ucm_add_mapping(pa_alsa_profile *p, pa_alsa_mapping *m) {
1006
1007 switch (m->direction) {
1008 case PA_ALSA_DIRECTION_ANY:
1009 pa_idxset_put(p->output_mappings, m, NULL);
1010 pa_idxset_put(p->input_mappings, m, NULL);
1011 break;
1012 case PA_ALSA_DIRECTION_OUTPUT:
1013 pa_idxset_put(p->output_mappings, m, NULL);
1014 break;
1015 case PA_ALSA_DIRECTION_INPUT:
1016 pa_idxset_put(p->input_mappings, m, NULL);
1017 break;
1018 }
1019 }
1020
1021 static void alsa_mapping_add_ucm_device(pa_alsa_mapping *m, pa_alsa_ucm_device *device) {
1022 char *cur_desc;
1023 const char *new_desc;
1024
1025 pa_idxset_put(m->ucm_context.ucm_devices, device, NULL);
1026
1027 new_desc = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_DESCRIPTION);
1028 cur_desc = m->description;
1029 if (cur_desc)
1030 m->description = pa_sprintf_malloc("%s + %s", cur_desc, new_desc);
1031 else
1032 m->description = pa_xstrdup(new_desc);
1033 pa_xfree(cur_desc);
1034
1035 /* walk around null case */
1036 m->description = m->description ? m->description : pa_xstrdup("");
1037
1038 /* save mapping to ucm device */
1039 if (m->direction == PA_ALSA_DIRECTION_OUTPUT)
1040 device->playback_mapping = m;
1041 else
1042 device->capture_mapping = m;
1043 }
1044
1045 static void alsa_mapping_add_ucm_modifier(pa_alsa_mapping *m, pa_alsa_ucm_modifier *modifier) {
1046 char *cur_desc;
1047 const char *new_desc, *mod_name, *channel_str;
1048 uint32_t channels = 0;
1049
1050 pa_idxset_put(m->ucm_context.ucm_modifiers, modifier, NULL);
1051
1052 new_desc = pa_proplist_gets(modifier->proplist, PA_ALSA_PROP_UCM_DESCRIPTION);
1053 cur_desc = m->description;
1054 if (cur_desc)
1055 m->description = pa_sprintf_malloc("%s + %s", cur_desc, new_desc);
1056 else
1057 m->description = pa_xstrdup(new_desc);
1058 pa_xfree(cur_desc);
1059
1060 if (!m->description)
1061 pa_xstrdup("");
1062
1063 /* Modifier sinks should not be routed to by default */
1064 m->priority = 0;
1065
1066 mod_name = pa_proplist_gets(modifier->proplist, PA_ALSA_PROP_UCM_NAME);
1067 pa_proplist_sets(m->proplist, PA_ALSA_PROP_UCM_MODIFIER, mod_name);
1068
1069 /* save mapping to ucm modifier */
1070 if (m->direction == PA_ALSA_DIRECTION_OUTPUT) {
1071 modifier->playback_mapping = m;
1072 channel_str = pa_proplist_gets(modifier->proplist, PA_ALSA_PROP_UCM_PLAYBACK_CHANNELS);
1073 } else {
1074 modifier->capture_mapping = m;
1075 channel_str = pa_proplist_gets(modifier->proplist, PA_ALSA_PROP_UCM_CAPTURE_CHANNELS);
1076 }
1077
1078 if (channel_str) {
1079 pa_assert_se(pa_atou(channel_str, &channels) == 0 && channels < PA_CHANNELS_MAX);
1080 pa_log_debug("Got channel count %" PRIu32 " for modifier", channels);
1081 }
1082
1083 if (channels)
1084 pa_channel_map_init_extend(&m->channel_map, channels, PA_CHANNEL_MAP_ALSA);
1085 else
1086 pa_channel_map_init(&m->channel_map);
1087 }
1088
1089 static int ucm_create_mapping_direction(
1090 pa_alsa_ucm_config *ucm,
1091 pa_alsa_profile_set *ps,
1092 pa_alsa_profile *p,
1093 pa_alsa_ucm_device *device,
1094 const char *verb_name,
1095 const char *device_name,
1096 const char *device_str,
1097 bool is_sink) {
1098
1099 pa_alsa_mapping *m;
1100 char *mapping_name;
1101 unsigned priority, channels;
1102
1103 mapping_name = pa_sprintf_malloc("Mapping %s: %s: %s", verb_name, device_str, is_sink ? "sink" : "source");
1104
1105 m = pa_alsa_mapping_get(ps, mapping_name);
1106 if (!m) {
1107 pa_log("No mapping for %s", mapping_name);
1108 pa_xfree(mapping_name);
1109 return -1;
1110 }
1111 pa_log_debug("UCM mapping: %s dev %s", mapping_name, device_name);
1112 pa_xfree(mapping_name);
1113
1114 priority = is_sink ? device->playback_priority : device->capture_priority;
1115 channels = is_sink ? device->playback_channels : device->capture_channels;
1116
1117 if (!m->ucm_context.ucm_devices) { /* new mapping */
1118 m->ucm_context.ucm_devices = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
1119 m->ucm_context.ucm = ucm;
1120 m->ucm_context.direction = is_sink ? PA_DIRECTION_OUTPUT : PA_DIRECTION_INPUT;
1121
1122 m->device_strings = pa_xnew0(char*, 2);
1123 m->device_strings[0] = pa_xstrdup(device_str);
1124 m->direction = is_sink ? PA_ALSA_DIRECTION_OUTPUT : PA_ALSA_DIRECTION_INPUT;
1125
1126 ucm_add_mapping(p, m);
1127 pa_channel_map_init_extend(&m->channel_map, channels, PA_CHANNEL_MAP_ALSA);
1128 }
1129
1130 /* mapping priority is the highest one of ucm devices */
1131 if (priority > m->priority)
1132 m->priority = priority;
1133
1134 /* mapping channels is the lowest one of ucm devices */
1135 if (channels < m->channel_map.channels)
1136 pa_channel_map_init_extend(&m->channel_map, channels, PA_CHANNEL_MAP_ALSA);
1137
1138 alsa_mapping_add_ucm_device(m, device);
1139
1140 return 0;
1141 }
1142
1143 static int ucm_create_mapping_for_modifier(
1144 pa_alsa_ucm_config *ucm,
1145 pa_alsa_profile_set *ps,
1146 pa_alsa_profile *p,
1147 pa_alsa_ucm_modifier *modifier,
1148 const char *verb_name,
1149 const char *mod_name,
1150 const char *device_str,
1151 bool is_sink) {
1152
1153 pa_alsa_mapping *m;
1154 char *mapping_name;
1155
1156 mapping_name = pa_sprintf_malloc("Mapping %s: %s: %s", verb_name, device_str, is_sink ? "sink" : "source");
1157
1158 m = pa_alsa_mapping_get(ps, mapping_name);
1159 if (!m) {
1160 pa_log("no mapping for %s", mapping_name);
1161 pa_xfree(mapping_name);
1162 return -1;
1163 }
1164 pa_log_info("ucm mapping: %s modifier %s", mapping_name, mod_name);
1165 pa_xfree(mapping_name);
1166
1167 if (!m->ucm_context.ucm_devices && !m->ucm_context.ucm_modifiers) { /* new mapping */
1168 m->ucm_context.ucm_devices = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
1169 m->ucm_context.ucm_modifiers = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
1170 m->ucm_context.ucm = ucm;
1171 m->ucm_context.direction = is_sink ? PA_DIRECTION_OUTPUT : PA_DIRECTION_INPUT;
1172
1173 m->device_strings = pa_xnew0(char*, 2);
1174 m->device_strings[0] = pa_xstrdup(device_str);
1175 m->direction = is_sink ? PA_ALSA_DIRECTION_OUTPUT : PA_ALSA_DIRECTION_INPUT;
1176 /* Modifier sinks should not be routed to by default */
1177 m->priority = 0;
1178
1179 ucm_add_mapping(p, m);
1180 } else if (!m->ucm_context.ucm_modifiers) /* share pcm with device */
1181 m->ucm_context.ucm_modifiers = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
1182
1183 alsa_mapping_add_ucm_modifier(m, modifier);
1184
1185 return 0;
1186 }
1187
1188 static int ucm_create_mapping(
1189 pa_alsa_ucm_config *ucm,
1190 pa_alsa_profile_set *ps,
1191 pa_alsa_profile *p,
1192 pa_alsa_ucm_device *device,
1193 const char *verb_name,
1194 const char *device_name,
1195 const char *sink,
1196 const char *source) {
1197
1198 int ret = 0;
1199
1200 if (!sink && !source) {
1201 pa_log("No sink and source at %s: %s", verb_name, device_name);
1202 return -1;
1203 }
1204
1205 if (sink)
1206 ret = ucm_create_mapping_direction(ucm, ps, p, device, verb_name, device_name, sink, true);
1207 if (ret == 0 && source)
1208 ret = ucm_create_mapping_direction(ucm, ps, p, device, verb_name, device_name, source, false);
1209
1210 return ret;
1211 }
1212
1213 static pa_alsa_jack* ucm_get_jack(pa_alsa_ucm_config *ucm, const char *dev_name, const char *pre_tag) {
1214 pa_alsa_jack *j;
1215 char *name = pa_sprintf_malloc("%s%s", pre_tag, dev_name);
1216
1217 PA_LLIST_FOREACH(j, ucm->jacks)
1218 if (pa_streq(j->name, name))
1219 goto out;
1220
1221 j = pa_xnew0(pa_alsa_jack, 1);
1222 j->state_unplugged = PA_AVAILABLE_NO;
1223 j->state_plugged = PA_AVAILABLE_YES;
1224 j->name = pa_xstrdup(name);
1225 j->alsa_name = pa_sprintf_malloc("%s Jack", dev_name);
1226
1227 PA_LLIST_PREPEND(pa_alsa_jack, ucm->jacks, j);
1228
1229 out:
1230 pa_xfree(name);
1231 return j;
1232 }
1233
1234 static int ucm_create_profile(
1235 pa_alsa_ucm_config *ucm,
1236 pa_alsa_profile_set *ps,
1237 pa_alsa_ucm_verb *verb,
1238 const char *verb_name,
1239 const char *verb_desc) {
1240
1241 pa_alsa_profile *p;
1242 pa_alsa_ucm_device *dev;
1243 pa_alsa_ucm_modifier *mod;
1244 int i = 0;
1245 const char *name, *sink, *source;
1246 char *verb_cmp, *c;
1247
1248 pa_assert(ps);
1249
1250 if (pa_hashmap_get(ps->profiles, verb_name)) {
1251 pa_log("Verb %s already exists", verb_name);
1252 return -1;
1253 }
1254
1255 p = pa_xnew0(pa_alsa_profile, 1);
1256 p->profile_set = ps;
1257 p->name = pa_xstrdup(verb_name);
1258 p->description = pa_xstrdup(verb_desc);
1259
1260 p->output_mappings = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
1261 p->input_mappings = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
1262
1263 p->supported = true;
1264 pa_hashmap_put(ps->profiles, p->name, p);
1265
1266 /* TODO: get profile priority from ucm info or policy management */
1267 c = verb_cmp = pa_xstrdup(verb_name);
1268 while (*c) {
1269 if (*c == '_') *c = ' ';
1270 c++;
1271 }
1272
1273 for (i = 0; verb_info[i].id; i++) {
1274 if (strcasecmp(verb_info[i].id, verb_cmp) == 0) {
1275 p->priority = verb_info[i].priority;
1276 break;
1277 }
1278 }
1279
1280 pa_xfree(verb_cmp);
1281
1282 if (verb_info[i].id == NULL)
1283 p->priority = 1000;
1284
1285 PA_LLIST_FOREACH(dev, verb->devices) {
1286 name = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_NAME);
1287
1288 sink = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_SINK);
1289 source = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_SOURCE);
1290
1291 ucm_create_mapping(ucm, ps, p, dev, verb_name, name, sink, source);
1292
1293 if (sink)
1294 dev->output_jack = ucm_get_jack(ucm, name, PA_UCM_PRE_TAG_OUTPUT);
1295 if (source)
1296 dev->input_jack = ucm_get_jack(ucm, name, PA_UCM_PRE_TAG_INPUT);
1297 }
1298
1299 /* Now find modifiers that have their own PlaybackPCM and create
1300 * separate sinks for them. */
1301 PA_LLIST_FOREACH(mod, verb->modifiers) {
1302 name = pa_proplist_gets(mod->proplist, PA_ALSA_PROP_UCM_NAME);
1303
1304 sink = pa_proplist_gets(mod->proplist, PA_ALSA_PROP_UCM_SINK);
1305 source = pa_proplist_gets(mod->proplist, PA_ALSA_PROP_UCM_SOURCE);
1306
1307 if (sink)
1308 ucm_create_mapping_for_modifier(ucm, ps, p, mod, verb_name, name, sink, true);
1309 else if (source)
1310 ucm_create_mapping_for_modifier(ucm, ps, p, mod, verb_name, name, source, false);
1311 }
1312
1313 pa_alsa_profile_dump(p);
1314
1315 return 0;
1316 }
1317
1318 static snd_pcm_t* mapping_open_pcm(pa_alsa_ucm_config *ucm, pa_alsa_mapping *m, int mode) {
1319 snd_pcm_t* pcm;
1320 pa_sample_spec try_ss = ucm->core->default_sample_spec;
1321 pa_channel_map try_map;
1322 snd_pcm_uframes_t try_period_size, try_buffer_size;
1323 bool exact_channels = m->channel_map.channels > 0;
1324
1325 if (exact_channels) {
1326 try_map = m->channel_map;
1327 try_ss.channels = try_map.channels;
1328 } else
1329 pa_channel_map_init_extend(&try_map, try_ss.channels, PA_CHANNEL_MAP_ALSA);
1330
1331 try_period_size =
1332 pa_usec_to_bytes(ucm->core->default_fragment_size_msec * PA_USEC_PER_MSEC, &try_ss) /
1333 pa_frame_size(&try_ss);
1334 try_buffer_size = ucm->core->default_n_fragments * try_period_size;
1335
1336 pcm = pa_alsa_open_by_device_string(m->device_strings[0], NULL, &try_ss,
1337 &try_map, mode, &try_period_size, &try_buffer_size, 0, NULL, NULL, exact_channels);
1338
1339 if (pcm && !exact_channels)
1340 m->channel_map = try_map;
1341
1342 return pcm;
1343 }
1344
1345 static void profile_finalize_probing(pa_alsa_profile *p) {
1346 pa_alsa_mapping *m;
1347 uint32_t idx;
1348
1349 PA_IDXSET_FOREACH(m, p->output_mappings, idx) {
1350 if (p->supported)
1351 m->supported++;
1352
1353 if (!m->output_pcm)
1354 continue;
1355
1356 snd_pcm_close(m->output_pcm);
1357 m->output_pcm = NULL;
1358 }
1359
1360 PA_IDXSET_FOREACH(m, p->input_mappings, idx) {
1361 if (p->supported)
1362 m->supported++;
1363
1364 if (!m->input_pcm)
1365 continue;
1366
1367 snd_pcm_close(m->input_pcm);
1368 m->input_pcm = NULL;
1369 }
1370 }
1371
1372 static void ucm_mapping_jack_probe(pa_alsa_mapping *m) {
1373 snd_pcm_t *pcm_handle;
1374 snd_mixer_t *mixer_handle;
1375 snd_hctl_t *hctl_handle;
1376 pa_alsa_ucm_mapping_context *context = &m->ucm_context;
1377 pa_alsa_ucm_device *dev;
1378 uint32_t idx;
1379
1380 pcm_handle = m->direction == PA_ALSA_DIRECTION_OUTPUT ? m->output_pcm : m->input_pcm;
1381 mixer_handle = pa_alsa_open_mixer_for_pcm(pcm_handle, NULL, &hctl_handle);
1382 if (!mixer_handle || !hctl_handle)
1383 return;
1384
1385 PA_IDXSET_FOREACH(dev, context->ucm_devices, idx) {
1386 pa_alsa_jack *jack;
1387 jack = m->direction == PA_ALSA_DIRECTION_OUTPUT ? dev->output_jack : dev->input_jack;
1388 pa_assert (jack);
1389 jack->has_control = pa_alsa_find_jack(hctl_handle, jack->alsa_name) != NULL;
1390 pa_log_info("UCM jack %s has_control=%d", jack->name, jack->has_control);
1391 }
1392
1393 snd_mixer_close(mixer_handle);
1394 }
1395
1396 static void ucm_probe_profile_set(pa_alsa_ucm_config *ucm, pa_alsa_profile_set *ps) {
1397 void *state;
1398 pa_alsa_profile *p;
1399 pa_alsa_mapping *m;
1400 uint32_t idx;
1401
1402 PA_HASHMAP_FOREACH(p, ps->profiles, state) {
1403 /* change verb */
1404 pa_log_info("Set ucm verb to %s", p->name);
1405
1406 if ((snd_use_case_set(ucm->ucm_mgr, "_verb", p->name)) < 0) {
1407 pa_log("Failed to set verb %s", p->name);
1408 p->supported = false;
1409 continue;
1410 }
1411
1412 PA_IDXSET_FOREACH(m, p->output_mappings, idx) {
1413 if (PA_UCM_IS_MODIFIER_MAPPING(m)) {
1414 /* Skip jack probing on modifier PCMs since we expect this to
1415 * only be controlled on the main device/verb PCM. */
1416 continue;
1417 }
1418
1419 m->output_pcm = mapping_open_pcm(ucm, m, SND_PCM_STREAM_PLAYBACK);
1420 if (!m->output_pcm) {
1421 p->supported = false;
1422 break;
1423 }
1424 }
1425
1426 if (p->supported) {
1427 PA_IDXSET_FOREACH(m, p->input_mappings, idx) {
1428 if (PA_UCM_IS_MODIFIER_MAPPING(m)) {
1429 /* Skip jack probing on modifier PCMs since we expect this to
1430 * only be controlled on the main device/verb PCM. */
1431 continue;
1432 }
1433
1434 m->input_pcm = mapping_open_pcm(ucm, m, SND_PCM_STREAM_CAPTURE);
1435 if (!m->input_pcm) {
1436 p->supported = false;
1437 break;
1438 }
1439 }
1440 }
1441
1442 if (!p->supported) {
1443 profile_finalize_probing(p);
1444 continue;
1445 }
1446
1447 pa_log_debug("Profile %s supported.", p->name);
1448
1449 PA_IDXSET_FOREACH(m, p->output_mappings, idx)
1450 if (!PA_UCM_IS_MODIFIER_MAPPING(m))
1451 ucm_mapping_jack_probe(m);
1452
1453 PA_IDXSET_FOREACH(m, p->input_mappings, idx)
1454 if (!PA_UCM_IS_MODIFIER_MAPPING(m))
1455 ucm_mapping_jack_probe(m);
1456
1457 profile_finalize_probing(p);
1458 }
1459
1460 /* restore ucm state */
1461 snd_use_case_set(ucm->ucm_mgr, "_verb", SND_USE_CASE_VERB_INACTIVE);
1462
1463 pa_alsa_profile_set_drop_unsupported(ps);
1464 }
1465
1466 pa_alsa_profile_set* pa_alsa_ucm_add_profile_set(pa_alsa_ucm_config *ucm, pa_channel_map *default_channel_map) {
1467 pa_alsa_ucm_verb *verb;
1468 pa_alsa_profile_set *ps;
1469
1470 ps = pa_xnew0(pa_alsa_profile_set, 1);
1471 ps->mappings = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
1472 ps->profiles = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
1473 ps->decibel_fixes = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
1474
1475 /* create a profile for each verb */
1476 PA_LLIST_FOREACH(verb, ucm->verbs) {
1477 const char *verb_name;
1478 const char *verb_desc;
1479
1480 verb_name = pa_proplist_gets(verb->proplist, PA_ALSA_PROP_UCM_NAME);
1481 verb_desc = pa_proplist_gets(verb->proplist, PA_ALSA_PROP_UCM_DESCRIPTION);
1482 if (verb_name == NULL) {
1483 pa_log("Verb with no name");
1484 continue;
1485 }
1486
1487 ucm_create_profile(ucm, ps, verb, verb_name, verb_desc);
1488 }
1489
1490 ucm_probe_profile_set(ucm, ps);
1491 ps->probed = true;
1492
1493 return ps;
1494 }
1495
1496 static void free_verb(pa_alsa_ucm_verb *verb) {
1497 pa_alsa_ucm_device *di, *dn;
1498 pa_alsa_ucm_modifier *mi, *mn;
1499
1500 PA_LLIST_FOREACH_SAFE(di, dn, verb->devices) {
1501 PA_LLIST_REMOVE(pa_alsa_ucm_device, verb->devices, di);
1502 pa_proplist_free(di->proplist);
1503 if (di->conflicting_devices)
1504 pa_idxset_free(di->conflicting_devices, NULL);
1505 if (di->supported_devices)
1506 pa_idxset_free(di->supported_devices, NULL);
1507 pa_xfree(di);
1508 }
1509
1510 PA_LLIST_FOREACH_SAFE(mi, mn, verb->modifiers) {
1511 PA_LLIST_REMOVE(pa_alsa_ucm_modifier, verb->modifiers, mi);
1512 pa_proplist_free(mi->proplist);
1513 if (mi->n_suppdev > 0)
1514 snd_use_case_free_list(mi->supported_devices, mi->n_suppdev);
1515 if (mi->n_confdev > 0)
1516 snd_use_case_free_list(mi->conflicting_devices, mi->n_confdev);
1517 pa_xfree(mi->media_role);
1518 pa_xfree(mi);
1519 }
1520 pa_proplist_free(verb->proplist);
1521 pa_xfree(verb);
1522 }
1523
1524 void pa_alsa_ucm_free(pa_alsa_ucm_config *ucm) {
1525 pa_alsa_ucm_verb *vi, *vn;
1526 pa_alsa_jack *ji, *jn;
1527
1528 PA_LLIST_FOREACH_SAFE(vi, vn, ucm->verbs) {
1529 PA_LLIST_REMOVE(pa_alsa_ucm_verb, ucm->verbs, vi);
1530 free_verb(vi);
1531 }
1532 PA_LLIST_FOREACH_SAFE(ji, jn, ucm->jacks) {
1533 PA_LLIST_REMOVE(pa_alsa_jack, ucm->jacks, ji);
1534 pa_xfree(ji->alsa_name);
1535 pa_xfree(ji->name);
1536 pa_xfree(ji);
1537 }
1538 if (ucm->ucm_mgr) {
1539 snd_use_case_mgr_close(ucm->ucm_mgr);
1540 ucm->ucm_mgr = NULL;
1541 }
1542 }
1543
1544 void pa_alsa_ucm_mapping_context_free(pa_alsa_ucm_mapping_context *context) {
1545 pa_alsa_ucm_device *dev;
1546 pa_alsa_ucm_modifier *mod;
1547 uint32_t idx;
1548
1549 if (context->ucm_devices) {
1550 /* clear ucm device pointer to mapping */
1551 PA_IDXSET_FOREACH(dev, context->ucm_devices, idx) {
1552 if (context->direction == PA_DIRECTION_OUTPUT)
1553 dev->playback_mapping = NULL;
1554 else
1555 dev->capture_mapping = NULL;
1556 }
1557
1558 pa_idxset_free(context->ucm_devices, NULL);
1559 }
1560
1561 if (context->ucm_modifiers) {
1562 PA_IDXSET_FOREACH(mod, context->ucm_modifiers, idx) {
1563 if (context->direction == PA_DIRECTION_OUTPUT)
1564 mod->playback_mapping = NULL;
1565 else
1566 mod->capture_mapping = NULL;
1567 }
1568
1569 pa_idxset_free(context->ucm_modifiers, NULL);
1570 }
1571 }
1572
1573 /* Enable the modifier when the first stream with matched role starts */
1574 void pa_alsa_ucm_roled_stream_begin(pa_alsa_ucm_config *ucm, const char *role, pa_direction_t dir) {
1575 pa_alsa_ucm_modifier *mod;
1576
1577 if (!ucm->active_verb)
1578 return;
1579
1580 PA_LLIST_FOREACH(mod, ucm->active_verb->modifiers) {
1581 if ((mod->action_direction == dir) && (pa_streq(mod->media_role, role))) {
1582 if (mod->enabled_counter == 0) {
1583 const char *mod_name = pa_proplist_gets(mod->proplist, PA_ALSA_PROP_UCM_NAME);
1584
1585 pa_log_info("Enable ucm modifier %s", mod_name);
1586 if (snd_use_case_set(ucm->ucm_mgr, "_enamod", mod_name) < 0) {
1587 pa_log("Failed to enable ucm modifier %s", mod_name);
1588 }
1589 }
1590
1591 mod->enabled_counter++;
1592 break;
1593 }
1594 }
1595 }
1596
1597 /* Disable the modifier when the last stream with matched role ends */
1598 void pa_alsa_ucm_roled_stream_end(pa_alsa_ucm_config *ucm, const char *role, pa_direction_t dir) {
1599 pa_alsa_ucm_modifier *mod;
1600
1601 if (!ucm->active_verb)
1602 return;
1603
1604 PA_LLIST_FOREACH(mod, ucm->active_verb->modifiers) {
1605 if ((mod->action_direction == dir) && (pa_streq(mod->media_role, role))) {
1606
1607 mod->enabled_counter--;
1608 if (mod->enabled_counter == 0) {
1609 const char *mod_name = pa_proplist_gets(mod->proplist, PA_ALSA_PROP_UCM_NAME);
1610
1611 pa_log_info("Disable ucm modifier %s", mod_name);
1612 if (snd_use_case_set(ucm->ucm_mgr, "_dismod", mod_name) < 0) {
1613 pa_log("Failed to disable ucm modifier %s", mod_name);
1614 }
1615 }
1616
1617 break;
1618 }
1619 }
1620 }