| /* Copyright (c) 2012 The Chromium OS Authors. All rights reserved. |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #include <alsa/asoundlib.h> |
| #include <alsa/use-case.h> |
| #include <ctype.h> |
| #include <string.h> |
| #include <syslog.h> |
| |
| #include "cras_alsa_ucm.h" |
| #include "utlist.h" |
| |
| static const char jack_var[] = "JackName"; |
| static const char jack_type_var[] = "JackType"; |
| static const char jack_switch_var[] = "JackSwitch"; |
| static const char edid_var[] = "EDIDFile"; |
| static const char cap_var[] = "CaptureControl"; |
| static const char mic_positions[] = "MicPositions"; |
| static const char override_type_name_var[] = "OverrideNodeType"; |
| static const char output_dsp_name_var[] = "OutputDspName"; |
| static const char input_dsp_name_var[] = "InputDspName"; |
| static const char mixer_var[] = "MixerName"; |
| static const char swap_mode_suffix[] = "Swap Mode"; |
| static const char min_buffer_level_var[] = "MinBufferLevel"; |
| static const char dma_period_var[] = "DmaPeriodMicrosecs"; |
| static const char disable_software_volume[] = "DisableSoftwareVolume"; |
| static const char playback_device_name_var[] = "PlaybackPCM"; |
| static const char playback_device_rate_var[] = "PlaybackRate"; |
| static const char capture_device_name_var[] = "CapturePCM"; |
| static const char capture_device_rate_var[] = "CaptureRate"; |
| static const char capture_channel_map_var[] = "CaptureChannelMap"; |
| static const char coupled_mixers[] = "CoupledMixers"; |
| /* Set this value in a SectionDevice to specify the maximum software gain in dBm |
| * and enable software gain on this node. */ |
| static const char max_software_gain[] = "MaxSoftwareGain"; |
| /* Set this value in a SectionDevice to specify the default node gain in dBm. */ |
| static const char default_node_gain[] = "DefaultNodeGain"; |
| static const char hotword_model_prefix[] = "Hotword Model"; |
| static const char fully_specified_ucm_var[] = "FullySpecifiedUCM"; |
| static const char main_volume_names[] = "MainVolumeNames"; |
| static const char enable_htimestamp_var[] = "EnableHtimestamp"; |
| |
| /* Use case verbs corresponding to CRAS_STREAM_TYPE. */ |
| static const char *use_case_verbs[] = { |
| "HiFi", |
| "Multimedia", |
| "Voice Call", |
| "Speech", |
| "Pro Audio", |
| }; |
| |
| /* Represents a list of section names found in UCM. */ |
| struct section_name { |
| const char* name; |
| struct section_name *prev, *next; |
| }; |
| |
| struct cras_use_case_mgr { |
| snd_use_case_mgr_t *mgr; |
| const char *name; |
| unsigned int avail_use_cases; |
| enum CRAS_STREAM_TYPE use_case; |
| }; |
| |
| static inline const char *uc_verb(struct cras_use_case_mgr *mgr) |
| { |
| return use_case_verbs[mgr->use_case]; |
| } |
| |
| static int device_enabled(struct cras_use_case_mgr *mgr, const char *dev) |
| { |
| const char **list; |
| unsigned int i; |
| int num_devs; |
| int enabled = 0; |
| |
| num_devs = snd_use_case_get_list(mgr->mgr, "_enadevs", &list); |
| if (num_devs <= 0) |
| return 0; |
| |
| for (i = 0; i < (unsigned int)num_devs; i++) |
| if (!strcmp(dev, list[i])) { |
| enabled = 1; |
| break; |
| } |
| |
| snd_use_case_free_list(list, num_devs); |
| return enabled; |
| } |
| |
| static int modifier_enabled(struct cras_use_case_mgr *mgr, const char *mod) |
| { |
| const char **list; |
| unsigned int mod_idx; |
| int num_mods; |
| |
| num_mods = snd_use_case_get_list(mgr->mgr, "_enamods", &list); |
| if (num_mods <= 0) |
| return 0; |
| |
| for (mod_idx = 0; mod_idx < (unsigned int)num_mods; mod_idx++) |
| if (!strcmp(mod, list[mod_idx])) |
| break; |
| |
| snd_use_case_free_list(list, num_mods); |
| return (mod_idx < (unsigned int)num_mods); |
| } |
| |
| static int get_var(struct cras_use_case_mgr *mgr, const char *var, |
| const char *dev, const char *verb, const char **value) |
| { |
| char *id; |
| int rc; |
| size_t len = strlen(var) + strlen(dev) + strlen(verb) + 4; |
| |
| id = (char *)malloc(len); |
| if (!id) |
| return -ENOMEM; |
| snprintf(id, len, "=%s/%s/%s", var, dev, verb); |
| rc = snd_use_case_get(mgr->mgr, id, value); |
| |
| free((void *)id); |
| return rc; |
| } |
| |
| static int get_int(struct cras_use_case_mgr *mgr, const char *var, |
| const char *dev, const char *verb, int *value) |
| { |
| const char *str_value; |
| int rc; |
| |
| if (!value) |
| return -EINVAL; |
| rc = get_var(mgr, var, dev, verb, &str_value); |
| if (rc != 0) |
| return rc; |
| *value = atoi(str_value); |
| free((void *)str_value); |
| return 0; |
| } |
| |
| static int ucm_set_modifier_enabled(struct cras_use_case_mgr *mgr, |
| const char *mod, int enable) |
| { |
| return snd_use_case_set(mgr->mgr, enable ? "_enamod" : "_dismod", mod); |
| } |
| |
| static int ucm_str_ends_with_suffix(const char *str, const char *suffix) |
| { |
| if (!str || !suffix) |
| return 0; |
| size_t len_str = strlen(str); |
| size_t len_suffix = strlen(suffix); |
| if (len_suffix > len_str) |
| return 0; |
| return strncmp(str + len_str - len_suffix, suffix, len_suffix) == 0; |
| } |
| |
| static int ucm_section_exists_with_name(struct cras_use_case_mgr *mgr, |
| const char *name, const char *identifier) |
| { |
| const char **list; |
| unsigned int i; |
| int num_entries; |
| int exist = 0; |
| |
| num_entries = snd_use_case_get_list(mgr->mgr, identifier, &list); |
| if (num_entries <= 0) |
| return 0; |
| |
| for (i = 0; i < (unsigned int)num_entries; i+=2) { |
| |
| if (!list[i]) |
| continue; |
| |
| if (strcmp(list[i], name) == 0) { |
| exist = 1; |
| break; |
| } |
| } |
| snd_use_case_free_list(list, num_entries); |
| return exist; |
| } |
| |
| static int ucm_section_exists_with_suffix(struct cras_use_case_mgr *mgr, |
| const char *suffix, const char *identifier) |
| { |
| const char **list; |
| unsigned int i; |
| int num_entries; |
| int exist = 0; |
| |
| num_entries = snd_use_case_get_list(mgr->mgr, identifier, &list); |
| if (num_entries <= 0) |
| return 0; |
| |
| for (i = 0; i < (unsigned int)num_entries; i+=2) { |
| |
| if (!list[i]) |
| continue; |
| |
| if (ucm_str_ends_with_suffix(list[i], suffix)) { |
| exist = 1; |
| break; |
| } |
| } |
| snd_use_case_free_list(list, num_entries); |
| return exist; |
| } |
| |
| static int ucm_mod_exists_with_suffix(struct cras_use_case_mgr *mgr, |
| const char *suffix) |
| { |
| char *identifier; |
| int rc; |
| |
| identifier = snd_use_case_identifier("_modifiers/%s", uc_verb(mgr)); |
| rc = ucm_section_exists_with_suffix(mgr, suffix, identifier); |
| free(identifier); |
| return rc; |
| } |
| |
| static int ucm_mod_exists_with_name(struct cras_use_case_mgr *mgr, |
| const char *name) |
| { |
| char *identifier; |
| int rc; |
| |
| identifier = snd_use_case_identifier("_modifiers/%s", uc_verb(mgr)); |
| rc = ucm_section_exists_with_name(mgr, name, identifier); |
| free(identifier); |
| return rc; |
| } |
| |
| /* Get a list of section names whose variable is the matched value. */ |
| static struct section_name * ucm_get_sections_for_var( |
| struct cras_use_case_mgr *mgr, |
| const char *var, const char *value, |
| const char *identifier, |
| enum CRAS_STREAM_DIRECTION direction) |
| { |
| const char **list; |
| struct section_name *section_names = NULL, *s_name; |
| unsigned int i; |
| int num_entries; |
| int rc; |
| |
| num_entries = snd_use_case_get_list(mgr->mgr, identifier, &list); |
| if (num_entries <= 0) |
| return NULL; |
| |
| /* snd_use_case_get_list fills list with pairs of device name and |
| * comment, so device names are in even-indexed elements. */ |
| for (i = 0; i < (unsigned int)num_entries; i+=2) { |
| const char *this_value; |
| |
| if (!list[i]) |
| continue; |
| |
| rc = get_var(mgr, var, list[i], uc_verb(mgr), &this_value); |
| if (rc) |
| continue; |
| |
| if (!strcmp(value, this_value)) { |
| s_name = (struct section_name *)malloc( |
| sizeof(struct section_name)); |
| |
| if (!s_name) { |
| syslog(LOG_ERR, "Failed to allocate memory"); |
| free((void *)this_value); |
| break; |
| } |
| |
| s_name->name = strdup(list[i]); |
| DL_APPEND(section_names, s_name); |
| } |
| free((void *)this_value); |
| } |
| |
| snd_use_case_free_list(list, num_entries); |
| return section_names; |
| } |
| |
| static struct section_name *ucm_get_devices_for_var( |
| struct cras_use_case_mgr *mgr, |
| const char *var, const char *value, |
| enum CRAS_STREAM_DIRECTION dir) |
| { |
| char *identifier; |
| struct section_name *section_names; |
| |
| identifier = snd_use_case_identifier("_devices/%s", uc_verb(mgr)); |
| section_names = ucm_get_sections_for_var(mgr, var, value, identifier, |
| dir); |
| free(identifier); |
| return section_names; |
| } |
| |
| static const char *ucm_get_playback_device_name_for_dev( |
| struct cras_use_case_mgr *mgr, const char *dev) |
| { |
| const char *name = NULL; |
| int rc; |
| |
| rc = get_var(mgr, playback_device_name_var, dev, uc_verb(mgr), &name); |
| if (rc) |
| return NULL; |
| |
| return name; |
| } |
| |
| static const char *ucm_get_capture_device_name_for_dev( |
| struct cras_use_case_mgr *mgr, const char *dev) |
| { |
| const char *name = NULL; |
| int rc; |
| |
| rc = get_var(mgr, capture_device_name_var, dev, uc_verb(mgr), &name); |
| if (rc) |
| return NULL; |
| |
| return name; |
| } |
| |
| /* Get a list of mixer names specified in a UCM variable separated by ",". |
| * E.g. "Left Playback,Right Playback". |
| */ |
| static struct mixer_name *ucm_get_mixer_names(struct cras_use_case_mgr *mgr, |
| const char *dev, const char* var, |
| enum CRAS_STREAM_DIRECTION dir, |
| mixer_name_type type) |
| { |
| const char *names_in_string = NULL; |
| int rc; |
| char *tokens, *name, *laststr; |
| struct mixer_name *names = NULL; |
| |
| rc = get_var(mgr, var, dev, uc_verb(mgr), &names_in_string); |
| if (rc) |
| return NULL; |
| |
| tokens = strdup(names_in_string); |
| name = strtok_r(tokens, ",", &laststr); |
| while (name != NULL) { |
| names = mixer_name_add(names, name, dir, type); |
| name = strtok_r(NULL, ",", &laststr); |
| } |
| free((void*)names_in_string); |
| free(tokens); |
| return names; |
| } |
| |
| /* Exported Interface */ |
| |
| struct cras_use_case_mgr *ucm_create(const char *name) |
| { |
| struct cras_use_case_mgr *mgr; |
| int rc; |
| const char **list; |
| int num_verbs, i, j; |
| |
| if (!name) |
| return NULL; |
| |
| mgr = (struct cras_use_case_mgr *)malloc(sizeof(*mgr)); |
| if (!mgr) |
| return NULL; |
| |
| rc = snd_use_case_mgr_open(&mgr->mgr, name); |
| if (rc) { |
| syslog(LOG_WARNING, "Can not open ucm for card %s, rc = %d", |
| name, rc); |
| goto cleanup; |
| } |
| |
| mgr->name = name; |
| mgr->avail_use_cases = 0; |
| num_verbs = snd_use_case_get_list(mgr->mgr, "_verbs", &list); |
| for (i = 0; i < num_verbs; i += 2) { |
| for (j = 0; j < CRAS_STREAM_NUM_TYPES; ++j) { |
| if (strcmp(list[i], use_case_verbs[j]) == 0) |
| break; |
| } |
| if (j < CRAS_STREAM_NUM_TYPES) |
| mgr->avail_use_cases |= (1 << j); |
| } |
| if (num_verbs > 0) |
| snd_use_case_free_list(list, num_verbs); |
| |
| rc = ucm_set_use_case(mgr, CRAS_STREAM_TYPE_DEFAULT); |
| if (rc) |
| goto cleanup_mgr; |
| |
| return mgr; |
| |
| cleanup_mgr: |
| snd_use_case_mgr_close(mgr->mgr); |
| cleanup: |
| free(mgr); |
| return NULL; |
| } |
| |
| void ucm_destroy(struct cras_use_case_mgr *mgr) |
| { |
| snd_use_case_mgr_close(mgr->mgr); |
| free(mgr); |
| } |
| |
| int ucm_set_use_case(struct cras_use_case_mgr *mgr, |
| enum CRAS_STREAM_TYPE use_case) |
| { |
| int rc; |
| |
| if (mgr->avail_use_cases & (1 << use_case)) { |
| mgr->use_case = use_case; |
| } else { |
| syslog(LOG_ERR, "Unavailable use case %d for card %s", |
| use_case, mgr->name); |
| return -1; |
| } |
| |
| rc = snd_use_case_set(mgr->mgr, "_verb", uc_verb(mgr)); |
| if (rc) { |
| syslog(LOG_ERR, "Can not set verb %s for card %s, rc = %d", |
| uc_verb(mgr), mgr->name, rc); |
| return rc; |
| } |
| |
| return 0; |
| } |
| |
| int ucm_swap_mode_exists(struct cras_use_case_mgr *mgr) |
| { |
| return ucm_mod_exists_with_suffix(mgr, swap_mode_suffix); |
| } |
| |
| int ucm_enable_swap_mode(struct cras_use_case_mgr *mgr, const char *node_name, |
| int enable) |
| { |
| char *swap_mod = NULL; |
| int rc; |
| size_t len = strlen(node_name) + 1 + strlen(swap_mode_suffix) + 1; |
| swap_mod = (char *)malloc(len); |
| if (!swap_mod) |
| return -ENOMEM; |
| snprintf(swap_mod, len, "%s %s", node_name, swap_mode_suffix); |
| if (!ucm_mod_exists_with_name(mgr, swap_mod)) { |
| syslog(LOG_ERR, "Can not find swap mode modifier %s.", swap_mod); |
| free((void *)swap_mod); |
| return -EPERM; |
| } |
| if (modifier_enabled(mgr, swap_mod) == !!enable) { |
| free((void *)swap_mod); |
| return 0; |
| } |
| rc = ucm_set_modifier_enabled(mgr, swap_mod, enable); |
| free((void *)swap_mod); |
| return rc; |
| } |
| |
| int ucm_set_enabled(struct cras_use_case_mgr *mgr, const char *dev, int enable) |
| { |
| if (device_enabled(mgr, dev) == !!enable) |
| return 0; |
| syslog(LOG_DEBUG, "UCM %s %s", enable ? "enable" : "disable", dev); |
| return snd_use_case_set(mgr->mgr, enable ? "_enadev" : "_disdev", dev); |
| } |
| |
| char *ucm_get_flag(struct cras_use_case_mgr *mgr, const char *flag_name) |
| { |
| char *flag_value = NULL; |
| const char *value; |
| int rc; |
| |
| /* Set device to empty string since flag is specified in verb section */ |
| rc = get_var(mgr, flag_name, "", uc_verb(mgr), &value); |
| if (!rc) { |
| flag_value = strdup(value); |
| free((void *)value); |
| } |
| |
| return flag_value; |
| } |
| |
| char *ucm_get_cap_control(struct cras_use_case_mgr *mgr, const char *ucm_dev) |
| { |
| char *control_name = NULL; |
| const char *value; |
| int rc; |
| |
| rc = get_var(mgr, cap_var, ucm_dev, uc_verb(mgr), &value); |
| if (!rc) { |
| control_name = strdup(value); |
| free((void *)value); |
| } |
| |
| return control_name; |
| } |
| |
| char *ucm_get_mic_positions(struct cras_use_case_mgr *mgr) |
| { |
| char *control_name = NULL; |
| const char *value; |
| int rc; |
| |
| rc = get_var(mgr, mic_positions, "", uc_verb(mgr), &value); |
| if (!rc) { |
| control_name = strdup(value); |
| free((void *)value); |
| } |
| |
| return control_name; |
| } |
| |
| const char *ucm_get_override_type_name(struct cras_use_case_mgr *mgr, |
| const char *dev) |
| { |
| const char *override_type_name; |
| int rc; |
| |
| rc = get_var(mgr, override_type_name_var, dev, uc_verb(mgr), |
| &override_type_name); |
| if (rc) |
| return NULL; |
| |
| return override_type_name; |
| } |
| |
| char *ucm_get_dev_for_jack(struct cras_use_case_mgr *mgr, const char *jack, |
| enum CRAS_STREAM_DIRECTION direction) |
| { |
| struct section_name *section_names, *c; |
| char *ret = NULL; |
| |
| section_names = ucm_get_devices_for_var(mgr, jack_var, jack, direction); |
| |
| DL_FOREACH(section_names, c) { |
| if (!strcmp(c->name, "Mic")) { |
| /* Skip mic section for output */ |
| if (direction == CRAS_STREAM_OUTPUT) |
| continue; |
| } else { |
| /* Only check mic for input. */ |
| if (direction == CRAS_STREAM_INPUT) |
| continue; |
| } |
| ret = strdup(c->name); |
| break; |
| } |
| |
| DL_FOREACH(section_names, c) { |
| DL_DELETE(section_names, c); |
| free((void*)c->name); |
| free(c); |
| } |
| |
| return ret; |
| } |
| |
| char *ucm_get_dev_for_mixer(struct cras_use_case_mgr *mgr, const char *mixer, |
| enum CRAS_STREAM_DIRECTION dir) |
| { |
| struct section_name *section_names, *c; |
| char *ret = NULL; |
| |
| section_names = ucm_get_devices_for_var(mgr, mixer_var, mixer, dir); |
| |
| if (section_names) |
| ret = strdup(section_names->name); |
| |
| DL_FOREACH(section_names, c) { |
| DL_DELETE(section_names, c); |
| free((void*)c->name); |
| free(c); |
| } |
| |
| return ret; |
| } |
| |
| const char *ucm_get_edid_file_for_dev(struct cras_use_case_mgr *mgr, |
| const char *dev) |
| { |
| const char *file_name; |
| int rc; |
| |
| rc = get_var(mgr, edid_var, dev, uc_verb(mgr), &file_name); |
| if (rc) |
| return NULL; |
| |
| return file_name; |
| } |
| |
| const char *ucm_get_dsp_name(struct cras_use_case_mgr *mgr, const char *ucm_dev, |
| int direction) |
| { |
| const char *var = (direction == CRAS_STREAM_OUTPUT) |
| ? output_dsp_name_var |
| : input_dsp_name_var; |
| const char *dsp_name = NULL; |
| int rc; |
| |
| rc = get_var(mgr, var, ucm_dev, uc_verb(mgr), &dsp_name); |
| if (rc) |
| return NULL; |
| |
| return dsp_name; |
| } |
| |
| const char *ucm_get_dsp_name_default(struct cras_use_case_mgr *mgr, |
| int direction) |
| { |
| return ucm_get_dsp_name(mgr, "", direction); |
| } |
| |
| unsigned int ucm_get_min_buffer_level(struct cras_use_case_mgr *mgr) |
| { |
| int value; |
| int rc; |
| |
| rc = get_int(mgr, min_buffer_level_var, "", uc_verb(mgr), &value); |
| if (rc) |
| return 0; |
| |
| return value; |
| } |
| |
| unsigned int ucm_get_disable_software_volume(struct cras_use_case_mgr *mgr) |
| { |
| int value; |
| int rc; |
| |
| rc = get_int(mgr, disable_software_volume, "", uc_verb(mgr), &value); |
| if (rc) |
| return 0; |
| |
| return value; |
| } |
| |
| int ucm_get_max_software_gain(struct cras_use_case_mgr *mgr, const char *dev, |
| long *gain) |
| { |
| int value; |
| int rc; |
| |
| rc = get_int(mgr, max_software_gain, dev, uc_verb(mgr), &value); |
| if (rc) |
| return rc; |
| *gain = value; |
| return 0; |
| } |
| |
| int ucm_get_default_node_gain(struct cras_use_case_mgr *mgr, const char *dev, |
| long *gain) |
| { |
| int value; |
| int rc; |
| |
| rc = get_int(mgr, default_node_gain, dev, uc_verb(mgr), &value); |
| if (rc) |
| return rc; |
| *gain = value; |
| return 0; |
| } |
| |
| const char *ucm_get_device_name_for_dev( |
| struct cras_use_case_mgr *mgr, const char *dev, |
| enum CRAS_STREAM_DIRECTION direction) |
| { |
| if (direction == CRAS_STREAM_OUTPUT) |
| return ucm_get_playback_device_name_for_dev(mgr, dev); |
| else if (direction == CRAS_STREAM_INPUT) |
| return ucm_get_capture_device_name_for_dev(mgr, dev); |
| return NULL; |
| } |
| |
| int ucm_get_sample_rate_for_dev(struct cras_use_case_mgr *mgr, const char *dev, |
| enum CRAS_STREAM_DIRECTION direction) |
| { |
| int value; |
| int rc; |
| const char *var_name; |
| |
| if (direction == CRAS_STREAM_OUTPUT) |
| var_name = playback_device_rate_var; |
| else if (direction == CRAS_STREAM_INPUT) |
| var_name = capture_device_rate_var; |
| else |
| return -EINVAL; |
| |
| rc = get_int(mgr, var_name, dev, uc_verb(mgr), &value); |
| if (rc) |
| return rc; |
| |
| return value; |
| } |
| |
| int ucm_get_capture_chmap_for_dev(struct cras_use_case_mgr *mgr, |
| const char *dev, |
| int8_t *channel_layout) |
| { |
| const char *var_str; |
| char *tokens, *token; |
| int i, rc; |
| |
| rc = get_var(mgr, capture_channel_map_var, dev, uc_verb(mgr), &var_str); |
| if (rc) |
| return rc; |
| |
| tokens = strdup(var_str); |
| token = strtok(tokens, " "); |
| for (i = 0; token && (i < CRAS_CH_MAX); i++) { |
| channel_layout[i] = atoi(token); |
| token = strtok(NULL, " "); |
| } |
| |
| free((void *)tokens); |
| free((void *)var_str); |
| return (i == CRAS_CH_MAX) ? 0 : -EINVAL; |
| } |
| |
| struct mixer_name *ucm_get_coupled_mixer_names( |
| struct cras_use_case_mgr *mgr, const char *dev) |
| { |
| return ucm_get_mixer_names(mgr, dev, coupled_mixers, |
| CRAS_STREAM_OUTPUT, |
| MIXER_NAME_VOLUME); |
| } |
| |
| static int get_device_index_from_target(const char *target_device_name) |
| { |
| /* Expects a string in the form: hw:card-name,<num> */ |
| const char *pos = target_device_name; |
| if (!pos) |
| return -1; |
| while (*pos && *pos != ',') |
| ++pos; |
| if (*pos == ',') { |
| ++pos; |
| return atoi(pos); |
| } |
| return -1; |
| } |
| |
| struct ucm_section *ucm_get_sections(struct cras_use_case_mgr *mgr) |
| { |
| struct ucm_section *sections = NULL; |
| struct ucm_section *dev_sec; |
| const char **list; |
| int num_devs; |
| int i; |
| char *identifier; |
| |
| /* Find the list of all mixers using the control names defined in |
| * the header definintion for this function. */ |
| identifier = snd_use_case_identifier("_devices/%s", uc_verb(mgr)); |
| num_devs = snd_use_case_get_list(mgr->mgr, identifier, &list); |
| free(identifier); |
| |
| /* snd_use_case_get_list fills list with pairs of device name and |
| * comment, so device names are in even-indexed elements. */ |
| for (i = 0; i < num_devs; i += 2) { |
| enum CRAS_STREAM_DIRECTION dir = CRAS_STREAM_UNDEFINED; |
| int dev_idx = -1; |
| const char *dev_name = strdup(list[i]); |
| const char *jack_name; |
| const char *jack_type; |
| const char *mixer_name; |
| struct mixer_name *m_name; |
| int rc; |
| const char *target_device_name; |
| |
| if (!dev_name) |
| continue; |
| |
| target_device_name = |
| ucm_get_playback_device_name_for_dev(mgr, dev_name); |
| if (target_device_name) |
| dir = CRAS_STREAM_OUTPUT; |
| else { |
| target_device_name = |
| ucm_get_capture_device_name_for_dev( |
| mgr, dev_name); |
| if (target_device_name) |
| dir = CRAS_STREAM_INPUT; |
| } |
| if (target_device_name) { |
| dev_idx = get_device_index_from_target( |
| target_device_name); |
| free((void *)target_device_name); |
| } |
| |
| if (dir == CRAS_STREAM_UNDEFINED) { |
| syslog(LOG_ERR, |
| "UCM configuration for device '%s' missing" |
| " PlaybackPCM or CapturePCM definition.", |
| dev_name); |
| goto error_cleanup; |
| } |
| |
| if (dev_idx == -1) { |
| syslog(LOG_ERR, |
| "PlaybackPCM or CapturePCM for '%s' must be in" |
| " the form 'hw:<card>,<number>'", dev_name); |
| goto error_cleanup; |
| } |
| |
| jack_name = ucm_get_jack_name_for_dev(mgr, dev_name); |
| jack_type = ucm_get_jack_type_for_dev(mgr, dev_name); |
| mixer_name = ucm_get_mixer_name_for_dev(mgr, dev_name); |
| |
| dev_sec = ucm_section_create(dev_name, dev_idx, dir, |
| jack_name, jack_type); |
| if (jack_name) |
| free((void *)jack_name); |
| if (jack_type) |
| free((void *)jack_type); |
| |
| if (!dev_sec) { |
| syslog(LOG_ERR, "Failed to allocate memory."); |
| if (mixer_name) |
| free((void *)mixer_name); |
| goto error_cleanup; |
| } |
| |
| dev_sec->jack_switch = |
| ucm_get_jack_switch_for_dev(mgr, dev_name); |
| |
| if (mixer_name) { |
| rc = ucm_section_set_mixer_name(dev_sec, mixer_name); |
| free((void *)mixer_name); |
| if (rc) |
| goto error_cleanup; |
| } |
| |
| m_name = ucm_get_mixer_names(mgr, dev_name, coupled_mixers, |
| dir, MIXER_NAME_VOLUME); |
| ucm_section_concat_coupled(dev_sec, m_name); |
| |
| DL_APPEND(sections, dev_sec); |
| ucm_section_dump(dev_sec); |
| } |
| |
| if (num_devs > 0) |
| snd_use_case_free_list(list, num_devs); |
| return sections; |
| |
| error_cleanup: |
| if (num_devs > 0) |
| snd_use_case_free_list(list, num_devs); |
| ucm_section_free_list(sections); |
| return NULL; |
| } |
| |
| char *ucm_get_hotword_models(struct cras_use_case_mgr *mgr) |
| { |
| const char **list; |
| int i, num_entries; |
| int models_len = 0; |
| char *models = NULL; |
| const char *tmp; |
| char *identifier; |
| |
| identifier = snd_use_case_identifier("_modifiers/%s", uc_verb(mgr)); |
| num_entries = snd_use_case_get_list(mgr->mgr, identifier, &list); |
| free(identifier); |
| if (num_entries <= 0) |
| return 0; |
| models = (char *)malloc(num_entries * 8); |
| for (i = 0; i < num_entries; i+=2) { |
| if (!list[i]) |
| continue; |
| if (0 == strncmp(list[i], hotword_model_prefix, |
| strlen(hotword_model_prefix))) { |
| tmp = list[i] + strlen(hotword_model_prefix); |
| while (isspace(*tmp)) |
| tmp++; |
| strcpy(models + models_len, tmp); |
| models_len += strlen(tmp); |
| if (i + 2 >= num_entries) |
| models[models_len] = '\0'; |
| else |
| models[models_len++] = ','; |
| } |
| } |
| snd_use_case_free_list(list, num_entries); |
| |
| return models; |
| } |
| |
| int ucm_set_hotword_model(struct cras_use_case_mgr *mgr, const char *model) |
| { |
| const char **list; |
| int num_enmods, mod_idx; |
| char *model_mod = NULL; |
| size_t model_mod_size = strlen(model) + 1 + |
| strlen(hotword_model_prefix) + 1; |
| model_mod = (char *)malloc(model_mod_size); |
| if (!model_mod) |
| return -ENOMEM; |
| snprintf(model_mod, model_mod_size, |
| "%s %s", hotword_model_prefix, model); |
| if (!ucm_mod_exists_with_name(mgr, model_mod)) { |
| free((void *)model_mod); |
| return -EINVAL; |
| } |
| |
| /* Disable all currently enabled horword model modifiers. */ |
| num_enmods = snd_use_case_get_list(mgr->mgr, "_enamods", &list); |
| if (num_enmods <= 0) |
| goto enable_mod; |
| |
| for (mod_idx = 0; mod_idx < num_enmods; mod_idx++) { |
| if (!strncmp(list[mod_idx], hotword_model_prefix, |
| strlen(hotword_model_prefix))) |
| ucm_set_modifier_enabled(mgr, list[mod_idx], 0); |
| } |
| snd_use_case_free_list(list, num_enmods); |
| |
| enable_mod: |
| ucm_set_modifier_enabled(mgr, model_mod, 1); |
| |
| return 0; |
| } |
| |
| int ucm_has_fully_specified_ucm_flag(struct cras_use_case_mgr *mgr) |
| { |
| char *flag; |
| int ret = 0; |
| flag = ucm_get_flag(mgr, fully_specified_ucm_var); |
| if (!flag) |
| return 0; |
| ret = !strcmp(flag, "1"); |
| free(flag); |
| return ret; |
| } |
| |
| const char *ucm_get_mixer_name_for_dev(struct cras_use_case_mgr *mgr, const char *dev) |
| { |
| const char *name = NULL; |
| int rc; |
| |
| rc = get_var(mgr, mixer_var, dev, uc_verb(mgr), &name); |
| if (rc) |
| return NULL; |
| |
| return name; |
| } |
| |
| struct mixer_name *ucm_get_main_volume_names(struct cras_use_case_mgr *mgr) |
| { |
| return ucm_get_mixer_names(mgr, "", main_volume_names, |
| CRAS_STREAM_OUTPUT, MIXER_NAME_MAIN_VOLUME); |
| } |
| |
| int ucm_list_section_devices_by_device_name( |
| struct cras_use_case_mgr *mgr, |
| enum CRAS_STREAM_DIRECTION direction, |
| const char *device_name, |
| ucm_list_section_devices_callback cb, |
| void *cb_arg) |
| { |
| int listed= 0; |
| struct section_name *section_names, *c; |
| const char* var; |
| char *identifier; |
| |
| if (direction == CRAS_STREAM_OUTPUT) |
| var = playback_device_name_var; |
| else if (direction == CRAS_STREAM_INPUT) |
| var = capture_device_name_var; |
| else |
| return 0; |
| |
| identifier = snd_use_case_identifier("_devices/%s", uc_verb(mgr)); |
| section_names = ucm_get_sections_for_var( |
| mgr, var, device_name, identifier, direction); |
| free(identifier); |
| if (!section_names) |
| return 0; |
| |
| DL_FOREACH(section_names, c) { |
| cb(c->name, cb_arg); |
| listed++; |
| } |
| |
| DL_FOREACH(section_names, c) { |
| DL_DELETE(section_names, c); |
| free((void*)c->name); |
| free(c); |
| } |
| return listed; |
| } |
| |
| const char *ucm_get_jack_name_for_dev(struct cras_use_case_mgr *mgr, |
| const char *dev) |
| { |
| const char *name = NULL; |
| int rc; |
| |
| rc = get_var(mgr, jack_var, dev, uc_verb(mgr), &name); |
| if (rc) |
| return NULL; |
| |
| return name; |
| } |
| |
| const char *ucm_get_jack_type_for_dev(struct cras_use_case_mgr *mgr, |
| const char *dev) |
| { |
| const char *name = NULL; |
| int rc; |
| |
| rc = get_var(mgr, jack_type_var, dev, uc_verb(mgr), &name); |
| if (rc) |
| return NULL; |
| |
| if (strcmp(name, "hctl") && strcmp(name, "gpio")) { |
| syslog(LOG_ERR, "Unknown jack type: %s", name); |
| return NULL; |
| } |
| return name; |
| } |
| |
| int ucm_get_jack_switch_for_dev(struct cras_use_case_mgr *mgr, const char *dev) |
| { |
| int value; |
| |
| int rc = get_int(mgr, jack_switch_var, dev, uc_verb(mgr), &value); |
| if (rc || value < 0) |
| return -1; |
| return value; |
| } |
| |
| unsigned int ucm_get_dma_period_for_dev(struct cras_use_case_mgr *mgr, |
| const char *dev) |
| { |
| int value; |
| |
| int rc = get_int(mgr, dma_period_var, dev, uc_verb(mgr), &value); |
| if (rc || value < 0) |
| return 0; |
| return value; |
| } |
| |
| unsigned int ucm_get_enable_htimestamp_flag(struct cras_use_case_mgr *mgr) |
| { |
| char *flag; |
| int ret = 0; |
| flag = ucm_get_flag(mgr, enable_htimestamp_var); |
| if (!flag) |
| return 0; |
| ret = !strcmp(flag, "1"); |
| free(flag); |
| return ret; |
| } |