| /* |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Lesser General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public |
| * License along with this library; if not, write to the Free Software |
| * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| * |
| * Support for the verb/device/modifier core logic and API, |
| * command line tool and file parser was kindly sponsored by |
| * Texas Instruments Inc. |
| * Support for multiple active modifiers and devices, |
| * transition sequences, multiple client access and user defined use |
| * cases was kindly sponsored by Wolfson Microelectronics PLC. |
| * |
| * Copyright (C) 2008-2010 SlimLogic Ltd |
| * Copyright (C) 2010 Wolfson Microelectronics PLC |
| * Copyright (C) 2010 Texas Instruments Inc. |
| * Copyright (C) 2010 Red Hat Inc. |
| * Authors: Liam Girdwood <lrg@slimlogic.co.uk> |
| * Stefan Schmidt <stefan@slimlogic.co.uk> |
| * Justin Xu <justinx@slimlogic.co.uk> |
| * Jaroslav Kysela <perex@perex.cz> |
| */ |
| |
| #include "ucm_local.h" |
| #include <dirent.h> |
| |
| /** The name of the environment variable containing the UCM directory */ |
| #define ALSA_CONFIG_UCM_VAR "ALSA_CONFIG_UCM" |
| |
| static int parse_sequence(snd_use_case_mgr_t *uc_mgr, |
| struct list_head *base, |
| snd_config_t *cfg); |
| |
| /* |
| * Parse string |
| */ |
| int parse_string(snd_config_t *n, char **res) |
| { |
| int err; |
| |
| err = snd_config_get_string(n, (const char **)res); |
| if (err < 0) |
| return err; |
| *res = strdup(*res); |
| if (*res == NULL) |
| return -ENOMEM; |
| return 0; |
| } |
| |
| /* |
| * Parse safe ID |
| */ |
| int parse_is_name_safe(const char *name) |
| { |
| if (strchr(name, '.')) { |
| uc_error("char '.' not allowed in '%s'", name); |
| return 0; |
| } |
| return 1; |
| } |
| |
| int parse_get_safe_id(snd_config_t *n, const char **id) |
| { |
| int err; |
| |
| err = snd_config_get_id(n, id); |
| if (err < 0) |
| return err; |
| if (!parse_is_name_safe((char *)(*id))) |
| return -EINVAL; |
| return 0; |
| } |
| |
| /* |
| * Parse transition |
| */ |
| static int parse_transition(snd_use_case_mgr_t *uc_mgr, |
| struct list_head *tlist, |
| snd_config_t *cfg) |
| { |
| struct transition_sequence *tseq; |
| const char *id; |
| snd_config_iterator_t i, next; |
| snd_config_t *n; |
| int err; |
| |
| if (snd_config_get_id(cfg, &id) < 0) |
| return -EINVAL; |
| |
| if (snd_config_get_type(cfg) != SND_CONFIG_TYPE_COMPOUND) { |
| uc_error("compound type expected for %s", id); |
| return -EINVAL; |
| } |
| |
| snd_config_for_each(i, next, cfg) { |
| n = snd_config_iterator_entry(i); |
| |
| if (snd_config_get_id(n, &id) < 0) |
| return -EINVAL; |
| |
| tseq = calloc(1, sizeof(*tseq)); |
| if (tseq == NULL) |
| return -ENOMEM; |
| INIT_LIST_HEAD(&tseq->transition_list); |
| |
| tseq->name = strdup(id); |
| if (tseq->name == NULL) { |
| free(tseq); |
| return -ENOMEM; |
| } |
| |
| err = parse_sequence(uc_mgr, &tseq->transition_list, n); |
| if (err < 0) { |
| uc_mgr_free_transition_element(tseq); |
| return err; |
| } |
| |
| list_add(&tseq->list, tlist); |
| } |
| return 0; |
| } |
| |
| /* |
| * Parse compound |
| */ |
| static int parse_compound(snd_use_case_mgr_t *uc_mgr, snd_config_t *cfg, |
| int (*fcn)(snd_use_case_mgr_t *, snd_config_t *, void *, void *), |
| void *data1, void *data2) |
| { |
| const char *id; |
| snd_config_iterator_t i, next; |
| snd_config_t *n; |
| int err; |
| |
| if (snd_config_get_id(cfg, &id) < 0) |
| return -EINVAL; |
| |
| if (snd_config_get_type(cfg) != SND_CONFIG_TYPE_COMPOUND) { |
| uc_error("compound type expected for %s", id); |
| return -EINVAL; |
| } |
| /* parse compound */ |
| snd_config_for_each(i, next, cfg) { |
| n = snd_config_iterator_entry(i); |
| |
| if (snd_config_get_type(cfg) != SND_CONFIG_TYPE_COMPOUND) { |
| uc_error("compound type expected for %s, is %d", id, snd_config_get_type(cfg)); |
| return -EINVAL; |
| } |
| |
| err = fcn(uc_mgr, n, data1, data2); |
| if (err < 0) |
| return err; |
| } |
| |
| return 0; |
| } |
| |
| static int strip_legacy_dev_index(char *name) |
| { |
| char *dot = strchr(name, '.'); |
| if (!dot) |
| return 0; |
| if (dot[1] != '0' || dot[2] != '\0') { |
| uc_error("device name %s contains a '.'," |
| " and is not legacy foo.0 format", name); |
| return -EINVAL; |
| } |
| *dot = '\0'; |
| return 0; |
| } |
| |
| /* |
| * Parse device list |
| */ |
| static int parse_device_list(snd_use_case_mgr_t *uc_mgr ATTRIBUTE_UNUSED, |
| struct dev_list *dev_list, |
| enum dev_list_type type, |
| snd_config_t *cfg) |
| { |
| struct dev_list_node *sdev; |
| const char *id; |
| snd_config_iterator_t i, next; |
| snd_config_t *n; |
| int err; |
| |
| if (dev_list->type != DEVLIST_NONE) { |
| uc_error("error: multiple supported or" |
| " conflicting device lists"); |
| return -EEXIST; |
| } |
| |
| if (snd_config_get_id(cfg, &id) < 0) |
| return -EINVAL; |
| |
| if (snd_config_get_type(cfg) != SND_CONFIG_TYPE_COMPOUND) { |
| uc_error("compound type expected for %s", id); |
| return -EINVAL; |
| } |
| |
| snd_config_for_each(i, next, cfg) { |
| n = snd_config_iterator_entry(i); |
| |
| if (snd_config_get_id(n, &id) < 0) |
| return -EINVAL; |
| |
| sdev = calloc(1, sizeof(struct dev_list_node)); |
| if (sdev == NULL) |
| return -ENOMEM; |
| err = parse_string(n, &sdev->name); |
| if (err < 0) { |
| free(sdev); |
| return err; |
| } |
| err = strip_legacy_dev_index(sdev->name); |
| if (err < 0) { |
| free(sdev->name); |
| free(sdev); |
| return err; |
| } |
| list_add(&sdev->list, &dev_list->list); |
| } |
| |
| dev_list->type = type; |
| |
| return 0; |
| } |
| |
| /* |
| * Parse sequences. |
| * |
| * Sequence controls elements are in the following form:- |
| * |
| * cdev "hw:0" |
| * cset "element_id_syntax value_syntax" |
| * usleep time |
| * exec "any unix command with arguments" |
| * |
| * e.g. |
| * cset "name='Master Playback Switch' 0,0" |
| * cset "iface=PCM,name='Disable HDMI',index=1 0" |
| */ |
| static int parse_sequence(snd_use_case_mgr_t *uc_mgr ATTRIBUTE_UNUSED, |
| struct list_head *base, |
| snd_config_t *cfg) |
| { |
| struct sequence_element *curr; |
| snd_config_iterator_t i, next; |
| snd_config_t *n; |
| int err, idx = 0; |
| const char *cmd = NULL; |
| |
| if (snd_config_get_type(cfg) != SND_CONFIG_TYPE_COMPOUND) { |
| uc_error("error: compound is expected for sequence definition"); |
| return -EINVAL; |
| } |
| |
| snd_config_for_each(i, next, cfg) { |
| const char *id; |
| idx ^= 1; |
| n = snd_config_iterator_entry(i); |
| err = snd_config_get_id(n, &id); |
| if (err < 0) |
| continue; |
| if (idx == 1) { |
| if (snd_config_get_type(n) != SND_CONFIG_TYPE_STRING) { |
| uc_error("error: string type is expected for sequence command"); |
| return -EINVAL; |
| } |
| snd_config_get_string(n, &cmd); |
| continue; |
| } |
| |
| /* alloc new sequence element */ |
| curr = calloc(1, sizeof(struct sequence_element)); |
| if (curr == NULL) |
| return -ENOMEM; |
| list_add_tail(&curr->list, base); |
| |
| if (strcmp(cmd, "cdev") == 0) { |
| curr->type = SEQUENCE_ELEMENT_TYPE_CDEV; |
| err = parse_string(n, &curr->data.cdev); |
| if (err < 0) { |
| uc_error("error: cdev requires a string!"); |
| return err; |
| } |
| continue; |
| } |
| |
| if (strcmp(cmd, "cset") == 0) { |
| curr->type = SEQUENCE_ELEMENT_TYPE_CSET; |
| err = parse_string(n, &curr->data.cset); |
| if (err < 0) { |
| uc_error("error: cset requires a string!"); |
| return err; |
| } |
| continue; |
| } |
| |
| if (strcmp(cmd, "cset-bin-file") == 0) { |
| curr->type = SEQUENCE_ELEMENT_TYPE_CSET_BIN_FILE; |
| err = parse_string(n, &curr->data.cset); |
| if (err < 0) { |
| uc_error("error: cset-bin-file requires a string!"); |
| return err; |
| } |
| continue; |
| } |
| |
| if (strcmp(cmd, "usleep") == 0) { |
| curr->type = SEQUENCE_ELEMENT_TYPE_SLEEP; |
| err = snd_config_get_integer(n, &curr->data.sleep); |
| if (err < 0) { |
| uc_error("error: usleep requires integer!"); |
| return err; |
| } |
| continue; |
| } |
| |
| if (strcmp(cmd, "msleep") == 0) { |
| curr->type = SEQUENCE_ELEMENT_TYPE_SLEEP; |
| err = snd_config_get_integer(n, &curr->data.sleep); |
| if (err < 0) { |
| uc_error("error: msleep requires integer!"); |
| return err; |
| } |
| curr->data.sleep *= 1000L; |
| continue; |
| } |
| |
| if (strcmp(cmd, "exec") == 0) { |
| curr->type = SEQUENCE_ELEMENT_TYPE_EXEC; |
| err = parse_string(n, &curr->data.exec); |
| if (err < 0) { |
| uc_error("error: exec requires a string!"); |
| return err; |
| } |
| continue; |
| } |
| |
| list_del(&curr->list); |
| uc_mgr_free_sequence_element(curr); |
| } |
| |
| return 0; |
| } |
| |
| /* |
| * Parse values. |
| * |
| * Parse values describing PCM, control/mixer settings and stream parameters. |
| * |
| * Value { |
| * TQ Voice |
| * CapturePCM "hw:1" |
| * PlaybackVolume "name='Master Playback Volume',index=2" |
| * PlaybackSwitch "name='Master Playback Switch',index=2" |
| * } |
| */ |
| static int parse_value(snd_use_case_mgr_t *uc_mgr ATTRIBUTE_UNUSED, |
| struct list_head *base, |
| snd_config_t *cfg) |
| { |
| struct ucm_value *curr; |
| snd_config_iterator_t i, next; |
| snd_config_t *n; |
| long l; |
| long long ll; |
| double d; |
| snd_config_type_t type; |
| int err; |
| |
| if (snd_config_get_type(cfg) != SND_CONFIG_TYPE_COMPOUND) { |
| uc_error("error: compound is expected for value definition"); |
| return -EINVAL; |
| } |
| snd_config_for_each(i, next, cfg) { |
| const char *id; |
| n = snd_config_iterator_entry(i); |
| err = snd_config_get_id(n, &id); |
| if (err < 0) |
| continue; |
| |
| /* alloc new value */ |
| curr = calloc(1, sizeof(struct ucm_value)); |
| if (curr == NULL) |
| return -ENOMEM; |
| list_add_tail(&curr->list, base); |
| curr->name = strdup(id); |
| if (curr->name == NULL) |
| return -ENOMEM; |
| type = snd_config_get_type(n); |
| switch (type) { |
| case SND_CONFIG_TYPE_INTEGER: |
| curr->data = malloc(16); |
| if (curr->data == NULL) |
| return -ENOMEM; |
| snd_config_get_integer(n, &l); |
| sprintf(curr->data, "%li", l); |
| break; |
| case SND_CONFIG_TYPE_INTEGER64: |
| curr->data = malloc(32); |
| if (curr->data == NULL) |
| return -ENOMEM; |
| snd_config_get_integer64(n, &ll); |
| sprintf(curr->data, "%lli", ll); |
| break; |
| case SND_CONFIG_TYPE_REAL: |
| curr->data = malloc(64); |
| if (curr->data == NULL) |
| return -ENOMEM; |
| snd_config_get_real(n, &d); |
| sprintf(curr->data, "%-16g", d); |
| break; |
| case SND_CONFIG_TYPE_STRING: |
| err = parse_string(n, &curr->data); |
| if (err < 0) { |
| uc_error("error: unable to parse a string for id '%s'!", id); |
| return err; |
| } |
| break; |
| default: |
| uc_error("error: invalid type %i in Value compound", type); |
| return -EINVAL; |
| } |
| } |
| |
| return 0; |
| } |
| |
| /* |
| * Parse Modifier Use cases |
| * |
| * # Each modifier is described in new section. N modifiers are allowed |
| * SectionModifier."Capture Voice" { |
| * |
| * Comment "Record voice call" |
| * |
| * SupportedDevice [ |
| * "x" |
| * "y" |
| * ] |
| * |
| * ConflictingDevice [ |
| * "x" |
| * "y" |
| * ] |
| * |
| * EnableSequence [ |
| * .... |
| * ] |
| * |
| * DisableSequence [ |
| * ... |
| * ] |
| * |
| * TransitionSequence."ToModifierName" [ |
| * ... |
| * ] |
| * |
| * # Optional TQ and ALSA PCMs |
| * Value { |
| * TQ Voice |
| * CapturePCM "hw:1" |
| * PlaybackVolume "name='Master Playback Volume',index=2" |
| * PlaybackSwitch "name='Master Playback Switch',index=2" |
| * } |
| * |
| * } |
| * |
| * SupportedDevice and ConflictingDevice cannot be specified together. |
| * Both are optional. |
| */ |
| static int parse_modifier(snd_use_case_mgr_t *uc_mgr, |
| snd_config_t *cfg, |
| void *data1, |
| void *data2) |
| { |
| struct use_case_verb *verb = data1; |
| struct use_case_modifier *modifier; |
| const char *name; |
| snd_config_iterator_t i, next; |
| snd_config_t *n; |
| int err; |
| |
| if (data2) { |
| name = data2; |
| if (!parse_is_name_safe(name)) |
| return -EINVAL; |
| } |
| else { |
| if (parse_get_safe_id(cfg, &name) < 0) |
| return -EINVAL; |
| } |
| |
| /* allocate modifier */ |
| modifier = calloc(1, sizeof(*modifier)); |
| if (modifier == NULL) |
| return -ENOMEM; |
| INIT_LIST_HEAD(&modifier->enable_list); |
| INIT_LIST_HEAD(&modifier->disable_list); |
| INIT_LIST_HEAD(&modifier->transition_list); |
| INIT_LIST_HEAD(&modifier->dev_list.list); |
| INIT_LIST_HEAD(&modifier->value_list); |
| list_add_tail(&modifier->list, &verb->modifier_list); |
| modifier->name = strdup(name); |
| |
| snd_config_for_each(i, next, cfg) { |
| const char *id; |
| n = snd_config_iterator_entry(i); |
| if (snd_config_get_id(n, &id) < 0) |
| continue; |
| |
| if (strcmp(id, "Comment") == 0) { |
| err = parse_string(n, &modifier->comment); |
| if (err < 0) { |
| uc_error("error: failed to get modifier comment"); |
| return err; |
| } |
| continue; |
| } |
| |
| if (strcmp(id, "SupportedDevice") == 0) { |
| err = parse_device_list(uc_mgr, &modifier->dev_list, |
| DEVLIST_SUPPORTED, n); |
| if (err < 0) { |
| uc_error("error: failed to parse supported" |
| " device list"); |
| return err; |
| } |
| } |
| |
| if (strcmp(id, "ConflictingDevice") == 0) { |
| err = parse_device_list(uc_mgr, &modifier->dev_list, |
| DEVLIST_CONFLICTING, n); |
| if (err < 0) { |
| uc_error("error: failed to parse conflicting" |
| " device list"); |
| return err; |
| } |
| } |
| |
| if (strcmp(id, "EnableSequence") == 0) { |
| err = parse_sequence(uc_mgr, &modifier->enable_list, n); |
| if (err < 0) { |
| uc_error("error: failed to parse modifier" |
| " enable sequence"); |
| return err; |
| } |
| continue; |
| } |
| |
| if (strcmp(id, "DisableSequence") == 0) { |
| err = parse_sequence(uc_mgr, &modifier->disable_list, n); |
| if (err < 0) { |
| uc_error("error: failed to parse modifier" |
| " disable sequence"); |
| return err; |
| } |
| continue; |
| } |
| |
| if (strcmp(id, "TransitionSequence") == 0) { |
| err = parse_transition(uc_mgr, &modifier->transition_list, n); |
| if (err < 0) { |
| uc_error("error: failed to parse transition" |
| " modifier"); |
| return err; |
| } |
| continue; |
| } |
| |
| if (strcmp(id, "Value") == 0) { |
| err = parse_value(uc_mgr, &modifier->value_list, n); |
| if (err < 0) { |
| uc_error("error: failed to parse Value"); |
| return err; |
| } |
| continue; |
| } |
| } |
| |
| return 0; |
| } |
| |
| /* |
| * Parse Device Use Cases |
| * |
| *# Each device is described in new section. N devices are allowed |
| *SectionDevice."Headphones" { |
| * Comment "Headphones connected to 3.5mm jack" |
| * |
| * upportedDevice [ |
| * "x" |
| * "y" |
| * ] |
| * |
| * ConflictingDevice [ |
| * "x" |
| * "y" |
| * ] |
| * |
| * EnableSequence [ |
| * .... |
| * ] |
| * |
| * DisableSequence [ |
| * ... |
| * ] |
| * |
| * TransitionSequence."ToDevice" [ |
| * ... |
| * ] |
| * |
| * Value { |
| * PlaybackVolume "name='Master Playback Volume',index=2" |
| * PlaybackSwitch "name='Master Playback Switch',index=2" |
| * } |
| * } |
| */ |
| static int parse_device(snd_use_case_mgr_t *uc_mgr, |
| snd_config_t *cfg, |
| void *data1, |
| void *data2) |
| { |
| struct use_case_verb *verb = data1; |
| const char *name; |
| struct use_case_device *device; |
| snd_config_iterator_t i, next; |
| snd_config_t *n; |
| int err; |
| |
| if (data2) { |
| name = data2; |
| if (!parse_is_name_safe(name)) |
| return -EINVAL; |
| } |
| else { |
| if (parse_get_safe_id(cfg, &name) < 0) |
| return -EINVAL; |
| } |
| |
| device = calloc(1, sizeof(*device)); |
| if (device == NULL) |
| return -ENOMEM; |
| INIT_LIST_HEAD(&device->enable_list); |
| INIT_LIST_HEAD(&device->disable_list); |
| INIT_LIST_HEAD(&device->transition_list); |
| INIT_LIST_HEAD(&device->dev_list.list); |
| INIT_LIST_HEAD(&device->value_list); |
| list_add_tail(&device->list, &verb->device_list); |
| device->name = strdup(name); |
| |
| snd_config_for_each(i, next, cfg) { |
| const char *id; |
| n = snd_config_iterator_entry(i); |
| if (snd_config_get_id(n, &id) < 0) |
| continue; |
| |
| if (strcmp(id, "Comment") == 0) { |
| err = parse_string(n, &device->comment); |
| if (err < 0) { |
| uc_error("error: failed to get device comment"); |
| return err; |
| } |
| continue; |
| } |
| |
| if (strcmp(id, "SupportedDevice") == 0) { |
| err = parse_device_list(uc_mgr, &device->dev_list, |
| DEVLIST_SUPPORTED, n); |
| if (err < 0) { |
| uc_error("error: failed to parse supported" |
| " device list"); |
| return err; |
| } |
| } |
| |
| if (strcmp(id, "ConflictingDevice") == 0) { |
| err = parse_device_list(uc_mgr, &device->dev_list, |
| DEVLIST_CONFLICTING, n); |
| if (err < 0) { |
| uc_error("error: failed to parse conflicting" |
| " device list"); |
| return err; |
| } |
| } |
| |
| if (strcmp(id, "EnableSequence") == 0) { |
| uc_dbg("EnableSequence"); |
| err = parse_sequence(uc_mgr, &device->enable_list, n); |
| if (err < 0) { |
| uc_error("error: failed to parse device enable" |
| " sequence"); |
| return err; |
| } |
| continue; |
| } |
| |
| if (strcmp(id, "DisableSequence") == 0) { |
| uc_dbg("DisableSequence"); |
| err = parse_sequence(uc_mgr, &device->disable_list, n); |
| if (err < 0) { |
| uc_error("error: failed to parse device disable" |
| " sequence"); |
| return err; |
| } |
| continue; |
| } |
| |
| if (strcmp(id, "TransitionSequence") == 0) { |
| uc_dbg("TransitionSequence"); |
| err = parse_transition(uc_mgr, &device->transition_list, n); |
| if (err < 0) { |
| uc_error("error: failed to parse transition" |
| " device"); |
| return err; |
| } |
| continue; |
| } |
| |
| if (strcmp(id, "Value") == 0) { |
| err = parse_value(uc_mgr, &device->value_list, n); |
| if (err < 0) { |
| uc_error("error: failed to parse Value"); |
| return err; |
| } |
| continue; |
| } |
| } |
| return 0; |
| } |
| |
| static int parse_compound_check_legacy(snd_use_case_mgr_t *uc_mgr, |
| snd_config_t *cfg, |
| int (*fcn)(snd_use_case_mgr_t *, snd_config_t *, void *, void *), |
| void *data1) |
| { |
| const char *id, *idchild; |
| int child_ctr = 0, legacy_format = 1; |
| snd_config_iterator_t i, next; |
| snd_config_t *child; |
| int err; |
| |
| err = snd_config_get_id(cfg, &id); |
| if (err < 0) |
| return err; |
| |
| snd_config_for_each(i, next, cfg) { |
| child_ctr++; |
| if (child_ctr > 1) { |
| break; |
| } |
| |
| child = snd_config_iterator_entry(i); |
| |
| if (snd_config_get_type(cfg) != SND_CONFIG_TYPE_COMPOUND) { |
| legacy_format = 0; |
| break; |
| } |
| |
| if (snd_config_get_id(child, &idchild) < 0) |
| return -EINVAL; |
| |
| if (strcmp(idchild, "0")) { |
| legacy_format = 0; |
| break; |
| } |
| } |
| if (child_ctr != 1) { |
| legacy_format = 0; |
| } |
| |
| if (legacy_format) |
| return parse_compound(uc_mgr, cfg, fcn, data1, (void *)id); |
| else |
| return fcn(uc_mgr, cfg, data1, NULL); |
| } |
| |
| static int parse_device_name(snd_use_case_mgr_t *uc_mgr, |
| snd_config_t *cfg, |
| void *data1, |
| void *data2 ATTRIBUTE_UNUSED) |
| { |
| return parse_compound_check_legacy(uc_mgr, cfg, parse_device, data1); |
| } |
| |
| static int parse_modifier_name(snd_use_case_mgr_t *uc_mgr, |
| snd_config_t *cfg, |
| void *data1, |
| void *data2 ATTRIBUTE_UNUSED) |
| { |
| return parse_compound_check_legacy(uc_mgr, cfg, parse_modifier, data1); |
| } |
| |
| /* |
| * Parse Verb Section |
| * |
| * # Example Use case verb section for Voice call blah |
| * # By Joe Blogs <joe@blogs.com> |
| * |
| * SectionVerb { |
| * # enable and disable sequences are compulsory |
| * EnableSequence [ |
| * cset "name='Master Playback Switch',index=2 0,0" |
| * cset "name='Master Playback Volume',index=2 25,25" |
| * msleep 50 |
| * cset "name='Master Playback Switch',index=2 1,1" |
| * cset "name='Master Playback Volume',index=2 50,50" |
| * ] |
| * |
| * DisableSequence [ |
| * cset "name='Master Playback Switch',index=2 0,0" |
| * cset "name='Master Playback Volume',index=2 25,25" |
| * msleep 50 |
| * cset "name='Master Playback Switch',index=2 1,1" |
| * cset "name='Master Playback Volume',index=2 50,50" |
| * ] |
| * |
| * # Optional transition verb |
| * TransitionSequence."ToCaseName" [ |
| * msleep 1 |
| * ] |
| * |
| * # Optional TQ and ALSA PCMs |
| * Value { |
| * TQ HiFi |
| * CapturePCM "hw:0" |
| * PlaybackPCM "hw:0" |
| * } |
| * } |
| */ |
| static int parse_verb(snd_use_case_mgr_t *uc_mgr, |
| struct use_case_verb *verb, |
| snd_config_t *cfg) |
| { |
| snd_config_iterator_t i, next; |
| snd_config_t *n; |
| int err; |
| |
| /* parse verb section */ |
| snd_config_for_each(i, next, cfg) { |
| const char *id; |
| n = snd_config_iterator_entry(i); |
| if (snd_config_get_id(n, &id) < 0) |
| continue; |
| |
| if (strcmp(id, "EnableSequence") == 0) { |
| uc_dbg("Parse EnableSequence"); |
| err = parse_sequence(uc_mgr, &verb->enable_list, n); |
| if (err < 0) { |
| uc_error("error: failed to parse verb enable sequence"); |
| return err; |
| } |
| continue; |
| } |
| |
| if (strcmp(id, "DisableSequence") == 0) { |
| uc_dbg("Parse DisableSequence"); |
| err = parse_sequence(uc_mgr, &verb->disable_list, n); |
| if (err < 0) { |
| uc_error("error: failed to parse verb disable sequence"); |
| return err; |
| } |
| continue; |
| } |
| |
| if (strcmp(id, "TransitionSequence") == 0) { |
| uc_dbg("Parse TransitionSequence"); |
| err = parse_transition(uc_mgr, &verb->transition_list, n); |
| if (err < 0) { |
| uc_error("error: failed to parse transition sequence"); |
| return err; |
| } |
| continue; |
| } |
| |
| if (strcmp(id, "Value") == 0) { |
| uc_dbg("Parse Value"); |
| err = parse_value(uc_mgr, &verb->value_list, n); |
| if (err < 0) |
| return err; |
| continue; |
| } |
| } |
| |
| return 0; |
| } |
| |
| /* |
| * Parse a Use case verb file. |
| * |
| * This file contains the following :- |
| * o Verb enable and disable sequences. |
| * o Supported Device enable and disable sequences for verb. |
| * o Supported Modifier enable and disable sequences for verb |
| * o Optional QoS for the verb and modifiers. |
| * o Optional PCM device ID for verb and modifiers |
| * o Alias kcontrols IDs for master and volumes and mutes. |
| */ |
| static int parse_verb_file(snd_use_case_mgr_t *uc_mgr, |
| const char *use_case_name, |
| const char *comment, |
| const char *file) |
| { |
| snd_config_iterator_t i, next; |
| snd_config_t *n; |
| struct use_case_verb *verb; |
| snd_config_t *cfg; |
| char filename[MAX_FILE]; |
| char *env = getenv(ALSA_CONFIG_UCM_VAR); |
| int err; |
| |
| /* allocate verb */ |
| verb = calloc(1, sizeof(struct use_case_verb)); |
| if (verb == NULL) |
| return -ENOMEM; |
| INIT_LIST_HEAD(&verb->enable_list); |
| INIT_LIST_HEAD(&verb->disable_list); |
| INIT_LIST_HEAD(&verb->transition_list); |
| INIT_LIST_HEAD(&verb->device_list); |
| INIT_LIST_HEAD(&verb->modifier_list); |
| INIT_LIST_HEAD(&verb->value_list); |
| list_add_tail(&verb->list, &uc_mgr->verb_list); |
| if (use_case_name == NULL) |
| return -EINVAL; |
| verb->name = strdup(use_case_name); |
| if (verb->name == NULL) |
| return -ENOMEM; |
| |
| if (comment != NULL) { |
| verb->comment = strdup(comment); |
| if (verb->comment == NULL) |
| return -ENOMEM; |
| } |
| |
| /* open Verb file for reading */ |
| snprintf(filename, sizeof(filename), "%s/%s/%s", |
| env ? env : ALSA_USE_CASE_DIR, |
| uc_mgr->card_name, file); |
| filename[sizeof(filename)-1] = '\0'; |
| |
| err = uc_mgr_config_load(filename, &cfg); |
| if (err < 0) { |
| uc_error("error: failed to open verb file %s : %d", |
| filename, -errno); |
| return err; |
| } |
| |
| /* parse master config sections */ |
| snd_config_for_each(i, next, cfg) { |
| const char *id; |
| n = snd_config_iterator_entry(i); |
| if (snd_config_get_id(n, &id) < 0) |
| continue; |
| |
| /* find verb section and parse it */ |
| if (strcmp(id, "SectionVerb") == 0) { |
| err = parse_verb(uc_mgr, verb, n); |
| if (err < 0) { |
| uc_error("error: %s failed to parse verb", |
| file); |
| return err; |
| } |
| continue; |
| } |
| |
| /* find device sections and parse them */ |
| if (strcmp(id, "SectionDevice") == 0) { |
| err = parse_compound(uc_mgr, n, |
| parse_device_name, verb, NULL); |
| if (err < 0) { |
| uc_error("error: %s failed to parse device", |
| file); |
| return err; |
| } |
| continue; |
| } |
| |
| /* find modifier sections and parse them */ |
| if (strcmp(id, "SectionModifier") == 0) { |
| err = parse_compound(uc_mgr, n, |
| parse_modifier_name, verb, NULL); |
| if (err < 0) { |
| uc_error("error: %s failed to parse modifier", |
| file); |
| return err; |
| } |
| continue; |
| } |
| } |
| |
| /* use case verb must have at least 1 device */ |
| if (list_empty(&verb->device_list)) { |
| uc_error("error: no use case device defined", file); |
| return -EINVAL; |
| } |
| return 0; |
| } |
| |
| /* |
| * Parse master section for "Use Case" and "File" tags. |
| */ |
| static int parse_master_section(snd_use_case_mgr_t *uc_mgr, snd_config_t *cfg, |
| void *data1 ATTRIBUTE_UNUSED, |
| void *data2 ATTRIBUTE_UNUSED) |
| { |
| snd_config_iterator_t i, next; |
| snd_config_t *n; |
| const char *use_case_name, *file = NULL, *comment = NULL; |
| int err; |
| |
| if (snd_config_get_id(cfg, &use_case_name) < 0) { |
| uc_error("unable to get name for use case section"); |
| return -EINVAL; |
| } |
| |
| if (snd_config_get_type(cfg) != SND_CONFIG_TYPE_COMPOUND) { |
| uc_error("compound type expected for use case section"); |
| return -EINVAL; |
| } |
| /* parse master config sections */ |
| snd_config_for_each(i, next, cfg) { |
| const char *id; |
| n = snd_config_iterator_entry(i); |
| if (snd_config_get_id(n, &id) < 0) |
| continue; |
| |
| /* get use case verb file name */ |
| if (strcmp(id, "File") == 0) { |
| err = snd_config_get_string(n, &file); |
| if (err < 0) { |
| uc_error("failed to get File"); |
| return err; |
| } |
| continue; |
| } |
| |
| /* get optional use case comment */ |
| if (strncmp(id, "Comment", 7) == 0) { |
| err = snd_config_get_string(n, &comment); |
| if (err < 0) { |
| uc_error("error: failed to get Comment"); |
| return err; |
| } |
| continue; |
| } |
| |
| uc_error("unknown field %s in master section"); |
| } |
| |
| uc_dbg("use_case_name %s file '%s'", use_case_name, file); |
| |
| /* do we have both use case name and file ? */ |
| if (!file) { |
| uc_error("error: use case missing file"); |
| return -EINVAL; |
| } |
| |
| /* parse verb file */ |
| return parse_verb_file(uc_mgr, use_case_name, comment, file); |
| } |
| |
| /* |
| * parse controls |
| */ |
| static int parse_controls(snd_use_case_mgr_t *uc_mgr, snd_config_t *cfg) |
| { |
| int err; |
| |
| if (!list_empty(&uc_mgr->default_list)) { |
| uc_error("Default list is not empty"); |
| return -EINVAL; |
| } |
| err = parse_sequence(uc_mgr, &uc_mgr->default_list, cfg); |
| if (err < 0) { |
| uc_error("Unable to parse SectionDefaults"); |
| return err; |
| } |
| |
| return 0; |
| } |
| |
| /* |
| * Each sound card has a master sound card file that lists all the supported |
| * use case verbs for that sound card. i.e. |
| * |
| * #Example master file for blah sound card |
| * #By Joe Blogs <joe@bloggs.org> |
| * |
| * Comment "Nice Abstracted Soundcard" |
| * |
| * # The file is divided into Use case sections. One section per use case verb. |
| * |
| * SectionUseCase."Voice Call" { |
| * File "voice_call_blah" |
| * Comment "Make a voice phone call." |
| * } |
| * |
| * SectionUseCase."HiFi" { |
| * File "hifi_blah" |
| * Comment "Play and record HiFi quality Music." |
| * } |
| * |
| * # Define Value defaults |
| * |
| * ValueDefaults { |
| * PlaybackCTL "hw:CARD=0" |
| * CaptureCTL "hw:CARD=0" |
| * } |
| * |
| * # This file also stores the default sound card state. |
| * |
| * SectionDefaults [ |
| * cset "name='Master Playback Switch',index=2 1,1" |
| * cset "name='Master Playback Volume',index=2 25,25" |
| * cset "name='Master Mono Playback',index=1 0" |
| * cset "name='Master Mono Playback Volume',index=1 0" |
| * cset "name='PCM Switch',index=2 1,1" |
| * exec "some binary here" |
| * msleep 50 |
| * ........ |
| * ] |
| * |
| * # End of example file. |
| */ |
| static int parse_master_file(snd_use_case_mgr_t *uc_mgr, snd_config_t *cfg) |
| { |
| snd_config_iterator_t i, next; |
| snd_config_t *n; |
| const char *id; |
| int err; |
| |
| if (snd_config_get_type(cfg) != SND_CONFIG_TYPE_COMPOUND) { |
| uc_error("compound type expected for master file"); |
| return -EINVAL; |
| } |
| |
| /* parse master config sections */ |
| snd_config_for_each(i, next, cfg) { |
| |
| n = snd_config_iterator_entry(i); |
| if (snd_config_get_id(n, &id) < 0) |
| continue; |
| |
| if (strcmp(id, "Comment") == 0) { |
| err = parse_string(n, &uc_mgr->comment); |
| if (err < 0) { |
| uc_error("error: failed to get master comment"); |
| return err; |
| } |
| continue; |
| } |
| |
| /* find use case section and parse it */ |
| if (strcmp(id, "SectionUseCase") == 0) { |
| err = parse_compound(uc_mgr, n, |
| parse_master_section, |
| NULL, NULL); |
| if (err < 0) |
| return err; |
| continue; |
| } |
| |
| /* find default control values section and parse it */ |
| if (strcmp(id, "SectionDefaults") == 0) { |
| err = parse_controls(uc_mgr, n); |
| if (err < 0) |
| return err; |
| continue; |
| } |
| |
| /* get the default values */ |
| if (strcmp(id, "ValueDefaults") == 0) { |
| err = parse_value(uc_mgr, &uc_mgr->value_list, n); |
| if (err < 0) { |
| uc_error("error: failed to parse ValueDefaults"); |
| return err; |
| } |
| continue; |
| } |
| |
| uc_error("uknown master file field %s", id); |
| } |
| return 0; |
| } |
| |
| static int load_master_config(const char *card_name, snd_config_t **cfg) |
| { |
| char filename[MAX_FILE]; |
| char *env = getenv(ALSA_CONFIG_UCM_VAR); |
| int err; |
| |
| snprintf(filename, sizeof(filename)-1, |
| "%s/%s/%s.conf", env ? env : ALSA_USE_CASE_DIR, |
| card_name, card_name); |
| filename[MAX_FILE-1] = '\0'; |
| |
| err = uc_mgr_config_load(filename, cfg); |
| if (err < 0) { |
| uc_error("error: could not parse configuration for card %s", |
| card_name); |
| return err; |
| } |
| |
| return 0; |
| } |
| |
| /* load master use case file for sound card */ |
| int uc_mgr_import_master_config(snd_use_case_mgr_t *uc_mgr) |
| { |
| snd_config_t *cfg; |
| int err; |
| |
| err = load_master_config(uc_mgr->card_name, &cfg); |
| if (err < 0) |
| return err; |
| err = parse_master_file(uc_mgr, cfg); |
| snd_config_delete(cfg); |
| if (err < 0) |
| uc_mgr_free_verb(uc_mgr); |
| |
| return err; |
| } |
| |
| static int filename_filter(const struct dirent *dirent) |
| { |
| if (dirent == NULL) |
| return 0; |
| if (dirent->d_type == DT_DIR) { |
| if (dirent->d_name[0] == '.') { |
| if (dirent->d_name[1] == '\0') |
| return 0; |
| if (dirent->d_name[1] == '.' && |
| dirent->d_name[2] == '\0') |
| return 0; |
| } |
| return 1; |
| } |
| return 0; |
| } |
| |
| /* scan all cards and comments */ |
| int uc_mgr_scan_master_configs(const char **_list[]) |
| { |
| char filename[MAX_FILE], dfl[MAX_FILE]; |
| char *env = getenv(ALSA_CONFIG_UCM_VAR); |
| const char **list; |
| snd_config_t *cfg, *c; |
| int i, cnt, err; |
| ssize_t ss; |
| struct dirent **namelist; |
| |
| snprintf(filename, sizeof(filename)-1, |
| "%s", env ? env : ALSA_USE_CASE_DIR); |
| filename[MAX_FILE-1] = '\0'; |
| |
| #ifdef _GNU_SOURCE |
| #define SORTFUNC versionsort |
| #else |
| #define SORTFUNC alphasort |
| #endif |
| err = scandir(filename, &namelist, filename_filter, SORTFUNC); |
| if (err < 0) { |
| err = -errno; |
| uc_error("error: could not scan directory %s: %s", |
| filename, strerror(-err)); |
| return err; |
| } |
| cnt = err; |
| |
| dfl[0] = '\0'; |
| if (strlen(filename) + 8 < sizeof(filename)) { |
| strcat(filename, "/default"); |
| ss = readlink(filename, dfl, sizeof(dfl)-1); |
| if (ss >= 0) { |
| dfl[ss] = '\0'; |
| dfl[sizeof(dfl)-1] = '\0'; |
| if (dfl[0] && dfl[strlen(dfl)-1] == '/') |
| dfl[strlen(dfl)-1] = '\0'; |
| } else { |
| dfl[0] = '\0'; |
| } |
| } |
| |
| list = calloc(1, cnt * 2 * sizeof(char *)); |
| if (list == NULL) { |
| err = -ENOMEM; |
| goto __err; |
| } |
| |
| for (i = 0; i < cnt; i++) { |
| err = load_master_config(namelist[i]->d_name, &cfg); |
| if (err < 0) |
| goto __err; |
| err = snd_config_search(cfg, "Comment", &c); |
| if (err >= 0) { |
| err = parse_string(c, (char **)&list[i*2+1]); |
| if (err < 0) { |
| snd_config_delete(cfg); |
| goto __err; |
| } |
| } |
| snd_config_delete(cfg); |
| list[i * 2] = strdup(namelist[i]->d_name); |
| if (list[i * 2] == NULL) { |
| err = -ENOMEM; |
| goto __err; |
| } |
| if (strcmp(dfl, list[i * 2]) == 0) { |
| /* default to top */ |
| const char *save1 = list[i * 2]; |
| const char *save2 = list[i * 2 + 1]; |
| memmove(list + 2, list, i * 2 * sizeof(char *)); |
| list[0] = save1; |
| list[1] = save2; |
| } |
| } |
| err = cnt * 2; |
| |
| __err: |
| for (i = 0; i < cnt; i++) { |
| free(namelist[i]); |
| if (err < 0) { |
| free((void *)list[i * 2]); |
| free((void *)list[i * 2 + 1]); |
| } |
| } |
| free(namelist); |
| |
| if (err >= 0) |
| *_list = list; |
| |
| return err; |
| } |