]> code.delx.au - pulseaudio/blob - src/modules/alsa/alsa-ucm.c
c88fc776aa19b2a58b21bca7071477517974342e
[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 && pa_channels_valid(ui))
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 && pa_channels_valid(ui))
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 && pa_sample_rate_valid(ui)) {
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 && pa_sample_rate_valid(ui)) {
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 /* FIXME: channel_str is unsanitized input from the UCM configuration,
1129 * we should do proper error handling instead of asserting.
1130 * https://bugs.freedesktop.org/show_bug.cgi?id=71823 */
1131 pa_assert_se(pa_atou(channel_str, &channels) == 0 && pa_channels_valid(channels));
1132 pa_log_debug("Got channel count %" PRIu32 " for modifier", channels);
1133 }
1134
1135 if (channels)
1136 pa_channel_map_init_extend(&m->channel_map, channels, PA_CHANNEL_MAP_ALSA);
1137 else
1138 pa_channel_map_init(&m->channel_map);
1139 }
1140
1141 static int ucm_create_mapping_direction(
1142 pa_alsa_ucm_config *ucm,
1143 pa_alsa_profile_set *ps,
1144 pa_alsa_profile *p,
1145 pa_alsa_ucm_device *device,
1146 const char *verb_name,
1147 const char *device_name,
1148 const char *device_str,
1149 bool is_sink) {
1150
1151 pa_alsa_mapping *m;
1152 char *mapping_name;
1153 unsigned priority, rate, channels;
1154
1155 mapping_name = pa_sprintf_malloc("Mapping %s: %s: %s", verb_name, device_str, is_sink ? "sink" : "source");
1156
1157 m = pa_alsa_mapping_get(ps, mapping_name);
1158 if (!m) {
1159 pa_log("No mapping for %s", mapping_name);
1160 pa_xfree(mapping_name);
1161 return -1;
1162 }
1163 pa_log_debug("UCM mapping: %s dev %s", mapping_name, device_name);
1164 pa_xfree(mapping_name);
1165
1166 priority = is_sink ? device->playback_priority : device->capture_priority;
1167 rate = is_sink ? device->playback_rate : device->capture_rate;
1168 channels = is_sink ? device->playback_channels : device->capture_channels;
1169
1170 if (!m->ucm_context.ucm_devices) { /* new mapping */
1171 m->ucm_context.ucm_devices = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
1172 m->ucm_context.ucm = ucm;
1173 m->ucm_context.direction = is_sink ? PA_DIRECTION_OUTPUT : PA_DIRECTION_INPUT;
1174
1175 m->device_strings = pa_xnew0(char*, 2);
1176 m->device_strings[0] = pa_xstrdup(device_str);
1177 m->direction = is_sink ? PA_ALSA_DIRECTION_OUTPUT : PA_ALSA_DIRECTION_INPUT;
1178
1179 ucm_add_mapping(p, m);
1180 if (rate)
1181 m->sample_spec.rate = rate;
1182 pa_channel_map_init_extend(&m->channel_map, channels, PA_CHANNEL_MAP_ALSA);
1183 }
1184
1185 /* mapping priority is the highest one of ucm devices */
1186 if (priority > m->priority)
1187 m->priority = priority;
1188
1189 /* mapping channels is the lowest one of ucm devices */
1190 if (channels < m->channel_map.channels)
1191 pa_channel_map_init_extend(&m->channel_map, channels, PA_CHANNEL_MAP_ALSA);
1192
1193 alsa_mapping_add_ucm_device(m, device);
1194
1195 return 0;
1196 }
1197
1198 static int ucm_create_mapping_for_modifier(
1199 pa_alsa_ucm_config *ucm,
1200 pa_alsa_profile_set *ps,
1201 pa_alsa_profile *p,
1202 pa_alsa_ucm_modifier *modifier,
1203 const char *verb_name,
1204 const char *mod_name,
1205 const char *device_str,
1206 bool is_sink) {
1207
1208 pa_alsa_mapping *m;
1209 char *mapping_name;
1210
1211 mapping_name = pa_sprintf_malloc("Mapping %s: %s: %s", verb_name, device_str, is_sink ? "sink" : "source");
1212
1213 m = pa_alsa_mapping_get(ps, mapping_name);
1214 if (!m) {
1215 pa_log("no mapping for %s", mapping_name);
1216 pa_xfree(mapping_name);
1217 return -1;
1218 }
1219 pa_log_info("ucm mapping: %s modifier %s", mapping_name, mod_name);
1220 pa_xfree(mapping_name);
1221
1222 if (!m->ucm_context.ucm_devices && !m->ucm_context.ucm_modifiers) { /* new mapping */
1223 m->ucm_context.ucm_devices = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
1224 m->ucm_context.ucm_modifiers = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
1225 m->ucm_context.ucm = ucm;
1226 m->ucm_context.direction = is_sink ? PA_DIRECTION_OUTPUT : PA_DIRECTION_INPUT;
1227
1228 m->device_strings = pa_xnew0(char*, 2);
1229 m->device_strings[0] = pa_xstrdup(device_str);
1230 m->direction = is_sink ? PA_ALSA_DIRECTION_OUTPUT : PA_ALSA_DIRECTION_INPUT;
1231 /* Modifier sinks should not be routed to by default */
1232 m->priority = 0;
1233
1234 ucm_add_mapping(p, m);
1235 } else if (!m->ucm_context.ucm_modifiers) /* share pcm with device */
1236 m->ucm_context.ucm_modifiers = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
1237
1238 alsa_mapping_add_ucm_modifier(m, modifier);
1239
1240 return 0;
1241 }
1242
1243 static int ucm_create_mapping(
1244 pa_alsa_ucm_config *ucm,
1245 pa_alsa_profile_set *ps,
1246 pa_alsa_profile *p,
1247 pa_alsa_ucm_device *device,
1248 const char *verb_name,
1249 const char *device_name,
1250 const char *sink,
1251 const char *source) {
1252
1253 int ret = 0;
1254
1255 if (!sink && !source) {
1256 pa_log("No sink and source at %s: %s", verb_name, device_name);
1257 return -1;
1258 }
1259
1260 if (sink)
1261 ret = ucm_create_mapping_direction(ucm, ps, p, device, verb_name, device_name, sink, true);
1262 if (ret == 0 && source)
1263 ret = ucm_create_mapping_direction(ucm, ps, p, device, verb_name, device_name, source, false);
1264
1265 return ret;
1266 }
1267
1268 static pa_alsa_jack* ucm_get_jack(pa_alsa_ucm_config *ucm, const char *dev_name, const char *pre_tag) {
1269 pa_alsa_jack *j;
1270 char *name = pa_sprintf_malloc("%s%s", pre_tag, dev_name);
1271
1272 PA_LLIST_FOREACH(j, ucm->jacks)
1273 if (pa_streq(j->name, name))
1274 goto out;
1275
1276 j = pa_xnew0(pa_alsa_jack, 1);
1277 j->state_unplugged = PA_AVAILABLE_NO;
1278 j->state_plugged = PA_AVAILABLE_YES;
1279 j->name = pa_xstrdup(name);
1280 j->alsa_name = pa_sprintf_malloc("%s Jack", dev_name);
1281
1282 PA_LLIST_PREPEND(pa_alsa_jack, ucm->jacks, j);
1283
1284 out:
1285 pa_xfree(name);
1286 return j;
1287 }
1288
1289 static int ucm_create_profile(
1290 pa_alsa_ucm_config *ucm,
1291 pa_alsa_profile_set *ps,
1292 pa_alsa_ucm_verb *verb,
1293 const char *verb_name,
1294 const char *verb_desc) {
1295
1296 pa_alsa_profile *p;
1297 pa_alsa_ucm_device *dev;
1298 pa_alsa_ucm_modifier *mod;
1299 int i = 0;
1300 const char *name, *sink, *source;
1301 char *verb_cmp, *c;
1302
1303 pa_assert(ps);
1304
1305 if (pa_hashmap_get(ps->profiles, verb_name)) {
1306 pa_log("Verb %s already exists", verb_name);
1307 return -1;
1308 }
1309
1310 p = pa_xnew0(pa_alsa_profile, 1);
1311 p->profile_set = ps;
1312 p->name = pa_xstrdup(verb_name);
1313 p->description = pa_xstrdup(verb_desc);
1314
1315 p->output_mappings = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
1316 p->input_mappings = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
1317
1318 p->supported = true;
1319 pa_hashmap_put(ps->profiles, p->name, p);
1320
1321 /* TODO: get profile priority from ucm info or policy management */
1322 c = verb_cmp = pa_xstrdup(verb_name);
1323 while (*c) {
1324 if (*c == '_') *c = ' ';
1325 c++;
1326 }
1327
1328 for (i = 0; verb_info[i].id; i++) {
1329 if (strcasecmp(verb_info[i].id, verb_cmp) == 0) {
1330 p->priority = verb_info[i].priority;
1331 break;
1332 }
1333 }
1334
1335 pa_xfree(verb_cmp);
1336
1337 if (verb_info[i].id == NULL)
1338 p->priority = 1000;
1339
1340 PA_LLIST_FOREACH(dev, verb->devices) {
1341 name = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_NAME);
1342
1343 sink = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_SINK);
1344 source = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_SOURCE);
1345
1346 ucm_create_mapping(ucm, ps, p, dev, verb_name, name, sink, source);
1347
1348 if (sink)
1349 dev->output_jack = ucm_get_jack(ucm, name, PA_UCM_PRE_TAG_OUTPUT);
1350 if (source)
1351 dev->input_jack = ucm_get_jack(ucm, name, PA_UCM_PRE_TAG_INPUT);
1352 }
1353
1354 /* Now find modifiers that have their own PlaybackPCM and create
1355 * separate sinks for them. */
1356 PA_LLIST_FOREACH(mod, verb->modifiers) {
1357 name = pa_proplist_gets(mod->proplist, PA_ALSA_PROP_UCM_NAME);
1358
1359 sink = pa_proplist_gets(mod->proplist, PA_ALSA_PROP_UCM_SINK);
1360 source = pa_proplist_gets(mod->proplist, PA_ALSA_PROP_UCM_SOURCE);
1361
1362 if (sink)
1363 ucm_create_mapping_for_modifier(ucm, ps, p, mod, verb_name, name, sink, true);
1364 else if (source)
1365 ucm_create_mapping_for_modifier(ucm, ps, p, mod, verb_name, name, source, false);
1366 }
1367
1368 pa_alsa_profile_dump(p);
1369
1370 return 0;
1371 }
1372
1373 static snd_pcm_t* mapping_open_pcm(pa_alsa_ucm_config *ucm, pa_alsa_mapping *m, int mode) {
1374 snd_pcm_t* pcm;
1375 pa_sample_spec try_ss = ucm->core->default_sample_spec;
1376 pa_channel_map try_map;
1377 snd_pcm_uframes_t try_period_size, try_buffer_size;
1378 bool exact_channels = m->channel_map.channels > 0;
1379
1380 if (exact_channels) {
1381 try_map = m->channel_map;
1382 try_ss.channels = try_map.channels;
1383 } else
1384 pa_channel_map_init_extend(&try_map, try_ss.channels, PA_CHANNEL_MAP_ALSA);
1385
1386 try_period_size =
1387 pa_usec_to_bytes(ucm->core->default_fragment_size_msec * PA_USEC_PER_MSEC, &try_ss) /
1388 pa_frame_size(&try_ss);
1389 try_buffer_size = ucm->core->default_n_fragments * try_period_size;
1390
1391 pcm = pa_alsa_open_by_device_string(m->device_strings[0], NULL, &try_ss,
1392 &try_map, mode, &try_period_size, &try_buffer_size, 0, NULL, NULL, exact_channels);
1393
1394 if (pcm && !exact_channels)
1395 m->channel_map = try_map;
1396
1397 return pcm;
1398 }
1399
1400 static void profile_finalize_probing(pa_alsa_profile *p) {
1401 pa_alsa_mapping *m;
1402 uint32_t idx;
1403
1404 PA_IDXSET_FOREACH(m, p->output_mappings, idx) {
1405 if (p->supported)
1406 m->supported++;
1407
1408 if (!m->output_pcm)
1409 continue;
1410
1411 snd_pcm_close(m->output_pcm);
1412 m->output_pcm = NULL;
1413 }
1414
1415 PA_IDXSET_FOREACH(m, p->input_mappings, idx) {
1416 if (p->supported)
1417 m->supported++;
1418
1419 if (!m->input_pcm)
1420 continue;
1421
1422 snd_pcm_close(m->input_pcm);
1423 m->input_pcm = NULL;
1424 }
1425 }
1426
1427 static void ucm_mapping_jack_probe(pa_alsa_mapping *m) {
1428 snd_pcm_t *pcm_handle;
1429 snd_mixer_t *mixer_handle;
1430 snd_hctl_t *hctl_handle;
1431 pa_alsa_ucm_mapping_context *context = &m->ucm_context;
1432 pa_alsa_ucm_device *dev;
1433 uint32_t idx;
1434
1435 pcm_handle = m->direction == PA_ALSA_DIRECTION_OUTPUT ? m->output_pcm : m->input_pcm;
1436 mixer_handle = pa_alsa_open_mixer_for_pcm(pcm_handle, NULL, &hctl_handle);
1437 if (!mixer_handle || !hctl_handle)
1438 return;
1439
1440 PA_IDXSET_FOREACH(dev, context->ucm_devices, idx) {
1441 pa_alsa_jack *jack;
1442 jack = m->direction == PA_ALSA_DIRECTION_OUTPUT ? dev->output_jack : dev->input_jack;
1443 pa_assert (jack);
1444 jack->has_control = pa_alsa_find_jack(hctl_handle, jack->alsa_name) != NULL;
1445 pa_log_info("UCM jack %s has_control=%d", jack->name, jack->has_control);
1446 }
1447
1448 snd_mixer_close(mixer_handle);
1449 }
1450
1451 static void ucm_probe_profile_set(pa_alsa_ucm_config *ucm, pa_alsa_profile_set *ps) {
1452 void *state;
1453 pa_alsa_profile *p;
1454 pa_alsa_mapping *m;
1455 uint32_t idx;
1456
1457 PA_HASHMAP_FOREACH(p, ps->profiles, state) {
1458 /* change verb */
1459 pa_log_info("Set ucm verb to %s", p->name);
1460
1461 if ((snd_use_case_set(ucm->ucm_mgr, "_verb", p->name)) < 0) {
1462 pa_log("Failed to set verb %s", p->name);
1463 p->supported = false;
1464 continue;
1465 }
1466
1467 PA_IDXSET_FOREACH(m, p->output_mappings, idx) {
1468 if (PA_UCM_IS_MODIFIER_MAPPING(m)) {
1469 /* Skip jack probing on modifier PCMs since we expect this to
1470 * only be controlled on the main device/verb PCM. */
1471 continue;
1472 }
1473
1474 m->output_pcm = mapping_open_pcm(ucm, m, SND_PCM_STREAM_PLAYBACK);
1475 if (!m->output_pcm) {
1476 p->supported = false;
1477 break;
1478 }
1479 }
1480
1481 if (p->supported) {
1482 PA_IDXSET_FOREACH(m, p->input_mappings, idx) {
1483 if (PA_UCM_IS_MODIFIER_MAPPING(m)) {
1484 /* Skip jack probing on modifier PCMs since we expect this to
1485 * only be controlled on the main device/verb PCM. */
1486 continue;
1487 }
1488
1489 m->input_pcm = mapping_open_pcm(ucm, m, SND_PCM_STREAM_CAPTURE);
1490 if (!m->input_pcm) {
1491 p->supported = false;
1492 break;
1493 }
1494 }
1495 }
1496
1497 if (!p->supported) {
1498 profile_finalize_probing(p);
1499 continue;
1500 }
1501
1502 pa_log_debug("Profile %s supported.", p->name);
1503
1504 PA_IDXSET_FOREACH(m, p->output_mappings, idx)
1505 if (!PA_UCM_IS_MODIFIER_MAPPING(m))
1506 ucm_mapping_jack_probe(m);
1507
1508 PA_IDXSET_FOREACH(m, p->input_mappings, idx)
1509 if (!PA_UCM_IS_MODIFIER_MAPPING(m))
1510 ucm_mapping_jack_probe(m);
1511
1512 profile_finalize_probing(p);
1513 }
1514
1515 /* restore ucm state */
1516 snd_use_case_set(ucm->ucm_mgr, "_verb", SND_USE_CASE_VERB_INACTIVE);
1517
1518 pa_alsa_profile_set_drop_unsupported(ps);
1519 }
1520
1521 pa_alsa_profile_set* pa_alsa_ucm_add_profile_set(pa_alsa_ucm_config *ucm, pa_channel_map *default_channel_map) {
1522 pa_alsa_ucm_verb *verb;
1523 pa_alsa_profile_set *ps;
1524
1525 ps = pa_xnew0(pa_alsa_profile_set, 1);
1526 ps->mappings = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
1527 ps->profiles = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
1528 ps->decibel_fixes = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
1529
1530 /* create a profile for each verb */
1531 PA_LLIST_FOREACH(verb, ucm->verbs) {
1532 const char *verb_name;
1533 const char *verb_desc;
1534
1535 verb_name = pa_proplist_gets(verb->proplist, PA_ALSA_PROP_UCM_NAME);
1536 verb_desc = pa_proplist_gets(verb->proplist, PA_ALSA_PROP_UCM_DESCRIPTION);
1537 if (verb_name == NULL) {
1538 pa_log("Verb with no name");
1539 continue;
1540 }
1541
1542 ucm_create_profile(ucm, ps, verb, verb_name, verb_desc);
1543 }
1544
1545 ucm_probe_profile_set(ucm, ps);
1546 ps->probed = true;
1547
1548 return ps;
1549 }
1550
1551 static void free_verb(pa_alsa_ucm_verb *verb) {
1552 pa_alsa_ucm_device *di, *dn;
1553 pa_alsa_ucm_modifier *mi, *mn;
1554
1555 PA_LLIST_FOREACH_SAFE(di, dn, verb->devices) {
1556 PA_LLIST_REMOVE(pa_alsa_ucm_device, verb->devices, di);
1557 pa_proplist_free(di->proplist);
1558 if (di->conflicting_devices)
1559 pa_idxset_free(di->conflicting_devices, NULL);
1560 if (di->supported_devices)
1561 pa_idxset_free(di->supported_devices, NULL);
1562 pa_xfree(di);
1563 }
1564
1565 PA_LLIST_FOREACH_SAFE(mi, mn, verb->modifiers) {
1566 PA_LLIST_REMOVE(pa_alsa_ucm_modifier, verb->modifiers, mi);
1567 pa_proplist_free(mi->proplist);
1568 if (mi->n_suppdev > 0)
1569 snd_use_case_free_list(mi->supported_devices, mi->n_suppdev);
1570 if (mi->n_confdev > 0)
1571 snd_use_case_free_list(mi->conflicting_devices, mi->n_confdev);
1572 pa_xfree(mi->media_role);
1573 pa_xfree(mi);
1574 }
1575 pa_proplist_free(verb->proplist);
1576 pa_xfree(verb);
1577 }
1578
1579 void pa_alsa_ucm_free(pa_alsa_ucm_config *ucm) {
1580 pa_alsa_ucm_verb *vi, *vn;
1581 pa_alsa_jack *ji, *jn;
1582
1583 PA_LLIST_FOREACH_SAFE(vi, vn, ucm->verbs) {
1584 PA_LLIST_REMOVE(pa_alsa_ucm_verb, ucm->verbs, vi);
1585 free_verb(vi);
1586 }
1587 PA_LLIST_FOREACH_SAFE(ji, jn, ucm->jacks) {
1588 PA_LLIST_REMOVE(pa_alsa_jack, ucm->jacks, ji);
1589 pa_xfree(ji->alsa_name);
1590 pa_xfree(ji->name);
1591 pa_xfree(ji);
1592 }
1593 if (ucm->ucm_mgr) {
1594 snd_use_case_mgr_close(ucm->ucm_mgr);
1595 ucm->ucm_mgr = NULL;
1596 }
1597 }
1598
1599 void pa_alsa_ucm_mapping_context_free(pa_alsa_ucm_mapping_context *context) {
1600 pa_alsa_ucm_device *dev;
1601 pa_alsa_ucm_modifier *mod;
1602 uint32_t idx;
1603
1604 if (context->ucm_devices) {
1605 /* clear ucm device pointer to mapping */
1606 PA_IDXSET_FOREACH(dev, context->ucm_devices, idx) {
1607 if (context->direction == PA_DIRECTION_OUTPUT)
1608 dev->playback_mapping = NULL;
1609 else
1610 dev->capture_mapping = NULL;
1611 }
1612
1613 pa_idxset_free(context->ucm_devices, NULL);
1614 }
1615
1616 if (context->ucm_modifiers) {
1617 PA_IDXSET_FOREACH(mod, context->ucm_modifiers, idx) {
1618 if (context->direction == PA_DIRECTION_OUTPUT)
1619 mod->playback_mapping = NULL;
1620 else
1621 mod->capture_mapping = NULL;
1622 }
1623
1624 pa_idxset_free(context->ucm_modifiers, NULL);
1625 }
1626 }
1627
1628 /* Enable the modifier when the first stream with matched role starts */
1629 void pa_alsa_ucm_roled_stream_begin(pa_alsa_ucm_config *ucm, const char *role, pa_direction_t dir) {
1630 pa_alsa_ucm_modifier *mod;
1631
1632 if (!ucm->active_verb)
1633 return;
1634
1635 PA_LLIST_FOREACH(mod, ucm->active_verb->modifiers) {
1636 if ((mod->action_direction == dir) && (pa_streq(mod->media_role, role))) {
1637 if (mod->enabled_counter == 0) {
1638 const char *mod_name = pa_proplist_gets(mod->proplist, PA_ALSA_PROP_UCM_NAME);
1639
1640 pa_log_info("Enable ucm modifier %s", mod_name);
1641 if (snd_use_case_set(ucm->ucm_mgr, "_enamod", mod_name) < 0) {
1642 pa_log("Failed to enable ucm modifier %s", mod_name);
1643 }
1644 }
1645
1646 mod->enabled_counter++;
1647 break;
1648 }
1649 }
1650 }
1651
1652 /* Disable the modifier when the last stream with matched role ends */
1653 void pa_alsa_ucm_roled_stream_end(pa_alsa_ucm_config *ucm, const char *role, pa_direction_t dir) {
1654 pa_alsa_ucm_modifier *mod;
1655
1656 if (!ucm->active_verb)
1657 return;
1658
1659 PA_LLIST_FOREACH(mod, ucm->active_verb->modifiers) {
1660 if ((mod->action_direction == dir) && (pa_streq(mod->media_role, role))) {
1661
1662 mod->enabled_counter--;
1663 if (mod->enabled_counter == 0) {
1664 const char *mod_name = pa_proplist_gets(mod->proplist, PA_ALSA_PROP_UCM_NAME);
1665
1666 pa_log_info("Disable ucm modifier %s", mod_name);
1667 if (snd_use_case_set(ucm->ucm_mgr, "_dismod", mod_name) < 0) {
1668 pa_log("Failed to disable ucm modifier %s", mod_name);
1669 }
1670 }
1671
1672 break;
1673 }
1674 }
1675 }
1676
1677 #else /* HAVE_ALSA_UCM */
1678
1679 /* Dummy functions for systems without UCM support */
1680
1681 int pa_alsa_ucm_query_profiles(pa_alsa_ucm_config *ucm, int card_index) {
1682 pa_log_info("UCM not available.");
1683 return -1;
1684 }
1685
1686 pa_alsa_profile_set* pa_alsa_ucm_add_profile_set(pa_alsa_ucm_config *ucm, pa_channel_map *default_channel_map) {
1687 return NULL;
1688 }
1689
1690 int pa_alsa_ucm_set_profile(pa_alsa_ucm_config *ucm, const char *new_profile, const char *old_profile) {
1691 return -1;
1692 }
1693
1694 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) {
1695 return -1;
1696 }
1697
1698 void pa_alsa_ucm_add_ports(
1699 pa_hashmap **hash,
1700 pa_proplist *proplist,
1701 pa_alsa_ucm_mapping_context *context,
1702 bool is_sink,
1703 pa_card *card) {
1704 }
1705
1706 void pa_alsa_ucm_add_ports_combination(
1707 pa_hashmap *hash,
1708 pa_alsa_ucm_mapping_context *context,
1709 bool is_sink,
1710 pa_hashmap *ports,
1711 pa_card_profile *cp,
1712 pa_core *core) {
1713 }
1714
1715 int pa_alsa_ucm_set_port(pa_alsa_ucm_mapping_context *context, pa_device_port *port, bool is_sink) {
1716 return -1;
1717 }
1718
1719 void pa_alsa_ucm_free(pa_alsa_ucm_config *ucm) {
1720 }
1721
1722 void pa_alsa_ucm_mapping_context_free(pa_alsa_ucm_mapping_context *context) {
1723 }
1724
1725 void pa_alsa_ucm_roled_stream_begin(pa_alsa_ucm_config *ucm, const char *role, pa_direction_t dir) {
1726 }
1727
1728 void pa_alsa_ucm_roled_stream_end(pa_alsa_ucm_config *ucm, const char *role, pa_direction_t dir) {
1729 }
1730
1731 #endif