153a5a1b3Sopenharmony_ci/*** 253a5a1b3Sopenharmony_ci This file is part of PulseAudio. 353a5a1b3Sopenharmony_ci 453a5a1b3Sopenharmony_ci Copyright 2009 Lennart Poettering 553a5a1b3Sopenharmony_ci 653a5a1b3Sopenharmony_ci PulseAudio is free software; you can redistribute it and/or modify 753a5a1b3Sopenharmony_ci it under the terms of the GNU Lesser General Public License as published 853a5a1b3Sopenharmony_ci by the Free Software Foundation; either version 2.1 of the License, 953a5a1b3Sopenharmony_ci or (at your option) any later version. 1053a5a1b3Sopenharmony_ci 1153a5a1b3Sopenharmony_ci PulseAudio is distributed in the hope that it will be useful, but 1253a5a1b3Sopenharmony_ci WITHOUT ANY WARRANTY; without even the implied warranty of 1353a5a1b3Sopenharmony_ci MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 1453a5a1b3Sopenharmony_ci General Public License for more details. 1553a5a1b3Sopenharmony_ci 1653a5a1b3Sopenharmony_ci You should have received a copy of the GNU Lesser General Public License 1753a5a1b3Sopenharmony_ci along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. 1853a5a1b3Sopenharmony_ci***/ 1953a5a1b3Sopenharmony_ci 2053a5a1b3Sopenharmony_ci#ifdef HAVE_CONFIG_H 2153a5a1b3Sopenharmony_ci#include <config.h> 2253a5a1b3Sopenharmony_ci#endif 2353a5a1b3Sopenharmony_ci 2453a5a1b3Sopenharmony_ci#include <pulse/xmalloc.h> 2553a5a1b3Sopenharmony_ci 2653a5a1b3Sopenharmony_ci#include <pulsecore/core-util.h> 2753a5a1b3Sopenharmony_ci#include <pulsecore/i18n.h> 2853a5a1b3Sopenharmony_ci#include <pulsecore/modargs.h> 2953a5a1b3Sopenharmony_ci#include <pulsecore/queue.h> 3053a5a1b3Sopenharmony_ci 3153a5a1b3Sopenharmony_ci#include <modules/reserve-wrap.h> 3253a5a1b3Sopenharmony_ci 3353a5a1b3Sopenharmony_ci#ifdef HAVE_UDEV 3453a5a1b3Sopenharmony_ci#include <modules/udev-util.h> 3553a5a1b3Sopenharmony_ci#endif 3653a5a1b3Sopenharmony_ci 3753a5a1b3Sopenharmony_ci#include "alsa-util.h" 3853a5a1b3Sopenharmony_ci#include "alsa-ucm.h" 3953a5a1b3Sopenharmony_ci#include "alsa-sink.h" 4053a5a1b3Sopenharmony_ci#include "alsa-source.h" 4153a5a1b3Sopenharmony_ci 4253a5a1b3Sopenharmony_ciPA_MODULE_AUTHOR("Lennart Poettering"); 4353a5a1b3Sopenharmony_ciPA_MODULE_DESCRIPTION("ALSA Card"); 4453a5a1b3Sopenharmony_ciPA_MODULE_VERSION(PACKAGE_VERSION); 4553a5a1b3Sopenharmony_ciPA_MODULE_LOAD_ONCE(false); 4653a5a1b3Sopenharmony_ciPA_MODULE_USAGE( 4753a5a1b3Sopenharmony_ci "name=<name for the card/sink/source, to be prefixed> " 4853a5a1b3Sopenharmony_ci "card_name=<name for the card> " 4953a5a1b3Sopenharmony_ci "card_properties=<properties for the card> " 5053a5a1b3Sopenharmony_ci "sink_name=<name for the sink> " 5153a5a1b3Sopenharmony_ci "sink_properties=<properties for the sink> " 5253a5a1b3Sopenharmony_ci "source_name=<name for the source> " 5353a5a1b3Sopenharmony_ci "source_properties=<properties for the source> " 5453a5a1b3Sopenharmony_ci "namereg_fail=<when false attempt to synthesise new names if they are already taken> " 5553a5a1b3Sopenharmony_ci "device_id=<ALSA card index> " 5653a5a1b3Sopenharmony_ci "format=<sample format> " 5753a5a1b3Sopenharmony_ci "rate=<sample rate> " 5853a5a1b3Sopenharmony_ci "fragments=<number of fragments> " 5953a5a1b3Sopenharmony_ci "fragment_size=<fragment size> " 6053a5a1b3Sopenharmony_ci "mmap=<enable memory mapping?> " 6153a5a1b3Sopenharmony_ci "tsched=<enable system timer based scheduling mode?> " 6253a5a1b3Sopenharmony_ci "tsched_buffer_size=<buffer size when using timer based scheduling> " 6353a5a1b3Sopenharmony_ci "tsched_buffer_watermark=<lower fill watermark> " 6453a5a1b3Sopenharmony_ci "profile=<profile name> " 6553a5a1b3Sopenharmony_ci "fixed_latency_range=<disable latency range changes on underrun?> " 6653a5a1b3Sopenharmony_ci "ignore_dB=<ignore dB information from the device?> " 6753a5a1b3Sopenharmony_ci "deferred_volume=<Synchronize software and hardware volume changes to avoid momentary jumps?> " 6853a5a1b3Sopenharmony_ci "profile_set=<profile set configuration file> " 6953a5a1b3Sopenharmony_ci "paths_dir=<directory containing the path configuration files> " 7053a5a1b3Sopenharmony_ci "use_ucm=<load use case manager> " 7153a5a1b3Sopenharmony_ci "avoid_resampling=<use stream original sample rate if possible?> " 7253a5a1b3Sopenharmony_ci "control=<name of mixer control> " 7353a5a1b3Sopenharmony_ci); 7453a5a1b3Sopenharmony_ci 7553a5a1b3Sopenharmony_cistatic const char* const valid_modargs[] = { 7653a5a1b3Sopenharmony_ci "name", 7753a5a1b3Sopenharmony_ci "card_name", 7853a5a1b3Sopenharmony_ci "card_properties", 7953a5a1b3Sopenharmony_ci "sink_name", 8053a5a1b3Sopenharmony_ci "sink_properties", 8153a5a1b3Sopenharmony_ci "source_name", 8253a5a1b3Sopenharmony_ci "source_properties", 8353a5a1b3Sopenharmony_ci "namereg_fail", 8453a5a1b3Sopenharmony_ci "device_id", 8553a5a1b3Sopenharmony_ci "format", 8653a5a1b3Sopenharmony_ci "rate", 8753a5a1b3Sopenharmony_ci "fragments", 8853a5a1b3Sopenharmony_ci "fragment_size", 8953a5a1b3Sopenharmony_ci "mmap", 9053a5a1b3Sopenharmony_ci "tsched", 9153a5a1b3Sopenharmony_ci "tsched_buffer_size", 9253a5a1b3Sopenharmony_ci "tsched_buffer_watermark", 9353a5a1b3Sopenharmony_ci "fixed_latency_range", 9453a5a1b3Sopenharmony_ci "profile", 9553a5a1b3Sopenharmony_ci "ignore_dB", 9653a5a1b3Sopenharmony_ci "deferred_volume", 9753a5a1b3Sopenharmony_ci "profile_set", 9853a5a1b3Sopenharmony_ci "paths_dir", 9953a5a1b3Sopenharmony_ci "use_ucm", 10053a5a1b3Sopenharmony_ci "avoid_resampling", 10153a5a1b3Sopenharmony_ci "control", 10253a5a1b3Sopenharmony_ci NULL 10353a5a1b3Sopenharmony_ci}; 10453a5a1b3Sopenharmony_ci 10553a5a1b3Sopenharmony_ci#define DEFAULT_DEVICE_ID "0" 10653a5a1b3Sopenharmony_ci 10753a5a1b3Sopenharmony_ci#define PULSE_MODARGS "PULSE_MODARGS" 10853a5a1b3Sopenharmony_ci 10953a5a1b3Sopenharmony_ci/* dynamic profile priority bonus, for all alsa profiles, the original priority 11053a5a1b3Sopenharmony_ci needs to be less than 0x7fff (32767), then could apply the rule of priority 11153a5a1b3Sopenharmony_ci bonus. So far there are 2 kinds of alsa profiles, one is from alsa ucm, the 11253a5a1b3Sopenharmony_ci other is from mixer profile-sets, their priorities are all far less than 0x7fff 11353a5a1b3Sopenharmony_ci*/ 11453a5a1b3Sopenharmony_ci#define PROFILE_PRIO_BONUS 0x8000 11553a5a1b3Sopenharmony_ci 11653a5a1b3Sopenharmony_cistruct userdata { 11753a5a1b3Sopenharmony_ci pa_core *core; 11853a5a1b3Sopenharmony_ci pa_module *module; 11953a5a1b3Sopenharmony_ci 12053a5a1b3Sopenharmony_ci char *device_id; 12153a5a1b3Sopenharmony_ci int alsa_card_index; 12253a5a1b3Sopenharmony_ci 12353a5a1b3Sopenharmony_ci pa_hashmap *mixers; 12453a5a1b3Sopenharmony_ci pa_hashmap *jacks; 12553a5a1b3Sopenharmony_ci 12653a5a1b3Sopenharmony_ci pa_card *card; 12753a5a1b3Sopenharmony_ci 12853a5a1b3Sopenharmony_ci pa_modargs *modargs; 12953a5a1b3Sopenharmony_ci 13053a5a1b3Sopenharmony_ci pa_alsa_profile_set *profile_set; 13153a5a1b3Sopenharmony_ci 13253a5a1b3Sopenharmony_ci /* ucm stuffs */ 13353a5a1b3Sopenharmony_ci bool use_ucm; 13453a5a1b3Sopenharmony_ci pa_alsa_ucm_config ucm; 13553a5a1b3Sopenharmony_ci 13653a5a1b3Sopenharmony_ci}; 13753a5a1b3Sopenharmony_ci 13853a5a1b3Sopenharmony_cistruct profile_data { 13953a5a1b3Sopenharmony_ci pa_alsa_profile *profile; 14053a5a1b3Sopenharmony_ci}; 14153a5a1b3Sopenharmony_ci 14253a5a1b3Sopenharmony_cistatic void add_profiles(struct userdata *u, pa_hashmap *h, pa_hashmap *ports) { 14353a5a1b3Sopenharmony_ci pa_alsa_profile *ap; 14453a5a1b3Sopenharmony_ci void *state; 14553a5a1b3Sopenharmony_ci 14653a5a1b3Sopenharmony_ci pa_assert(u); 14753a5a1b3Sopenharmony_ci pa_assert(h); 14853a5a1b3Sopenharmony_ci 14953a5a1b3Sopenharmony_ci PA_HASHMAP_FOREACH(ap, u->profile_set->profiles, state) { 15053a5a1b3Sopenharmony_ci struct profile_data *d; 15153a5a1b3Sopenharmony_ci pa_card_profile *cp; 15253a5a1b3Sopenharmony_ci pa_alsa_mapping *m; 15353a5a1b3Sopenharmony_ci uint32_t idx; 15453a5a1b3Sopenharmony_ci 15553a5a1b3Sopenharmony_ci cp = pa_card_profile_new(ap->name, ap->description, sizeof(struct profile_data)); 15653a5a1b3Sopenharmony_ci cp->priority = ap->priority ? ap->priority : 1; 15753a5a1b3Sopenharmony_ci cp->input_name = pa_xstrdup(ap->input_name); 15853a5a1b3Sopenharmony_ci cp->output_name = pa_xstrdup(ap->output_name); 15953a5a1b3Sopenharmony_ci 16053a5a1b3Sopenharmony_ci if (ap->output_mappings) { 16153a5a1b3Sopenharmony_ci cp->n_sinks = pa_idxset_size(ap->output_mappings); 16253a5a1b3Sopenharmony_ci 16353a5a1b3Sopenharmony_ci PA_IDXSET_FOREACH(m, ap->output_mappings, idx) { 16453a5a1b3Sopenharmony_ci if (u->use_ucm) 16553a5a1b3Sopenharmony_ci pa_alsa_ucm_add_ports_combination(NULL, &m->ucm_context, true, ports, cp, u->core); 16653a5a1b3Sopenharmony_ci else 16753a5a1b3Sopenharmony_ci pa_alsa_path_set_add_ports(m->output_path_set, cp, ports, NULL, u->core); 16853a5a1b3Sopenharmony_ci if (m->channel_map.channels > cp->max_sink_channels) 16953a5a1b3Sopenharmony_ci cp->max_sink_channels = m->channel_map.channels; 17053a5a1b3Sopenharmony_ci } 17153a5a1b3Sopenharmony_ci } 17253a5a1b3Sopenharmony_ci 17353a5a1b3Sopenharmony_ci if (ap->input_mappings) { 17453a5a1b3Sopenharmony_ci cp->n_sources = pa_idxset_size(ap->input_mappings); 17553a5a1b3Sopenharmony_ci 17653a5a1b3Sopenharmony_ci PA_IDXSET_FOREACH(m, ap->input_mappings, idx) { 17753a5a1b3Sopenharmony_ci if (u->use_ucm) 17853a5a1b3Sopenharmony_ci pa_alsa_ucm_add_ports_combination(NULL, &m->ucm_context, false, ports, cp, u->core); 17953a5a1b3Sopenharmony_ci else 18053a5a1b3Sopenharmony_ci pa_alsa_path_set_add_ports(m->input_path_set, cp, ports, NULL, u->core); 18153a5a1b3Sopenharmony_ci if (m->channel_map.channels > cp->max_source_channels) 18253a5a1b3Sopenharmony_ci cp->max_source_channels = m->channel_map.channels; 18353a5a1b3Sopenharmony_ci } 18453a5a1b3Sopenharmony_ci } 18553a5a1b3Sopenharmony_ci 18653a5a1b3Sopenharmony_ci d = PA_CARD_PROFILE_DATA(cp); 18753a5a1b3Sopenharmony_ci d->profile = ap; 18853a5a1b3Sopenharmony_ci 18953a5a1b3Sopenharmony_ci pa_hashmap_put(h, cp->name, cp); 19053a5a1b3Sopenharmony_ci } 19153a5a1b3Sopenharmony_ci} 19253a5a1b3Sopenharmony_ci 19353a5a1b3Sopenharmony_cistatic void add_disabled_profile(pa_hashmap *profiles) { 19453a5a1b3Sopenharmony_ci pa_card_profile *p; 19553a5a1b3Sopenharmony_ci struct profile_data *d; 19653a5a1b3Sopenharmony_ci 19753a5a1b3Sopenharmony_ci p = pa_card_profile_new("off", _("Off"), sizeof(struct profile_data)); 19853a5a1b3Sopenharmony_ci 19953a5a1b3Sopenharmony_ci d = PA_CARD_PROFILE_DATA(p); 20053a5a1b3Sopenharmony_ci d->profile = NULL; 20153a5a1b3Sopenharmony_ci 20253a5a1b3Sopenharmony_ci pa_hashmap_put(profiles, p->name, p); 20353a5a1b3Sopenharmony_ci} 20453a5a1b3Sopenharmony_ci 20553a5a1b3Sopenharmony_cistatic int card_set_profile(pa_card *c, pa_card_profile *new_profile) { 20653a5a1b3Sopenharmony_ci struct userdata *u; 20753a5a1b3Sopenharmony_ci struct profile_data *nd, *od; 20853a5a1b3Sopenharmony_ci uint32_t idx; 20953a5a1b3Sopenharmony_ci pa_alsa_mapping *am; 21053a5a1b3Sopenharmony_ci pa_queue *sink_inputs = NULL, *source_outputs = NULL; 21153a5a1b3Sopenharmony_ci int ret = 0; 21253a5a1b3Sopenharmony_ci 21353a5a1b3Sopenharmony_ci pa_assert(c); 21453a5a1b3Sopenharmony_ci pa_assert(new_profile); 21553a5a1b3Sopenharmony_ci pa_assert_se(u = c->userdata); 21653a5a1b3Sopenharmony_ci 21753a5a1b3Sopenharmony_ci nd = PA_CARD_PROFILE_DATA(new_profile); 21853a5a1b3Sopenharmony_ci od = PA_CARD_PROFILE_DATA(c->active_profile); 21953a5a1b3Sopenharmony_ci 22053a5a1b3Sopenharmony_ci if (od->profile && od->profile->output_mappings) 22153a5a1b3Sopenharmony_ci PA_IDXSET_FOREACH(am, od->profile->output_mappings, idx) { 22253a5a1b3Sopenharmony_ci if (!am->sink) 22353a5a1b3Sopenharmony_ci continue; 22453a5a1b3Sopenharmony_ci 22553a5a1b3Sopenharmony_ci if (nd->profile && 22653a5a1b3Sopenharmony_ci nd->profile->output_mappings && 22753a5a1b3Sopenharmony_ci pa_idxset_get_by_data(nd->profile->output_mappings, am, NULL)) 22853a5a1b3Sopenharmony_ci continue; 22953a5a1b3Sopenharmony_ci 23053a5a1b3Sopenharmony_ci sink_inputs = pa_sink_move_all_start(am->sink, sink_inputs); 23153a5a1b3Sopenharmony_ci pa_alsa_sink_free(am->sink); 23253a5a1b3Sopenharmony_ci am->sink = NULL; 23353a5a1b3Sopenharmony_ci } 23453a5a1b3Sopenharmony_ci 23553a5a1b3Sopenharmony_ci if (od->profile && od->profile->input_mappings) 23653a5a1b3Sopenharmony_ci PA_IDXSET_FOREACH(am, od->profile->input_mappings, idx) { 23753a5a1b3Sopenharmony_ci if (!am->source) 23853a5a1b3Sopenharmony_ci continue; 23953a5a1b3Sopenharmony_ci 24053a5a1b3Sopenharmony_ci if (nd->profile && 24153a5a1b3Sopenharmony_ci nd->profile->input_mappings && 24253a5a1b3Sopenharmony_ci pa_idxset_get_by_data(nd->profile->input_mappings, am, NULL)) 24353a5a1b3Sopenharmony_ci continue; 24453a5a1b3Sopenharmony_ci 24553a5a1b3Sopenharmony_ci source_outputs = pa_source_move_all_start(am->source, source_outputs); 24653a5a1b3Sopenharmony_ci pa_alsa_source_free(am->source); 24753a5a1b3Sopenharmony_ci am->source = NULL; 24853a5a1b3Sopenharmony_ci } 24953a5a1b3Sopenharmony_ci 25053a5a1b3Sopenharmony_ci /* if UCM is available for this card then update the verb */ 25153a5a1b3Sopenharmony_ci if (u->use_ucm) { 25253a5a1b3Sopenharmony_ci if (pa_alsa_ucm_set_profile(&u->ucm, c, nd->profile ? nd->profile->name : NULL, 25353a5a1b3Sopenharmony_ci od->profile ? od->profile->name : NULL) < 0) { 25453a5a1b3Sopenharmony_ci ret = -1; 25553a5a1b3Sopenharmony_ci goto finish; 25653a5a1b3Sopenharmony_ci } 25753a5a1b3Sopenharmony_ci } 25853a5a1b3Sopenharmony_ci 25953a5a1b3Sopenharmony_ci if (nd->profile && nd->profile->output_mappings) 26053a5a1b3Sopenharmony_ci PA_IDXSET_FOREACH(am, nd->profile->output_mappings, idx) { 26153a5a1b3Sopenharmony_ci 26253a5a1b3Sopenharmony_ci if (!am->sink) 26353a5a1b3Sopenharmony_ci am->sink = pa_alsa_sink_new(c->module, u->modargs, __FILE__, c, am); 26453a5a1b3Sopenharmony_ci 26553a5a1b3Sopenharmony_ci if (sink_inputs && am->sink) { 26653a5a1b3Sopenharmony_ci pa_sink_move_all_finish(am->sink, sink_inputs, false); 26753a5a1b3Sopenharmony_ci sink_inputs = NULL; 26853a5a1b3Sopenharmony_ci } 26953a5a1b3Sopenharmony_ci } 27053a5a1b3Sopenharmony_ci 27153a5a1b3Sopenharmony_ci if (nd->profile && nd->profile->input_mappings) 27253a5a1b3Sopenharmony_ci PA_IDXSET_FOREACH(am, nd->profile->input_mappings, idx) { 27353a5a1b3Sopenharmony_ci 27453a5a1b3Sopenharmony_ci if (!am->source) 27553a5a1b3Sopenharmony_ci am->source = pa_alsa_source_new(c->module, u->modargs, __FILE__, c, am); 27653a5a1b3Sopenharmony_ci 27753a5a1b3Sopenharmony_ci if (source_outputs && am->source) { 27853a5a1b3Sopenharmony_ci pa_source_move_all_finish(am->source, source_outputs, false); 27953a5a1b3Sopenharmony_ci source_outputs = NULL; 28053a5a1b3Sopenharmony_ci } 28153a5a1b3Sopenharmony_ci } 28253a5a1b3Sopenharmony_ci 28353a5a1b3Sopenharmony_cifinish: 28453a5a1b3Sopenharmony_ci if (sink_inputs) 28553a5a1b3Sopenharmony_ci pa_sink_move_all_fail(sink_inputs); 28653a5a1b3Sopenharmony_ci 28753a5a1b3Sopenharmony_ci if (source_outputs) 28853a5a1b3Sopenharmony_ci pa_source_move_all_fail(source_outputs); 28953a5a1b3Sopenharmony_ci 29053a5a1b3Sopenharmony_ci return ret; 29153a5a1b3Sopenharmony_ci} 29253a5a1b3Sopenharmony_ci 29353a5a1b3Sopenharmony_cistatic void init_profile(struct userdata *u) { 29453a5a1b3Sopenharmony_ci uint32_t idx; 29553a5a1b3Sopenharmony_ci pa_alsa_mapping *am; 29653a5a1b3Sopenharmony_ci struct profile_data *d; 29753a5a1b3Sopenharmony_ci pa_alsa_ucm_config *ucm = &u->ucm; 29853a5a1b3Sopenharmony_ci 29953a5a1b3Sopenharmony_ci pa_assert(u); 30053a5a1b3Sopenharmony_ci 30153a5a1b3Sopenharmony_ci d = PA_CARD_PROFILE_DATA(u->card->active_profile); 30253a5a1b3Sopenharmony_ci 30353a5a1b3Sopenharmony_ci if (d->profile && u->use_ucm) { 30453a5a1b3Sopenharmony_ci /* Set initial verb */ 30553a5a1b3Sopenharmony_ci if (pa_alsa_ucm_set_profile(ucm, u->card, d->profile->name, NULL) < 0) { 30653a5a1b3Sopenharmony_ci pa_log("Failed to set ucm profile %s", d->profile->name); 30753a5a1b3Sopenharmony_ci return; 30853a5a1b3Sopenharmony_ci } 30953a5a1b3Sopenharmony_ci } 31053a5a1b3Sopenharmony_ci 31153a5a1b3Sopenharmony_ci if (d->profile && d->profile->output_mappings) 31253a5a1b3Sopenharmony_ci PA_IDXSET_FOREACH(am, d->profile->output_mappings, idx) 31353a5a1b3Sopenharmony_ci am->sink = pa_alsa_sink_new(u->module, u->modargs, __FILE__, u->card, am); 31453a5a1b3Sopenharmony_ci 31553a5a1b3Sopenharmony_ci if (d->profile && d->profile->input_mappings) 31653a5a1b3Sopenharmony_ci PA_IDXSET_FOREACH(am, d->profile->input_mappings, idx) 31753a5a1b3Sopenharmony_ci am->source = pa_alsa_source_new(u->module, u->modargs, __FILE__, u->card, am); 31853a5a1b3Sopenharmony_ci} 31953a5a1b3Sopenharmony_ci 32053a5a1b3Sopenharmony_cistatic pa_available_t calc_port_state(pa_device_port *p, struct userdata *u) { 32153a5a1b3Sopenharmony_ci void *state; 32253a5a1b3Sopenharmony_ci pa_alsa_jack *jack; 32353a5a1b3Sopenharmony_ci pa_available_t pa = PA_AVAILABLE_UNKNOWN; 32453a5a1b3Sopenharmony_ci pa_device_port *port; 32553a5a1b3Sopenharmony_ci 32653a5a1b3Sopenharmony_ci PA_HASHMAP_FOREACH(jack, u->jacks, state) { 32753a5a1b3Sopenharmony_ci pa_available_t cpa; 32853a5a1b3Sopenharmony_ci 32953a5a1b3Sopenharmony_ci if (u->use_ucm) 33053a5a1b3Sopenharmony_ci port = pa_hashmap_get(u->card->ports, jack->name); 33153a5a1b3Sopenharmony_ci else { 33253a5a1b3Sopenharmony_ci if (jack->path) 33353a5a1b3Sopenharmony_ci port = jack->path->port; 33453a5a1b3Sopenharmony_ci else 33553a5a1b3Sopenharmony_ci continue; 33653a5a1b3Sopenharmony_ci } 33753a5a1b3Sopenharmony_ci 33853a5a1b3Sopenharmony_ci if (p != port) 33953a5a1b3Sopenharmony_ci continue; 34053a5a1b3Sopenharmony_ci 34153a5a1b3Sopenharmony_ci cpa = jack->plugged_in ? jack->state_plugged : jack->state_unplugged; 34253a5a1b3Sopenharmony_ci 34353a5a1b3Sopenharmony_ci if (cpa == PA_AVAILABLE_NO) { 34453a5a1b3Sopenharmony_ci /* If a plugged-in jack causes the availability to go to NO, it 34553a5a1b3Sopenharmony_ci * should override all other availability information (like a 34653a5a1b3Sopenharmony_ci * blacklist) so set and bail */ 34753a5a1b3Sopenharmony_ci if (jack->plugged_in) { 34853a5a1b3Sopenharmony_ci pa = cpa; 34953a5a1b3Sopenharmony_ci break; 35053a5a1b3Sopenharmony_ci } 35153a5a1b3Sopenharmony_ci 35253a5a1b3Sopenharmony_ci /* If the current availablility is unknown go the more precise no, 35353a5a1b3Sopenharmony_ci * but otherwise don't change state */ 35453a5a1b3Sopenharmony_ci if (pa == PA_AVAILABLE_UNKNOWN) 35553a5a1b3Sopenharmony_ci pa = cpa; 35653a5a1b3Sopenharmony_ci } else if (cpa == PA_AVAILABLE_YES) { 35753a5a1b3Sopenharmony_ci /* Output is available through at least one jack, so go to that 35853a5a1b3Sopenharmony_ci * level of availability. We still need to continue iterating through 35953a5a1b3Sopenharmony_ci * the jacks in case a jack is plugged in that forces the state to no 36053a5a1b3Sopenharmony_ci */ 36153a5a1b3Sopenharmony_ci pa = cpa; 36253a5a1b3Sopenharmony_ci } 36353a5a1b3Sopenharmony_ci } 36453a5a1b3Sopenharmony_ci return pa; 36553a5a1b3Sopenharmony_ci} 36653a5a1b3Sopenharmony_ci 36753a5a1b3Sopenharmony_cistruct temp_port_avail { 36853a5a1b3Sopenharmony_ci pa_device_port *port; 36953a5a1b3Sopenharmony_ci pa_available_t avail; 37053a5a1b3Sopenharmony_ci}; 37153a5a1b3Sopenharmony_ci 37253a5a1b3Sopenharmony_cistatic int report_jack_state(snd_mixer_elem_t *melem, unsigned int mask) { 37353a5a1b3Sopenharmony_ci struct userdata *u = snd_mixer_elem_get_callback_private(melem); 37453a5a1b3Sopenharmony_ci snd_hctl_elem_t *elem = snd_mixer_elem_get_private(melem); 37553a5a1b3Sopenharmony_ci snd_ctl_elem_value_t *elem_value; 37653a5a1b3Sopenharmony_ci bool plugged_in; 37753a5a1b3Sopenharmony_ci void *state; 37853a5a1b3Sopenharmony_ci pa_alsa_jack *jack; 37953a5a1b3Sopenharmony_ci struct temp_port_avail *tp, *tports; 38053a5a1b3Sopenharmony_ci pa_card_profile *profile; 38153a5a1b3Sopenharmony_ci pa_available_t active_available = PA_AVAILABLE_UNKNOWN; 38253a5a1b3Sopenharmony_ci 38353a5a1b3Sopenharmony_ci pa_assert(u); 38453a5a1b3Sopenharmony_ci 38553a5a1b3Sopenharmony_ci /* Changing the jack state may cause a port change, and a port change will 38653a5a1b3Sopenharmony_ci * make the sink or source change the mixer settings. If there are multiple 38753a5a1b3Sopenharmony_ci * users having pulseaudio running, the mixer changes done by inactive 38853a5a1b3Sopenharmony_ci * users may mess up the volume settings for the active users, because when 38953a5a1b3Sopenharmony_ci * the inactive users change the mixer settings, those changes are picked 39053a5a1b3Sopenharmony_ci * up by the active user's pulseaudio instance and the changes are 39153a5a1b3Sopenharmony_ci * interpreted as if the active user changed the settings manually e.g. 39253a5a1b3Sopenharmony_ci * with alsamixer. Even single-user systems suffer from this, because gdm 39353a5a1b3Sopenharmony_ci * runs its own pulseaudio instance. 39453a5a1b3Sopenharmony_ci * 39553a5a1b3Sopenharmony_ci * We rerun this function when being unsuspended to catch up on jack state 39653a5a1b3Sopenharmony_ci * changes */ 39753a5a1b3Sopenharmony_ci if (u->card->suspend_cause & PA_SUSPEND_SESSION) 39853a5a1b3Sopenharmony_ci return 0; 39953a5a1b3Sopenharmony_ci 40053a5a1b3Sopenharmony_ci if (mask == SND_CTL_EVENT_MASK_REMOVE) 40153a5a1b3Sopenharmony_ci return 0; 40253a5a1b3Sopenharmony_ci 40353a5a1b3Sopenharmony_ci snd_ctl_elem_value_alloca(&elem_value); 40453a5a1b3Sopenharmony_ci if (snd_hctl_elem_read(elem, elem_value) < 0) { 40553a5a1b3Sopenharmony_ci pa_log_warn("Failed to read jack detection from '%s'", pa_strnull(snd_hctl_elem_get_name(elem))); 40653a5a1b3Sopenharmony_ci return 0; 40753a5a1b3Sopenharmony_ci } 40853a5a1b3Sopenharmony_ci 40953a5a1b3Sopenharmony_ci plugged_in = !!snd_ctl_elem_value_get_boolean(elem_value, 0); 41053a5a1b3Sopenharmony_ci 41153a5a1b3Sopenharmony_ci pa_log_debug("Jack '%s' is now %s", pa_strnull(snd_hctl_elem_get_name(elem)), plugged_in ? "plugged in" : "unplugged"); 41253a5a1b3Sopenharmony_ci 41353a5a1b3Sopenharmony_ci tports = tp = pa_xnew0(struct temp_port_avail, pa_hashmap_size(u->jacks)+1); 41453a5a1b3Sopenharmony_ci 41553a5a1b3Sopenharmony_ci PA_HASHMAP_FOREACH(jack, u->jacks, state) 41653a5a1b3Sopenharmony_ci if (jack->melem == melem) { 41753a5a1b3Sopenharmony_ci pa_alsa_jack_set_plugged_in(jack, plugged_in); 41853a5a1b3Sopenharmony_ci 41953a5a1b3Sopenharmony_ci if (u->use_ucm) { 42053a5a1b3Sopenharmony_ci /* When using UCM, pa_alsa_jack_set_plugged_in() maps the jack 42153a5a1b3Sopenharmony_ci * state to port availability. */ 42253a5a1b3Sopenharmony_ci continue; 42353a5a1b3Sopenharmony_ci } 42453a5a1b3Sopenharmony_ci 42553a5a1b3Sopenharmony_ci /* When not using UCM, we have to do the jack state -> port 42653a5a1b3Sopenharmony_ci * availability mapping ourselves. */ 42753a5a1b3Sopenharmony_ci pa_assert_se(tp->port = jack->path->port); 42853a5a1b3Sopenharmony_ci tp->avail = calc_port_state(tp->port, u); 42953a5a1b3Sopenharmony_ci tp++; 43053a5a1b3Sopenharmony_ci } 43153a5a1b3Sopenharmony_ci 43253a5a1b3Sopenharmony_ci /* Report available ports before unavailable ones: in case port 1 becomes available when port 2 becomes unavailable, 43353a5a1b3Sopenharmony_ci this prevents an unnecessary switch port 1 -> port 3 -> port 2 */ 43453a5a1b3Sopenharmony_ci 43553a5a1b3Sopenharmony_ci for (tp = tports; tp->port; tp++) 43653a5a1b3Sopenharmony_ci if (tp->avail != PA_AVAILABLE_NO) 43753a5a1b3Sopenharmony_ci pa_device_port_set_available(tp->port, tp->avail); 43853a5a1b3Sopenharmony_ci for (tp = tports; tp->port; tp++) 43953a5a1b3Sopenharmony_ci if (tp->avail == PA_AVAILABLE_NO) 44053a5a1b3Sopenharmony_ci pa_device_port_set_available(tp->port, tp->avail); 44153a5a1b3Sopenharmony_ci 44253a5a1b3Sopenharmony_ci for (tp = tports; tp->port; tp++) { 44353a5a1b3Sopenharmony_ci pa_alsa_port_data *data; 44453a5a1b3Sopenharmony_ci pa_sink *sink; 44553a5a1b3Sopenharmony_ci uint32_t idx; 44653a5a1b3Sopenharmony_ci 44753a5a1b3Sopenharmony_ci data = PA_DEVICE_PORT_DATA(tp->port); 44853a5a1b3Sopenharmony_ci 44953a5a1b3Sopenharmony_ci if (!data->suspend_when_unavailable) 45053a5a1b3Sopenharmony_ci continue; 45153a5a1b3Sopenharmony_ci 45253a5a1b3Sopenharmony_ci PA_IDXSET_FOREACH(sink, u->core->sinks, idx) { 45353a5a1b3Sopenharmony_ci if (sink->active_port == tp->port) 45453a5a1b3Sopenharmony_ci pa_sink_suspend(sink, tp->avail == PA_AVAILABLE_NO, PA_SUSPEND_UNAVAILABLE); 45553a5a1b3Sopenharmony_ci } 45653a5a1b3Sopenharmony_ci } 45753a5a1b3Sopenharmony_ci 45853a5a1b3Sopenharmony_ci /* Update profile availabilities. Ideally we would mark all profiles 45953a5a1b3Sopenharmony_ci * unavailable that contain unavailable devices. We can't currently do that 46053a5a1b3Sopenharmony_ci * in all cases, because if there are multiple sinks in a profile, and the 46153a5a1b3Sopenharmony_ci * profile contains a mix of available and unavailable ports, we don't know 46253a5a1b3Sopenharmony_ci * how the ports are distributed between the different sinks. It's possible 46353a5a1b3Sopenharmony_ci * that some sinks contain only unavailable ports, in which case we should 46453a5a1b3Sopenharmony_ci * mark the profile as unavailable, but it's also possible that all sinks 46553a5a1b3Sopenharmony_ci * contain at least one available port, in which case we should mark the 46653a5a1b3Sopenharmony_ci * profile as available. Until the data structures are improved so that we 46753a5a1b3Sopenharmony_ci * can distinguish between these two cases, we mark the problematic cases 46853a5a1b3Sopenharmony_ci * as available (well, "unknown" to be precise, but there's little 46953a5a1b3Sopenharmony_ci * practical difference). 47053a5a1b3Sopenharmony_ci * 47153a5a1b3Sopenharmony_ci * A profile will be marked unavailable: 47253a5a1b3Sopenharmony_ci * only contains output ports and all ports are unavailable 47353a5a1b3Sopenharmony_ci * only contains input ports and all ports are unavailable 47453a5a1b3Sopenharmony_ci * contains both input and output ports and all ports are unavailable 47553a5a1b3Sopenharmony_ci * 47653a5a1b3Sopenharmony_ci * A profile will be awarded priority bonus: 47753a5a1b3Sopenharmony_ci * only contains output ports and at least one port is available 47853a5a1b3Sopenharmony_ci * only contains input ports and at least one port is available 47953a5a1b3Sopenharmony_ci * contains both output and input ports and at least one output port 48053a5a1b3Sopenharmony_ci * and one input port are available 48153a5a1b3Sopenharmony_ci * 48253a5a1b3Sopenharmony_ci * The rest profiles will not be marked unavailable and will not be 48353a5a1b3Sopenharmony_ci * awarded priority bonus 48453a5a1b3Sopenharmony_ci * 48553a5a1b3Sopenharmony_ci * If there are no output ports at all, but the profile contains at least 48653a5a1b3Sopenharmony_ci * one sink, then the output is considered to be available. */ 48753a5a1b3Sopenharmony_ci if (u->card->active_profile) 48853a5a1b3Sopenharmony_ci active_available = u->card->active_profile->available; 48953a5a1b3Sopenharmony_ci PA_HASHMAP_FOREACH(profile, u->card->profiles, state) { 49053a5a1b3Sopenharmony_ci pa_device_port *port; 49153a5a1b3Sopenharmony_ci void *state2; 49253a5a1b3Sopenharmony_ci bool has_input_port = false; 49353a5a1b3Sopenharmony_ci bool has_output_port = false; 49453a5a1b3Sopenharmony_ci bool found_available_input_port = false; 49553a5a1b3Sopenharmony_ci bool found_available_output_port = false; 49653a5a1b3Sopenharmony_ci pa_available_t available = PA_AVAILABLE_UNKNOWN; 49753a5a1b3Sopenharmony_ci 49853a5a1b3Sopenharmony_ci profile->priority &= ~PROFILE_PRIO_BONUS; 49953a5a1b3Sopenharmony_ci PA_HASHMAP_FOREACH(port, u->card->ports, state2) { 50053a5a1b3Sopenharmony_ci if (!pa_hashmap_get(port->profiles, profile->name)) 50153a5a1b3Sopenharmony_ci continue; 50253a5a1b3Sopenharmony_ci 50353a5a1b3Sopenharmony_ci if (port->direction == PA_DIRECTION_INPUT) { 50453a5a1b3Sopenharmony_ci has_input_port = true; 50553a5a1b3Sopenharmony_ci 50653a5a1b3Sopenharmony_ci if (port->available != PA_AVAILABLE_NO) 50753a5a1b3Sopenharmony_ci found_available_input_port = true; 50853a5a1b3Sopenharmony_ci } else { 50953a5a1b3Sopenharmony_ci has_output_port = true; 51053a5a1b3Sopenharmony_ci 51153a5a1b3Sopenharmony_ci if (port->available != PA_AVAILABLE_NO) 51253a5a1b3Sopenharmony_ci found_available_output_port = true; 51353a5a1b3Sopenharmony_ci } 51453a5a1b3Sopenharmony_ci } 51553a5a1b3Sopenharmony_ci 51653a5a1b3Sopenharmony_ci if ((has_input_port && found_available_input_port && !has_output_port) || 51753a5a1b3Sopenharmony_ci (has_output_port && found_available_output_port && !has_input_port) || 51853a5a1b3Sopenharmony_ci (has_input_port && found_available_input_port && has_output_port && found_available_output_port)) 51953a5a1b3Sopenharmony_ci profile->priority |= PROFILE_PRIO_BONUS; 52053a5a1b3Sopenharmony_ci 52153a5a1b3Sopenharmony_ci if ((has_input_port && !found_available_input_port && has_output_port && !found_available_output_port) || 52253a5a1b3Sopenharmony_ci (has_input_port && !found_available_input_port && !has_output_port) || 52353a5a1b3Sopenharmony_ci (has_output_port && !found_available_output_port && !has_input_port)) 52453a5a1b3Sopenharmony_ci available = PA_AVAILABLE_NO; 52553a5a1b3Sopenharmony_ci 52653a5a1b3Sopenharmony_ci /* We want to update the active profile's status last, so logic that 52753a5a1b3Sopenharmony_ci * may change the active profile based on profile availability status 52853a5a1b3Sopenharmony_ci * has an updated view of all profiles' availabilities. */ 52953a5a1b3Sopenharmony_ci if (profile == u->card->active_profile) 53053a5a1b3Sopenharmony_ci active_available = available; 53153a5a1b3Sopenharmony_ci else 53253a5a1b3Sopenharmony_ci pa_card_profile_set_available(profile, available); 53353a5a1b3Sopenharmony_ci } 53453a5a1b3Sopenharmony_ci 53553a5a1b3Sopenharmony_ci if (u->card->active_profile) 53653a5a1b3Sopenharmony_ci pa_card_profile_set_available(u->card->active_profile, active_available); 53753a5a1b3Sopenharmony_ci 53853a5a1b3Sopenharmony_ci pa_xfree(tports); 53953a5a1b3Sopenharmony_ci return 0; 54053a5a1b3Sopenharmony_ci} 54153a5a1b3Sopenharmony_ci 54253a5a1b3Sopenharmony_cistatic pa_device_port* find_port_with_eld_device(struct userdata *u, int device) { 54353a5a1b3Sopenharmony_ci void *state; 54453a5a1b3Sopenharmony_ci pa_device_port *p; 54553a5a1b3Sopenharmony_ci 54653a5a1b3Sopenharmony_ci if (u->use_ucm) { 54753a5a1b3Sopenharmony_ci PA_HASHMAP_FOREACH(p, u->card->ports, state) { 54853a5a1b3Sopenharmony_ci pa_alsa_ucm_port_data *data = PA_DEVICE_PORT_DATA(p); 54953a5a1b3Sopenharmony_ci pa_assert(data->eld_mixer_device_name); 55053a5a1b3Sopenharmony_ci if (device == data->eld_device) 55153a5a1b3Sopenharmony_ci return p; 55253a5a1b3Sopenharmony_ci } 55353a5a1b3Sopenharmony_ci } else { 55453a5a1b3Sopenharmony_ci PA_HASHMAP_FOREACH(p, u->card->ports, state) { 55553a5a1b3Sopenharmony_ci pa_alsa_port_data *data = PA_DEVICE_PORT_DATA(p); 55653a5a1b3Sopenharmony_ci pa_assert(data->path); 55753a5a1b3Sopenharmony_ci if (device == data->path->eld_device) 55853a5a1b3Sopenharmony_ci return p; 55953a5a1b3Sopenharmony_ci } 56053a5a1b3Sopenharmony_ci } 56153a5a1b3Sopenharmony_ci return NULL; 56253a5a1b3Sopenharmony_ci} 56353a5a1b3Sopenharmony_ci 56453a5a1b3Sopenharmony_cistatic int hdmi_eld_changed(snd_mixer_elem_t *melem, unsigned int mask) { 56553a5a1b3Sopenharmony_ci struct userdata *u = snd_mixer_elem_get_callback_private(melem); 56653a5a1b3Sopenharmony_ci snd_hctl_elem_t *elem = snd_mixer_elem_get_private(melem); 56753a5a1b3Sopenharmony_ci int device = snd_hctl_elem_get_device(elem); 56853a5a1b3Sopenharmony_ci const char *old_monitor_name; 56953a5a1b3Sopenharmony_ci pa_device_port *p; 57053a5a1b3Sopenharmony_ci pa_hdmi_eld eld; 57153a5a1b3Sopenharmony_ci bool changed = false; 57253a5a1b3Sopenharmony_ci 57353a5a1b3Sopenharmony_ci if (mask == SND_CTL_EVENT_MASK_REMOVE) 57453a5a1b3Sopenharmony_ci return 0; 57553a5a1b3Sopenharmony_ci 57653a5a1b3Sopenharmony_ci p = find_port_with_eld_device(u, device); 57753a5a1b3Sopenharmony_ci if (p == NULL) { 57853a5a1b3Sopenharmony_ci pa_log_error("Invalid device changed in ALSA: %d", device); 57953a5a1b3Sopenharmony_ci return 0; 58053a5a1b3Sopenharmony_ci } 58153a5a1b3Sopenharmony_ci 58253a5a1b3Sopenharmony_ci if (pa_alsa_get_hdmi_eld(elem, &eld) < 0) 58353a5a1b3Sopenharmony_ci memset(&eld, 0, sizeof(eld)); 58453a5a1b3Sopenharmony_ci 58553a5a1b3Sopenharmony_ci old_monitor_name = pa_proplist_gets(p->proplist, PA_PROP_DEVICE_PRODUCT_NAME); 58653a5a1b3Sopenharmony_ci if (eld.monitor_name[0] == '\0') { 58753a5a1b3Sopenharmony_ci changed |= old_monitor_name != NULL; 58853a5a1b3Sopenharmony_ci pa_proplist_unset(p->proplist, PA_PROP_DEVICE_PRODUCT_NAME); 58953a5a1b3Sopenharmony_ci } else { 59053a5a1b3Sopenharmony_ci changed |= (old_monitor_name == NULL) || (strcmp(old_monitor_name, eld.monitor_name) != 0); 59153a5a1b3Sopenharmony_ci pa_proplist_sets(p->proplist, PA_PROP_DEVICE_PRODUCT_NAME, eld.monitor_name); 59253a5a1b3Sopenharmony_ci } 59353a5a1b3Sopenharmony_ci 59453a5a1b3Sopenharmony_ci if (changed && mask != 0) 59553a5a1b3Sopenharmony_ci pa_subscription_post(u->core, PA_SUBSCRIPTION_EVENT_CARD|PA_SUBSCRIPTION_EVENT_CHANGE, u->card->index); 59653a5a1b3Sopenharmony_ci 59753a5a1b3Sopenharmony_ci return 0; 59853a5a1b3Sopenharmony_ci} 59953a5a1b3Sopenharmony_ci 60053a5a1b3Sopenharmony_cistatic void init_eld_ctls(struct userdata *u) { 60153a5a1b3Sopenharmony_ci void *state; 60253a5a1b3Sopenharmony_ci pa_device_port *port; 60353a5a1b3Sopenharmony_ci 60453a5a1b3Sopenharmony_ci /* The code in this function expects ports to have a pa_alsa_port_data 60553a5a1b3Sopenharmony_ci * struct as their data, but in UCM mode ports don't have any data. Hence, 60653a5a1b3Sopenharmony_ci * the ELD controls can't currently be used in UCM mode. */ 60753a5a1b3Sopenharmony_ci PA_HASHMAP_FOREACH(port, u->card->ports, state) { 60853a5a1b3Sopenharmony_ci snd_mixer_t *mixer_handle; 60953a5a1b3Sopenharmony_ci snd_mixer_elem_t* melem; 61053a5a1b3Sopenharmony_ci int device; 61153a5a1b3Sopenharmony_ci 61253a5a1b3Sopenharmony_ci if (u->use_ucm) { 61353a5a1b3Sopenharmony_ci pa_alsa_ucm_port_data *data = PA_DEVICE_PORT_DATA(port); 61453a5a1b3Sopenharmony_ci device = data->eld_device; 61553a5a1b3Sopenharmony_ci if (device < 0 || !data->eld_mixer_device_name) 61653a5a1b3Sopenharmony_ci continue; 61753a5a1b3Sopenharmony_ci 61853a5a1b3Sopenharmony_ci mixer_handle = pa_alsa_open_mixer_by_name(u->mixers, data->eld_mixer_device_name, true); 61953a5a1b3Sopenharmony_ci } else { 62053a5a1b3Sopenharmony_ci pa_alsa_port_data *data = PA_DEVICE_PORT_DATA(port); 62153a5a1b3Sopenharmony_ci 62253a5a1b3Sopenharmony_ci pa_assert(data->path); 62353a5a1b3Sopenharmony_ci 62453a5a1b3Sopenharmony_ci device = data->path->eld_device; 62553a5a1b3Sopenharmony_ci if (device < 0) 62653a5a1b3Sopenharmony_ci continue; 62753a5a1b3Sopenharmony_ci 62853a5a1b3Sopenharmony_ci mixer_handle = pa_alsa_open_mixer(u->mixers, u->alsa_card_index, true); 62953a5a1b3Sopenharmony_ci } 63053a5a1b3Sopenharmony_ci 63153a5a1b3Sopenharmony_ci if (!mixer_handle) 63253a5a1b3Sopenharmony_ci continue; 63353a5a1b3Sopenharmony_ci 63453a5a1b3Sopenharmony_ci melem = pa_alsa_mixer_find_pcm(mixer_handle, "ELD", device); 63553a5a1b3Sopenharmony_ci if (melem) { 63653a5a1b3Sopenharmony_ci pa_alsa_mixer_set_fdlist(u->mixers, mixer_handle, u->core->mainloop); 63753a5a1b3Sopenharmony_ci snd_mixer_elem_set_callback(melem, hdmi_eld_changed); 63853a5a1b3Sopenharmony_ci snd_mixer_elem_set_callback_private(melem, u); 63953a5a1b3Sopenharmony_ci hdmi_eld_changed(melem, 0); 64053a5a1b3Sopenharmony_ci pa_log_info("ELD device found for port %s (%d).", port->name, device); 64153a5a1b3Sopenharmony_ci } 64253a5a1b3Sopenharmony_ci else 64353a5a1b3Sopenharmony_ci pa_log_debug("No ELD device found for port %s (%d).", port->name, device); 64453a5a1b3Sopenharmony_ci } 64553a5a1b3Sopenharmony_ci} 64653a5a1b3Sopenharmony_ci 64753a5a1b3Sopenharmony_cistatic void init_jacks(struct userdata *u) { 64853a5a1b3Sopenharmony_ci void *state; 64953a5a1b3Sopenharmony_ci pa_alsa_path* path; 65053a5a1b3Sopenharmony_ci pa_alsa_jack* jack; 65153a5a1b3Sopenharmony_ci char buf[64]; 65253a5a1b3Sopenharmony_ci 65353a5a1b3Sopenharmony_ci u->jacks = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); 65453a5a1b3Sopenharmony_ci 65553a5a1b3Sopenharmony_ci if (u->use_ucm) { 65653a5a1b3Sopenharmony_ci PA_LLIST_FOREACH(jack, u->ucm.jacks) 65753a5a1b3Sopenharmony_ci if (jack->has_control) 65853a5a1b3Sopenharmony_ci pa_hashmap_put(u->jacks, jack, jack); 65953a5a1b3Sopenharmony_ci } else { 66053a5a1b3Sopenharmony_ci /* See if we have any jacks */ 66153a5a1b3Sopenharmony_ci if (u->profile_set->output_paths) 66253a5a1b3Sopenharmony_ci PA_HASHMAP_FOREACH(path, u->profile_set->output_paths, state) 66353a5a1b3Sopenharmony_ci PA_LLIST_FOREACH(jack, path->jacks) 66453a5a1b3Sopenharmony_ci if (jack->has_control) 66553a5a1b3Sopenharmony_ci pa_hashmap_put(u->jacks, jack, jack); 66653a5a1b3Sopenharmony_ci 66753a5a1b3Sopenharmony_ci if (u->profile_set->input_paths) 66853a5a1b3Sopenharmony_ci PA_HASHMAP_FOREACH(path, u->profile_set->input_paths, state) 66953a5a1b3Sopenharmony_ci PA_LLIST_FOREACH(jack, path->jacks) 67053a5a1b3Sopenharmony_ci if (jack->has_control) 67153a5a1b3Sopenharmony_ci pa_hashmap_put(u->jacks, jack, jack); 67253a5a1b3Sopenharmony_ci } 67353a5a1b3Sopenharmony_ci 67453a5a1b3Sopenharmony_ci pa_log_debug("Found %d jacks.", pa_hashmap_size(u->jacks)); 67553a5a1b3Sopenharmony_ci 67653a5a1b3Sopenharmony_ci if (pa_hashmap_size(u->jacks) == 0) 67753a5a1b3Sopenharmony_ci return; 67853a5a1b3Sopenharmony_ci 67953a5a1b3Sopenharmony_ci PA_HASHMAP_FOREACH(jack, u->jacks, state) { 68053a5a1b3Sopenharmony_ci if (!jack->mixer_device_name) { 68153a5a1b3Sopenharmony_ci jack->mixer_handle = pa_alsa_open_mixer(u->mixers, u->alsa_card_index, false); 68253a5a1b3Sopenharmony_ci if (!jack->mixer_handle) { 68353a5a1b3Sopenharmony_ci pa_log("Failed to open mixer for card %d for jack detection", u->alsa_card_index); 68453a5a1b3Sopenharmony_ci continue; 68553a5a1b3Sopenharmony_ci } 68653a5a1b3Sopenharmony_ci } else { 68753a5a1b3Sopenharmony_ci jack->mixer_handle = pa_alsa_open_mixer_by_name(u->mixers, jack->mixer_device_name, false); 68853a5a1b3Sopenharmony_ci if (!jack->mixer_handle) { 68953a5a1b3Sopenharmony_ci pa_log("Failed to open mixer '%s' for jack detection", jack->mixer_device_name); 69053a5a1b3Sopenharmony_ci continue; 69153a5a1b3Sopenharmony_ci } 69253a5a1b3Sopenharmony_ci } 69353a5a1b3Sopenharmony_ci pa_alsa_mixer_set_fdlist(u->mixers, jack->mixer_handle, u->core->mainloop); 69453a5a1b3Sopenharmony_ci jack->melem = pa_alsa_mixer_find_card(jack->mixer_handle, &jack->alsa_id, 0); 69553a5a1b3Sopenharmony_ci if (!jack->melem) { 69653a5a1b3Sopenharmony_ci pa_alsa_mixer_id_to_string(buf, sizeof(buf), &jack->alsa_id); 69753a5a1b3Sopenharmony_ci pa_log_warn("Jack %s seems to have disappeared.", buf); 69853a5a1b3Sopenharmony_ci pa_alsa_jack_set_has_control(jack, false); 69953a5a1b3Sopenharmony_ci continue; 70053a5a1b3Sopenharmony_ci } 70153a5a1b3Sopenharmony_ci snd_mixer_elem_set_callback(jack->melem, report_jack_state); 70253a5a1b3Sopenharmony_ci snd_mixer_elem_set_callback_private(jack->melem, u); 70353a5a1b3Sopenharmony_ci report_jack_state(jack->melem, 0); 70453a5a1b3Sopenharmony_ci } 70553a5a1b3Sopenharmony_ci} 70653a5a1b3Sopenharmony_ci 70753a5a1b3Sopenharmony_cistatic void prune_singleton_availability_groups(pa_hashmap *ports) { 70853a5a1b3Sopenharmony_ci pa_device_port *p; 70953a5a1b3Sopenharmony_ci pa_hashmap *group_counts; 71053a5a1b3Sopenharmony_ci void *state, *count; 71153a5a1b3Sopenharmony_ci const char *group; 71253a5a1b3Sopenharmony_ci 71353a5a1b3Sopenharmony_ci /* Collect groups and erase those that don't have more than 1 path */ 71453a5a1b3Sopenharmony_ci group_counts = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); 71553a5a1b3Sopenharmony_ci 71653a5a1b3Sopenharmony_ci PA_HASHMAP_FOREACH(p, ports, state) { 71753a5a1b3Sopenharmony_ci if (p->availability_group) { 71853a5a1b3Sopenharmony_ci count = pa_hashmap_get(group_counts, p->availability_group); 71953a5a1b3Sopenharmony_ci pa_hashmap_remove(group_counts, p->availability_group); 72053a5a1b3Sopenharmony_ci pa_hashmap_put(group_counts, p->availability_group, PA_UINT_TO_PTR(PA_PTR_TO_UINT(count) + 1)); 72153a5a1b3Sopenharmony_ci } 72253a5a1b3Sopenharmony_ci } 72353a5a1b3Sopenharmony_ci 72453a5a1b3Sopenharmony_ci /* Now we have an availability_group -> count map, let's drop all groups 72553a5a1b3Sopenharmony_ci * that have only one member */ 72653a5a1b3Sopenharmony_ci PA_HASHMAP_FOREACH_KV(group, count, group_counts, state) { 72753a5a1b3Sopenharmony_ci if (count == PA_UINT_TO_PTR(1)) 72853a5a1b3Sopenharmony_ci pa_hashmap_remove(group_counts, group); 72953a5a1b3Sopenharmony_ci } 73053a5a1b3Sopenharmony_ci 73153a5a1b3Sopenharmony_ci PA_HASHMAP_FOREACH(p, ports, state) { 73253a5a1b3Sopenharmony_ci if (p->availability_group && !pa_hashmap_get(group_counts, p->availability_group)) { 73353a5a1b3Sopenharmony_ci pa_log_debug("Pruned singleton availability group %s from port %s", p->availability_group, p->name); 73453a5a1b3Sopenharmony_ci 73553a5a1b3Sopenharmony_ci pa_xfree(p->availability_group); 73653a5a1b3Sopenharmony_ci p->availability_group = NULL; 73753a5a1b3Sopenharmony_ci } 73853a5a1b3Sopenharmony_ci } 73953a5a1b3Sopenharmony_ci 74053a5a1b3Sopenharmony_ci pa_hashmap_free(group_counts); 74153a5a1b3Sopenharmony_ci} 74253a5a1b3Sopenharmony_ci 74353a5a1b3Sopenharmony_cistatic void set_card_name(pa_card_new_data *data, pa_modargs *ma, const char *device_id) { 74453a5a1b3Sopenharmony_ci char *t; 74553a5a1b3Sopenharmony_ci const char *n; 74653a5a1b3Sopenharmony_ci 74753a5a1b3Sopenharmony_ci pa_assert(data); 74853a5a1b3Sopenharmony_ci pa_assert(ma); 74953a5a1b3Sopenharmony_ci pa_assert(device_id); 75053a5a1b3Sopenharmony_ci 75153a5a1b3Sopenharmony_ci if ((n = pa_modargs_get_value(ma, "card_name", NULL))) { 75253a5a1b3Sopenharmony_ci pa_card_new_data_set_name(data, n); 75353a5a1b3Sopenharmony_ci data->namereg_fail = true; 75453a5a1b3Sopenharmony_ci return; 75553a5a1b3Sopenharmony_ci } 75653a5a1b3Sopenharmony_ci 75753a5a1b3Sopenharmony_ci if ((n = pa_modargs_get_value(ma, "name", NULL))) 75853a5a1b3Sopenharmony_ci data->namereg_fail = true; 75953a5a1b3Sopenharmony_ci else { 76053a5a1b3Sopenharmony_ci n = device_id; 76153a5a1b3Sopenharmony_ci data->namereg_fail = false; 76253a5a1b3Sopenharmony_ci } 76353a5a1b3Sopenharmony_ci 76453a5a1b3Sopenharmony_ci t = pa_sprintf_malloc("alsa_card.%s", n); 76553a5a1b3Sopenharmony_ci pa_card_new_data_set_name(data, t); 76653a5a1b3Sopenharmony_ci pa_xfree(t); 76753a5a1b3Sopenharmony_ci} 76853a5a1b3Sopenharmony_ci 76953a5a1b3Sopenharmony_cistatic pa_hook_result_t card_suspend_changed(pa_core *c, pa_card *card, struct userdata *u) { 77053a5a1b3Sopenharmony_ci void *state; 77153a5a1b3Sopenharmony_ci pa_alsa_jack *jack; 77253a5a1b3Sopenharmony_ci 77353a5a1b3Sopenharmony_ci if (card->suspend_cause == 0) { 77453a5a1b3Sopenharmony_ci /* We were unsuspended, update jack state in case it changed while we were suspended */ 77553a5a1b3Sopenharmony_ci PA_HASHMAP_FOREACH(jack, u->jacks, state) { 77653a5a1b3Sopenharmony_ci if (jack->melem) 77753a5a1b3Sopenharmony_ci report_jack_state(jack->melem, 0); 77853a5a1b3Sopenharmony_ci } 77953a5a1b3Sopenharmony_ci } 78053a5a1b3Sopenharmony_ci 78153a5a1b3Sopenharmony_ci return PA_HOOK_OK; 78253a5a1b3Sopenharmony_ci} 78353a5a1b3Sopenharmony_ci 78453a5a1b3Sopenharmony_cistatic pa_hook_result_t sink_input_put_hook_callback(pa_core *c, pa_sink_input *sink_input, struct userdata *u) { 78553a5a1b3Sopenharmony_ci const char *role; 78653a5a1b3Sopenharmony_ci pa_sink *sink = sink_input->sink; 78753a5a1b3Sopenharmony_ci 78853a5a1b3Sopenharmony_ci pa_assert(sink); 78953a5a1b3Sopenharmony_ci 79053a5a1b3Sopenharmony_ci role = pa_proplist_gets(sink_input->proplist, PA_PROP_MEDIA_ROLE); 79153a5a1b3Sopenharmony_ci 79253a5a1b3Sopenharmony_ci /* new sink input linked to sink of this card */ 79353a5a1b3Sopenharmony_ci if (role && sink->card == u->card) 79453a5a1b3Sopenharmony_ci pa_alsa_ucm_roled_stream_begin(&u->ucm, role, PA_DIRECTION_OUTPUT); 79553a5a1b3Sopenharmony_ci 79653a5a1b3Sopenharmony_ci return PA_HOOK_OK; 79753a5a1b3Sopenharmony_ci} 79853a5a1b3Sopenharmony_ci 79953a5a1b3Sopenharmony_cistatic pa_hook_result_t source_output_put_hook_callback(pa_core *c, pa_source_output *source_output, struct userdata *u) { 80053a5a1b3Sopenharmony_ci const char *role; 80153a5a1b3Sopenharmony_ci pa_source *source = source_output->source; 80253a5a1b3Sopenharmony_ci 80353a5a1b3Sopenharmony_ci pa_assert(source); 80453a5a1b3Sopenharmony_ci 80553a5a1b3Sopenharmony_ci role = pa_proplist_gets(source_output->proplist, PA_PROP_MEDIA_ROLE); 80653a5a1b3Sopenharmony_ci 80753a5a1b3Sopenharmony_ci /* new source output linked to source of this card */ 80853a5a1b3Sopenharmony_ci if (role && source->card == u->card) 80953a5a1b3Sopenharmony_ci pa_alsa_ucm_roled_stream_begin(&u->ucm, role, PA_DIRECTION_INPUT); 81053a5a1b3Sopenharmony_ci 81153a5a1b3Sopenharmony_ci return PA_HOOK_OK; 81253a5a1b3Sopenharmony_ci} 81353a5a1b3Sopenharmony_ci 81453a5a1b3Sopenharmony_cistatic pa_hook_result_t sink_input_unlink_hook_callback(pa_core *c, pa_sink_input *sink_input, struct userdata *u) { 81553a5a1b3Sopenharmony_ci const char *role; 81653a5a1b3Sopenharmony_ci pa_sink *sink = sink_input->sink; 81753a5a1b3Sopenharmony_ci 81853a5a1b3Sopenharmony_ci pa_assert(sink); 81953a5a1b3Sopenharmony_ci 82053a5a1b3Sopenharmony_ci role = pa_proplist_gets(sink_input->proplist, PA_PROP_MEDIA_ROLE); 82153a5a1b3Sopenharmony_ci 82253a5a1b3Sopenharmony_ci /* new sink input unlinked from sink of this card */ 82353a5a1b3Sopenharmony_ci if (role && sink->card == u->card) 82453a5a1b3Sopenharmony_ci pa_alsa_ucm_roled_stream_end(&u->ucm, role, PA_DIRECTION_OUTPUT); 82553a5a1b3Sopenharmony_ci 82653a5a1b3Sopenharmony_ci return PA_HOOK_OK; 82753a5a1b3Sopenharmony_ci} 82853a5a1b3Sopenharmony_ci 82953a5a1b3Sopenharmony_cistatic pa_hook_result_t source_output_unlink_hook_callback(pa_core *c, pa_source_output *source_output, struct userdata *u) { 83053a5a1b3Sopenharmony_ci const char *role; 83153a5a1b3Sopenharmony_ci pa_source *source = source_output->source; 83253a5a1b3Sopenharmony_ci 83353a5a1b3Sopenharmony_ci pa_assert(source); 83453a5a1b3Sopenharmony_ci 83553a5a1b3Sopenharmony_ci role = pa_proplist_gets(source_output->proplist, PA_PROP_MEDIA_ROLE); 83653a5a1b3Sopenharmony_ci 83753a5a1b3Sopenharmony_ci /* new source output unlinked from source of this card */ 83853a5a1b3Sopenharmony_ci if (role && source->card == u->card) 83953a5a1b3Sopenharmony_ci pa_alsa_ucm_roled_stream_end(&u->ucm, role, PA_DIRECTION_INPUT); 84053a5a1b3Sopenharmony_ci 84153a5a1b3Sopenharmony_ci return PA_HOOK_OK; 84253a5a1b3Sopenharmony_ci} 84353a5a1b3Sopenharmony_ci 84453a5a1b3Sopenharmony_ciint pa__init(pa_module *m) { 84553a5a1b3Sopenharmony_ci pa_card_new_data data; 84653a5a1b3Sopenharmony_ci bool ignore_dB = false; 84753a5a1b3Sopenharmony_ci struct userdata *u; 84853a5a1b3Sopenharmony_ci pa_reserve_wrapper *reserve = NULL; 84953a5a1b3Sopenharmony_ci const char *description; 85053a5a1b3Sopenharmony_ci const char *profile_str = NULL; 85153a5a1b3Sopenharmony_ci char *fn = NULL; 85253a5a1b3Sopenharmony_ci char *udev_args = NULL; 85353a5a1b3Sopenharmony_ci bool namereg_fail = false; 85453a5a1b3Sopenharmony_ci int err = -PA_MODULE_ERR_UNSPECIFIED, rval; 85553a5a1b3Sopenharmony_ci 85653a5a1b3Sopenharmony_ci pa_alsa_refcnt_inc(); 85753a5a1b3Sopenharmony_ci 85853a5a1b3Sopenharmony_ci pa_assert(m); 85953a5a1b3Sopenharmony_ci 86053a5a1b3Sopenharmony_ci m->userdata = u = pa_xnew0(struct userdata, 1); 86153a5a1b3Sopenharmony_ci u->core = m->core; 86253a5a1b3Sopenharmony_ci u->module = m; 86353a5a1b3Sopenharmony_ci u->use_ucm = true; 86453a5a1b3Sopenharmony_ci u->ucm.core = m->core; 86553a5a1b3Sopenharmony_ci 86653a5a1b3Sopenharmony_ci u->mixers = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, 86753a5a1b3Sopenharmony_ci pa_xfree, (pa_free_cb_t) pa_alsa_mixer_free); 86853a5a1b3Sopenharmony_ci u->ucm.mixers = u->mixers; /* alias */ 86953a5a1b3Sopenharmony_ci 87053a5a1b3Sopenharmony_ci if (!(u->modargs = pa_modargs_new(m->argument, valid_modargs))) { 87153a5a1b3Sopenharmony_ci pa_log("Failed to parse module arguments."); 87253a5a1b3Sopenharmony_ci goto fail; 87353a5a1b3Sopenharmony_ci } 87453a5a1b3Sopenharmony_ci 87553a5a1b3Sopenharmony_ci u->device_id = pa_xstrdup(pa_modargs_get_value(u->modargs, "device_id", DEFAULT_DEVICE_ID)); 87653a5a1b3Sopenharmony_ci 87753a5a1b3Sopenharmony_ci if ((u->alsa_card_index = snd_card_get_index(u->device_id)) < 0) { 87853a5a1b3Sopenharmony_ci pa_log("Card '%s' doesn't exist: %s", u->device_id, pa_alsa_strerror(u->alsa_card_index)); 87953a5a1b3Sopenharmony_ci goto fail; 88053a5a1b3Sopenharmony_ci } 88153a5a1b3Sopenharmony_ci 88253a5a1b3Sopenharmony_ci#ifdef HAVE_UDEV 88353a5a1b3Sopenharmony_ci udev_args = pa_udev_get_property(u->alsa_card_index, PULSE_MODARGS); 88453a5a1b3Sopenharmony_ci#endif 88553a5a1b3Sopenharmony_ci 88653a5a1b3Sopenharmony_ci if (udev_args) { 88753a5a1b3Sopenharmony_ci bool udev_modargs_success = true; 88853a5a1b3Sopenharmony_ci pa_modargs *temp_ma = pa_modargs_new(udev_args, valid_modargs); 88953a5a1b3Sopenharmony_ci 89053a5a1b3Sopenharmony_ci if (temp_ma) { 89153a5a1b3Sopenharmony_ci /* do not try to replace device_id */ 89253a5a1b3Sopenharmony_ci 89353a5a1b3Sopenharmony_ci if (pa_modargs_remove_key(temp_ma, "device_id") == 0) { 89453a5a1b3Sopenharmony_ci pa_log_warn("Unexpected 'device_id' module argument override ignored from udev " PULSE_MODARGS "='%s'", udev_args); 89553a5a1b3Sopenharmony_ci } 89653a5a1b3Sopenharmony_ci 89753a5a1b3Sopenharmony_ci /* Implement modargs override by copying original module arguments 89853a5a1b3Sopenharmony_ci * over udev entry arguments ignoring duplicates. */ 89953a5a1b3Sopenharmony_ci 90053a5a1b3Sopenharmony_ci if (pa_modargs_merge_missing(temp_ma, u->modargs, valid_modargs) == 0) { 90153a5a1b3Sopenharmony_ci /* swap module arguments */ 90253a5a1b3Sopenharmony_ci pa_modargs *old_ma = u->modargs; 90353a5a1b3Sopenharmony_ci u->modargs = temp_ma; 90453a5a1b3Sopenharmony_ci temp_ma = old_ma; 90553a5a1b3Sopenharmony_ci 90653a5a1b3Sopenharmony_ci pa_log_info("Applied module arguments override from udev " PULSE_MODARGS "='%s'", udev_args); 90753a5a1b3Sopenharmony_ci } else { 90853a5a1b3Sopenharmony_ci pa_log("Failed to apply module arguments override from udev " PULSE_MODARGS "='%s'", udev_args); 90953a5a1b3Sopenharmony_ci udev_modargs_success = false; 91053a5a1b3Sopenharmony_ci } 91153a5a1b3Sopenharmony_ci 91253a5a1b3Sopenharmony_ci pa_modargs_free(temp_ma); 91353a5a1b3Sopenharmony_ci } else { 91453a5a1b3Sopenharmony_ci pa_log("Failed to parse module arguments from udev " PULSE_MODARGS "='%s'", udev_args); 91553a5a1b3Sopenharmony_ci udev_modargs_success = false; 91653a5a1b3Sopenharmony_ci } 91753a5a1b3Sopenharmony_ci pa_xfree(udev_args); 91853a5a1b3Sopenharmony_ci 91953a5a1b3Sopenharmony_ci if (!udev_modargs_success) 92053a5a1b3Sopenharmony_ci goto fail; 92153a5a1b3Sopenharmony_ci } 92253a5a1b3Sopenharmony_ci 92353a5a1b3Sopenharmony_ci if (pa_modargs_get_value_boolean(u->modargs, "ignore_dB", &ignore_dB) < 0) { 92453a5a1b3Sopenharmony_ci pa_log("Failed to parse ignore_dB argument."); 92553a5a1b3Sopenharmony_ci goto fail; 92653a5a1b3Sopenharmony_ci } 92753a5a1b3Sopenharmony_ci 92853a5a1b3Sopenharmony_ci if (!pa_in_system_mode()) { 92953a5a1b3Sopenharmony_ci char *rname; 93053a5a1b3Sopenharmony_ci 93153a5a1b3Sopenharmony_ci if ((rname = pa_alsa_get_reserve_name(u->device_id))) { 93253a5a1b3Sopenharmony_ci reserve = pa_reserve_wrapper_get(m->core, rname); 93353a5a1b3Sopenharmony_ci pa_xfree(rname); 93453a5a1b3Sopenharmony_ci 93553a5a1b3Sopenharmony_ci if (!reserve) 93653a5a1b3Sopenharmony_ci goto fail; 93753a5a1b3Sopenharmony_ci } 93853a5a1b3Sopenharmony_ci } 93953a5a1b3Sopenharmony_ci 94053a5a1b3Sopenharmony_ci if (pa_modargs_get_value_boolean(u->modargs, "use_ucm", &u->use_ucm) < 0) { 94153a5a1b3Sopenharmony_ci pa_log("Failed to parse use_ucm argument."); 94253a5a1b3Sopenharmony_ci goto fail; 94353a5a1b3Sopenharmony_ci } 94453a5a1b3Sopenharmony_ci 94553a5a1b3Sopenharmony_ci /* Force ALSA to reread its configuration. This matters if our device 94653a5a1b3Sopenharmony_ci * was hot-plugged after ALSA has already read its configuration - see 94753a5a1b3Sopenharmony_ci * https://bugs.freedesktop.org/show_bug.cgi?id=54029 94853a5a1b3Sopenharmony_ci */ 94953a5a1b3Sopenharmony_ci 95053a5a1b3Sopenharmony_ci snd_config_update_free_global(); 95153a5a1b3Sopenharmony_ci 95253a5a1b3Sopenharmony_ci rval = u->use_ucm ? pa_alsa_ucm_query_profiles(&u->ucm, u->alsa_card_index) : -1; 95353a5a1b3Sopenharmony_ci if (rval == -PA_ALSA_ERR_UCM_LINKED) { 95453a5a1b3Sopenharmony_ci err = -PA_MODULE_ERR_SKIP; 95553a5a1b3Sopenharmony_ci goto fail; 95653a5a1b3Sopenharmony_ci } 95753a5a1b3Sopenharmony_ci if (rval == 0) { 95853a5a1b3Sopenharmony_ci pa_log_info("Found UCM profiles"); 95953a5a1b3Sopenharmony_ci 96053a5a1b3Sopenharmony_ci u->profile_set = pa_alsa_ucm_add_profile_set(&u->ucm, &u->core->default_channel_map); 96153a5a1b3Sopenharmony_ci 96253a5a1b3Sopenharmony_ci /* hook start of sink input/source output to enable modifiers */ 96353a5a1b3Sopenharmony_ci /* A little bit later than module-role-cork */ 96453a5a1b3Sopenharmony_ci pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_SINK_INPUT_PUT], PA_HOOK_LATE+10, 96553a5a1b3Sopenharmony_ci (pa_hook_cb_t) sink_input_put_hook_callback, u); 96653a5a1b3Sopenharmony_ci pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_PUT], PA_HOOK_LATE+10, 96753a5a1b3Sopenharmony_ci (pa_hook_cb_t) source_output_put_hook_callback, u); 96853a5a1b3Sopenharmony_ci 96953a5a1b3Sopenharmony_ci /* hook end of sink input/source output to disable modifiers */ 97053a5a1b3Sopenharmony_ci /* A little bit later than module-role-cork */ 97153a5a1b3Sopenharmony_ci pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_SINK_INPUT_UNLINK], PA_HOOK_LATE+10, 97253a5a1b3Sopenharmony_ci (pa_hook_cb_t) sink_input_unlink_hook_callback, u); 97353a5a1b3Sopenharmony_ci pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_UNLINK], PA_HOOK_LATE+10, 97453a5a1b3Sopenharmony_ci (pa_hook_cb_t) source_output_unlink_hook_callback, u); 97553a5a1b3Sopenharmony_ci } 97653a5a1b3Sopenharmony_ci else { 97753a5a1b3Sopenharmony_ci u->use_ucm = false; 97853a5a1b3Sopenharmony_ci#ifdef HAVE_UDEV 97953a5a1b3Sopenharmony_ci fn = pa_udev_get_property(u->alsa_card_index, "PULSE_PROFILE_SET"); 98053a5a1b3Sopenharmony_ci#endif 98153a5a1b3Sopenharmony_ci 98253a5a1b3Sopenharmony_ci if (pa_modargs_get_value(u->modargs, "profile_set", NULL)) { 98353a5a1b3Sopenharmony_ci pa_xfree(fn); 98453a5a1b3Sopenharmony_ci fn = pa_xstrdup(pa_modargs_get_value(u->modargs, "profile_set", NULL)); 98553a5a1b3Sopenharmony_ci } 98653a5a1b3Sopenharmony_ci 98753a5a1b3Sopenharmony_ci u->profile_set = pa_alsa_profile_set_new(fn, &u->core->default_channel_map); 98853a5a1b3Sopenharmony_ci pa_xfree(fn); 98953a5a1b3Sopenharmony_ci } 99053a5a1b3Sopenharmony_ci 99153a5a1b3Sopenharmony_ci if (!u->profile_set) 99253a5a1b3Sopenharmony_ci goto fail; 99353a5a1b3Sopenharmony_ci 99453a5a1b3Sopenharmony_ci u->profile_set->ignore_dB = ignore_dB; 99553a5a1b3Sopenharmony_ci 99653a5a1b3Sopenharmony_ci pa_alsa_profile_set_probe(u->profile_set, u->mixers, u->device_id, &m->core->default_sample_spec, m->core->default_n_fragments, m->core->default_fragment_size_msec); 99753a5a1b3Sopenharmony_ci pa_alsa_profile_set_dump(u->profile_set); 99853a5a1b3Sopenharmony_ci 99953a5a1b3Sopenharmony_ci pa_card_new_data_init(&data); 100053a5a1b3Sopenharmony_ci data.driver = __FILE__; 100153a5a1b3Sopenharmony_ci data.module = m; 100253a5a1b3Sopenharmony_ci 100353a5a1b3Sopenharmony_ci pa_alsa_init_proplist_card(m->core, data.proplist, u->alsa_card_index); 100453a5a1b3Sopenharmony_ci 100553a5a1b3Sopenharmony_ci pa_proplist_sets(data.proplist, PA_PROP_DEVICE_STRING, u->device_id); 100653a5a1b3Sopenharmony_ci pa_alsa_init_description(data.proplist, NULL); 100753a5a1b3Sopenharmony_ci set_card_name(&data, u->modargs, u->device_id); 100853a5a1b3Sopenharmony_ci 100953a5a1b3Sopenharmony_ci /* We need to give pa_modargs_get_value_boolean() a pointer to a local 101053a5a1b3Sopenharmony_ci * variable instead of using &data.namereg_fail directly, because 101153a5a1b3Sopenharmony_ci * data.namereg_fail is a bitfield and taking the address of a bitfield 101253a5a1b3Sopenharmony_ci * variable is impossible. */ 101353a5a1b3Sopenharmony_ci namereg_fail = data.namereg_fail; 101453a5a1b3Sopenharmony_ci if (pa_modargs_get_value_boolean(u->modargs, "namereg_fail", &namereg_fail) < 0) { 101553a5a1b3Sopenharmony_ci pa_log("Failed to parse namereg_fail argument."); 101653a5a1b3Sopenharmony_ci pa_card_new_data_done(&data); 101753a5a1b3Sopenharmony_ci goto fail; 101853a5a1b3Sopenharmony_ci } 101953a5a1b3Sopenharmony_ci data.namereg_fail = namereg_fail; 102053a5a1b3Sopenharmony_ci 102153a5a1b3Sopenharmony_ci if (reserve) 102253a5a1b3Sopenharmony_ci if ((description = pa_proplist_gets(data.proplist, PA_PROP_DEVICE_DESCRIPTION))) 102353a5a1b3Sopenharmony_ci pa_reserve_wrapper_set_application_device_name(reserve, description); 102453a5a1b3Sopenharmony_ci 102553a5a1b3Sopenharmony_ci add_profiles(u, data.profiles, data.ports); 102653a5a1b3Sopenharmony_ci 102753a5a1b3Sopenharmony_ci if (pa_hashmap_isempty(data.profiles)) { 102853a5a1b3Sopenharmony_ci pa_log("Failed to find a working profile."); 102953a5a1b3Sopenharmony_ci pa_card_new_data_done(&data); 103053a5a1b3Sopenharmony_ci goto fail; 103153a5a1b3Sopenharmony_ci } 103253a5a1b3Sopenharmony_ci 103353a5a1b3Sopenharmony_ci add_disabled_profile(data.profiles); 103453a5a1b3Sopenharmony_ci prune_singleton_availability_groups(data.ports); 103553a5a1b3Sopenharmony_ci 103653a5a1b3Sopenharmony_ci if (pa_modargs_get_proplist(u->modargs, "card_properties", data.proplist, PA_UPDATE_REPLACE) < 0) { 103753a5a1b3Sopenharmony_ci pa_log("Invalid properties"); 103853a5a1b3Sopenharmony_ci pa_card_new_data_done(&data); 103953a5a1b3Sopenharmony_ci goto fail; 104053a5a1b3Sopenharmony_ci } 104153a5a1b3Sopenharmony_ci 104253a5a1b3Sopenharmony_ci /* The Intel HDMI LPE driver needs some special handling. When the HDMI 104353a5a1b3Sopenharmony_ci * cable is not plugged in, trying to play audio doesn't work. Any written 104453a5a1b3Sopenharmony_ci * audio is immediately discarded and an underrun is reported, and that 104553a5a1b3Sopenharmony_ci * results in an infinite loop of "fill buffer, handle underrun". To work 104653a5a1b3Sopenharmony_ci * around this issue, the suspend_when_unavailable flag is used to stop 104753a5a1b3Sopenharmony_ci * playback when the HDMI cable is unplugged. */ 104853a5a1b3Sopenharmony_ci if (!u->use_ucm && 104953a5a1b3Sopenharmony_ci pa_safe_streq(pa_proplist_gets(data.proplist, "alsa.driver_name"), "snd_hdmi_lpe_audio")) { 105053a5a1b3Sopenharmony_ci pa_device_port *port; 105153a5a1b3Sopenharmony_ci void *state; 105253a5a1b3Sopenharmony_ci 105353a5a1b3Sopenharmony_ci PA_HASHMAP_FOREACH(port, data.ports, state) { 105453a5a1b3Sopenharmony_ci pa_alsa_port_data *port_data; 105553a5a1b3Sopenharmony_ci 105653a5a1b3Sopenharmony_ci port_data = PA_DEVICE_PORT_DATA(port); 105753a5a1b3Sopenharmony_ci port_data->suspend_when_unavailable = true; 105853a5a1b3Sopenharmony_ci } 105953a5a1b3Sopenharmony_ci } 106053a5a1b3Sopenharmony_ci 106153a5a1b3Sopenharmony_ci u->card = pa_card_new(m->core, &data); 106253a5a1b3Sopenharmony_ci pa_card_new_data_done(&data); 106353a5a1b3Sopenharmony_ci 106453a5a1b3Sopenharmony_ci if (!u->card) 106553a5a1b3Sopenharmony_ci goto fail; 106653a5a1b3Sopenharmony_ci 106753a5a1b3Sopenharmony_ci u->card->userdata = u; 106853a5a1b3Sopenharmony_ci u->card->set_profile = card_set_profile; 106953a5a1b3Sopenharmony_ci 107053a5a1b3Sopenharmony_ci pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_CARD_SUSPEND_CHANGED], PA_HOOK_NORMAL, 107153a5a1b3Sopenharmony_ci (pa_hook_cb_t) card_suspend_changed, u); 107253a5a1b3Sopenharmony_ci 107353a5a1b3Sopenharmony_ci init_jacks(u); 107453a5a1b3Sopenharmony_ci 107553a5a1b3Sopenharmony_ci pa_card_choose_initial_profile(u->card); 107653a5a1b3Sopenharmony_ci 107753a5a1b3Sopenharmony_ci /* If the "profile" modarg is given, we have to override whatever the usual 107853a5a1b3Sopenharmony_ci * policy chose in pa_card_choose_initial_profile(). */ 107953a5a1b3Sopenharmony_ci profile_str = pa_modargs_get_value(u->modargs, "profile", NULL); 108053a5a1b3Sopenharmony_ci if (profile_str) { 108153a5a1b3Sopenharmony_ci pa_card_profile *profile; 108253a5a1b3Sopenharmony_ci 108353a5a1b3Sopenharmony_ci profile = pa_hashmap_get(u->card->profiles, profile_str); 108453a5a1b3Sopenharmony_ci if (!profile) { 108553a5a1b3Sopenharmony_ci pa_log("No such profile: %s", profile_str); 108653a5a1b3Sopenharmony_ci goto fail; 108753a5a1b3Sopenharmony_ci } 108853a5a1b3Sopenharmony_ci 108953a5a1b3Sopenharmony_ci pa_card_set_profile(u->card, profile, false); 109053a5a1b3Sopenharmony_ci } 109153a5a1b3Sopenharmony_ci 109253a5a1b3Sopenharmony_ci pa_card_put(u->card); 109353a5a1b3Sopenharmony_ci 109453a5a1b3Sopenharmony_ci init_profile(u); 109553a5a1b3Sopenharmony_ci init_eld_ctls(u); 109653a5a1b3Sopenharmony_ci 109753a5a1b3Sopenharmony_ci /* Remove all probe only mixers */ 109853a5a1b3Sopenharmony_ci if (u->mixers) { 109953a5a1b3Sopenharmony_ci const char *devname; 110053a5a1b3Sopenharmony_ci pa_alsa_mixer *pm; 110153a5a1b3Sopenharmony_ci void *state; 110253a5a1b3Sopenharmony_ci PA_HASHMAP_FOREACH_KV(devname, pm, u->mixers, state) 110353a5a1b3Sopenharmony_ci if (pm->used_for_probe_only) 110453a5a1b3Sopenharmony_ci pa_hashmap_remove_and_free(u->mixers, devname); 110553a5a1b3Sopenharmony_ci } 110653a5a1b3Sopenharmony_ci 110753a5a1b3Sopenharmony_ci if (reserve) 110853a5a1b3Sopenharmony_ci pa_reserve_wrapper_unref(reserve); 110953a5a1b3Sopenharmony_ci 111053a5a1b3Sopenharmony_ci if (!pa_hashmap_isempty(u->profile_set->decibel_fixes)) 111153a5a1b3Sopenharmony_ci pa_log_warn("Card %s uses decibel fixes (i.e. overrides the decibel information for some alsa volume elements). " 111253a5a1b3Sopenharmony_ci "Please note that this feature is meant just as a help for figuring out the correct decibel values. " 111353a5a1b3Sopenharmony_ci "PulseAudio is not the correct place to maintain the decibel mappings! The fixed decibel values " 111453a5a1b3Sopenharmony_ci "should be sent to ALSA developers so that they can fix the driver. If it turns out that this feature " 111553a5a1b3Sopenharmony_ci "is abused (i.e. fixes are not pushed to ALSA), the decibel fix feature may be removed in some future " 111653a5a1b3Sopenharmony_ci "PulseAudio version.", u->card->name); 111753a5a1b3Sopenharmony_ci 111853a5a1b3Sopenharmony_ci return 0; 111953a5a1b3Sopenharmony_ci 112053a5a1b3Sopenharmony_cifail: 112153a5a1b3Sopenharmony_ci if (reserve) 112253a5a1b3Sopenharmony_ci pa_reserve_wrapper_unref(reserve); 112353a5a1b3Sopenharmony_ci 112453a5a1b3Sopenharmony_ci pa__done(m); 112553a5a1b3Sopenharmony_ci 112653a5a1b3Sopenharmony_ci return err; 112753a5a1b3Sopenharmony_ci} 112853a5a1b3Sopenharmony_ci 112953a5a1b3Sopenharmony_ciint pa__get_n_used(pa_module *m) { 113053a5a1b3Sopenharmony_ci struct userdata *u; 113153a5a1b3Sopenharmony_ci int n = 0; 113253a5a1b3Sopenharmony_ci uint32_t idx; 113353a5a1b3Sopenharmony_ci pa_sink *sink; 113453a5a1b3Sopenharmony_ci pa_source *source; 113553a5a1b3Sopenharmony_ci 113653a5a1b3Sopenharmony_ci pa_assert(m); 113753a5a1b3Sopenharmony_ci pa_assert_se(u = m->userdata); 113853a5a1b3Sopenharmony_ci pa_assert(u->card); 113953a5a1b3Sopenharmony_ci 114053a5a1b3Sopenharmony_ci PA_IDXSET_FOREACH(sink, u->card->sinks, idx) 114153a5a1b3Sopenharmony_ci n += pa_sink_linked_by(sink); 114253a5a1b3Sopenharmony_ci 114353a5a1b3Sopenharmony_ci PA_IDXSET_FOREACH(source, u->card->sources, idx) 114453a5a1b3Sopenharmony_ci n += pa_source_linked_by(source); 114553a5a1b3Sopenharmony_ci 114653a5a1b3Sopenharmony_ci return n; 114753a5a1b3Sopenharmony_ci} 114853a5a1b3Sopenharmony_ci 114953a5a1b3Sopenharmony_civoid pa__done(pa_module*m) { 115053a5a1b3Sopenharmony_ci struct userdata *u; 115153a5a1b3Sopenharmony_ci 115253a5a1b3Sopenharmony_ci pa_assert(m); 115353a5a1b3Sopenharmony_ci 115453a5a1b3Sopenharmony_ci if (!(u = m->userdata)) 115553a5a1b3Sopenharmony_ci goto finish; 115653a5a1b3Sopenharmony_ci 115753a5a1b3Sopenharmony_ci if (u->mixers) 115853a5a1b3Sopenharmony_ci pa_hashmap_free(u->mixers); 115953a5a1b3Sopenharmony_ci if (u->jacks) 116053a5a1b3Sopenharmony_ci pa_hashmap_free(u->jacks); 116153a5a1b3Sopenharmony_ci 116253a5a1b3Sopenharmony_ci if (u->card && u->card->sinks) 116353a5a1b3Sopenharmony_ci pa_idxset_remove_all(u->card->sinks, (pa_free_cb_t) pa_alsa_sink_free); 116453a5a1b3Sopenharmony_ci 116553a5a1b3Sopenharmony_ci if (u->card && u->card->sources) 116653a5a1b3Sopenharmony_ci pa_idxset_remove_all(u->card->sources, (pa_free_cb_t) pa_alsa_source_free); 116753a5a1b3Sopenharmony_ci 116853a5a1b3Sopenharmony_ci if (u->card) 116953a5a1b3Sopenharmony_ci pa_card_free(u->card); 117053a5a1b3Sopenharmony_ci 117153a5a1b3Sopenharmony_ci if (u->modargs) 117253a5a1b3Sopenharmony_ci pa_modargs_free(u->modargs); 117353a5a1b3Sopenharmony_ci 117453a5a1b3Sopenharmony_ci if (u->profile_set) 117553a5a1b3Sopenharmony_ci pa_alsa_profile_set_free(u->profile_set); 117653a5a1b3Sopenharmony_ci 117753a5a1b3Sopenharmony_ci pa_alsa_ucm_free(&u->ucm); 117853a5a1b3Sopenharmony_ci 117953a5a1b3Sopenharmony_ci pa_xfree(u->device_id); 118053a5a1b3Sopenharmony_ci pa_xfree(u); 118153a5a1b3Sopenharmony_ci 118253a5a1b3Sopenharmony_cifinish: 118353a5a1b3Sopenharmony_ci pa_alsa_refcnt_dec(); 118453a5a1b3Sopenharmony_ci} 1185