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