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