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