summaryrefslogtreecommitdiff
path: root/src/ucm/parser.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/ucm/parser.c')
-rw-r--r--src/ucm/parser.c956
1 files changed, 956 insertions, 0 deletions
diff --git a/src/ucm/parser.c b/src/ucm/parser.c
new file mode 100644
index 00000000..bb047515
--- /dev/null
+++ b/src/ucm/parser.c
@@ -0,0 +1,956 @@
+/*
+ * 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 General Public License
+ * along with this program; 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"
+
+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 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;
+
+ 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;
+ }
+
+ if (snd_config_get_type(cfg) != SND_CONFIG_TYPE_COMPOUND) {
+ uc_error("compound type expected for %s", id);
+ err = -EINVAL;
+ goto __err;
+ }
+ /* parse master config sections */
+ 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", id);
+ err = -EINVAL;
+ goto __err;
+ }
+
+ err = parse_sequence(uc_mgr, &tseq->transition_list, n);
+ if (err < 0)
+ return err;
+ }
+
+ list_add(&tseq->list, tlist);
+ return 0;
+ __err:
+ free(tseq->name);
+ free(tseq);
+ return err;
+}
+
+/*
+ * 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 master config sections */
+ 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", id);
+ return -EINVAL;
+ }
+
+ err = fcn(uc_mgr, n, data1, data2);
+ if (err < 0)
+ return err;
+ }
+
+ return 0;
+}
+
+/*
+ * Parse sequences.
+ *
+ * Sequence controls elements are in the following form:-
+ *
+ * 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, j, next2;
+ snd_config_t *n, *n2;
+ int err;
+
+ snd_config_for_each(i, next, cfg) {
+ n = snd_config_iterator_entry(i);
+ snd_config_for_each(j, next2, n) {
+ const char *id;
+ n2 = snd_config_iterator_entry(i);
+ err = snd_config_get_id(n2, &id);
+ if (err < 0)
+ 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(id, "cset") == 0) {
+ curr->type = SEQUENCE_ELEMENT_TYPE_CSET;
+ err = parse_string(n2, &curr->data.cset);
+ if (err < 0) {
+ uc_error("error: cset requires a string!");
+ return err;
+ }
+ continue;
+ }
+
+ if (strcmp(id, "usleep") == 0) {
+ curr->type = SEQUENCE_ELEMENT_TYPE_SLEEP;
+ err = snd_config_get_integer(n2, &curr->data.sleep);
+ if (err < 0) {
+ uc_error("error: usleep requires integer!");
+ return err;
+ }
+ continue;
+ }
+
+ if (strcmp(id, "exec") == 0) {
+ curr->type = SEQUENCE_ELEMENT_TYPE_EXEC;
+ err = parse_string(n2, &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 Modifier Use cases
+ *
+ * # Each modifier is described in new section. N modifier are allowed
+ * SectionModifier."Capture Voice" {
+ *
+ * Comment "Record voice call"
+ * SupportedDevice [
+ * "x"
+ * "y"
+ * ]
+ *
+ * EnableSequence [
+ * ....
+ * ]
+ *
+ * DisableSequence [
+ * ...
+ * ]
+ *
+ * # Optional TQ and ALSA PCMs
+ * TQ Voice
+ * CapturePCM "hw:1"
+ * MasterPlaybackVolume "name='Master Playback Volume',index=2"
+ * MasterPlaybackSwitch "name='Master Playback Switch',index=2"
+ *
+ * }
+ */
+static int parse_modifier(snd_use_case_mgr_t *uc_mgr,
+ snd_config_t *cfg,
+ void *data1,
+ void *data2 ATTRIBUTE_UNUSED)
+{
+ struct use_case_verb *verb = data1;
+ struct use_case_modifier *modifier;
+ const char *id;
+ snd_config_iterator_t i, next;
+ snd_config_t *n;
+ int err;
+
+ /* 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_add_tail(&modifier->list, &verb->modifier_list);
+ err = snd_config_get_id(cfg, &id);
+ if (err < 0)
+ return err;
+ modifier->name = strdup(id);
+ if (modifier->name == NULL)
+ return -EINVAL;
+
+ 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) {
+ struct dev_list *sdev;
+
+ sdev = calloc(1, sizeof(struct dev_list));
+ if (sdev == NULL)
+ return -ENOMEM;
+ err = parse_string(n, &sdev->name);
+ if (err < 0) {
+ free(sdev);
+ return err;
+ }
+ list_add(&sdev->list, &modifier->dev_list);
+ continue;
+ }
+
+ 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, "TransitionModifier") == 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, "TQ") == 0) {
+ err = parse_string(n, &modifier->tq);
+ if (err < 0) {
+ uc_error("error: failed to parse TQ");
+ return err;
+ }
+ continue;
+ }
+
+ if (strcmp(id, "CapturePCM") == 0) {
+ err = parse_string(n, &modifier->capture_pcm);
+ if (err < 0) {
+ uc_error("error: failed to get Capture PCM ID");
+ return err;
+ }
+ continue;
+ }
+
+ if (strcmp(id, "PlaybackPCM") == 0) {
+ err = parse_string(n, &modifier->playback_pcm);
+ if (err < 0) {
+ uc_error("error: failed to get Playback PCM ID");
+ return err;
+ }
+ continue;
+ }
+
+ if (strcmp(id, "MasterPlaybackVolume") == 0) {
+ err = parse_string(n, &modifier->playback_volume_id);
+ if (err < 0) {
+ uc_error("error: failed to get MasterPlaybackVolume");
+ return err;
+ }
+ continue;
+ }
+
+ if (strcmp(id, "MasterPlaybackSwitch") == 0) {
+ err = parse_string(n, &modifier->playback_switch_id);
+ if (err < 0) {
+ uc_error("error: failed to get MasterPlaybackSwitch");
+ return err;
+ }
+ continue;
+ }
+
+ if (strcmp(id, "MasterCaptureVolume") == 0) {
+ err = parse_string(n, &modifier->capture_volume_id);
+ if (err < 0) {
+ uc_error("error: failed to get MasterCaptureVolume");
+ return err;
+ }
+ continue;
+ }
+
+ if (strcmp(id, "MasterCaptureSwitch") == 0) {
+ err = parse_string(n, &modifier->capture_switch_id);
+ if (err < 0) {
+ uc_error("error: failed to get MasterCaptureSwitch");
+ return err;
+ }
+ continue;
+ }
+ }
+
+ if (list_empty(&modifier->dev_list)) {
+ uc_error("error: %s: modifier missing supported device sequence");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/*
+ * Parse Device Use Cases
+ *
+ *# Each device is described in new section. N devices are allowed
+ *SectionDevice."Headphones".0 {
+ * Comment "Headphones connected to 3.5mm jack"
+ *
+ * EnableSequence [
+ * ....
+ * ]
+ *
+ * DisableSequence [
+ * ...
+ * ]
+ *
+ * MasterPlaybackVolume "name='Master Playback Volume',index=2"
+ * MasterPlaybackSwitch "name='Master Playback Switch',index=2"
+ *
+ * }
+ */
+static int parse_device_index(snd_use_case_mgr_t *uc_mgr,
+ snd_config_t *cfg,
+ void *data1,
+ void *data2)
+{
+ struct use_case_verb *verb = data1;
+ char *name = data2;
+ struct use_case_device *device;
+ const char *id;
+ snd_config_iterator_t i, next;
+ snd_config_t *n;
+ int err;
+
+ 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);
+ list_add_tail(&device->list, &verb->device_list);
+ device->name = strdup(name);
+ if (device->name == NULL)
+ return -ENOMEM;
+ if (snd_config_get_id(cfg, &id) < 0)
+ return -EINVAL;
+ err = safe_strtol(id, &device->idx);
+ if (err < 0) {
+ uc_error("Invalid device index '%s'", id);
+ return -EINVAL;
+ }
+
+ 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, "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, "TransitionDevice") == 0) {
+ uc_dbg("TransitionDevice");
+ 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, "MasterPlaybackVolume") == 0) {
+ err = parse_string(n, &device->playback_volume_id);
+ if (err < 0) {
+ uc_error("error: failed to get MasterPlaybackVolume");
+ return err;
+ }
+ continue;
+ }
+
+ if (strcmp(id, "MasterPlaybackSwitch") == 0) {
+ err = parse_string(n, &device->playback_switch_id);
+ if (err < 0) {
+ uc_error("error: failed to get MasterPlaybackSwitch");
+ return err;
+ }
+ continue;
+ }
+
+ if (strcmp(id, "MasterCaptureVolume") == 0) {
+ err = parse_string(n, &device->capture_volume_id);
+ if (err < 0) {
+ uc_error("error: failed to get MasterCaptureVolume");
+ return err;
+ }
+ continue;
+ }
+
+ if (strcmp(id, "MasterCaptureSwitch") == 0) {
+ err = parse_string(n, &device->capture_switch_id);
+ if (err < 0) {
+ uc_error("error: failed to get MasterCaptureSwitch");
+ return err;
+ }
+ continue;
+ }
+ }
+ return 0;
+}
+
+static int parse_device_name(snd_use_case_mgr_t *uc_mgr,
+ snd_config_t *cfg,
+ void *data1,
+ void *data2 ATTRIBUTE_UNUSED)
+{
+ const char *id;
+ int err;
+
+ err = snd_config_get_id(cfg, &id);
+ if (err < 0)
+ return err;
+ return parse_compound(uc_mgr, cfg, parse_device_index,
+ data1, (void *)id);
+}
+
+static int parse_device(snd_use_case_mgr_t *uc_mgr,
+ struct use_case_verb *verb,
+ snd_config_t *cfg)
+{
+ return parse_compound(uc_mgr, cfg, parse_device_name, verb, NULL);
+}
+
+/*
+ * 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 TQ and ALSA PCMs
+ * TQ HiFi
+ * CapturePCM 0
+ * PlaybackPCM 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, cfg);
+ 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, cfg);
+ if (err < 0) {
+ uc_error("error: failed to parse verb disable sequence");
+ return err;
+ }
+ continue;
+ }
+
+ if (strcmp(id, "TransitionVerb") == 0) {
+ uc_dbg("Parse TransitionVerb");
+ err = parse_transition(uc_mgr, &verb->transition_list, n);
+ if (err < 0) {
+ uc_error("error: failed to parse transition verb");
+ return err;
+ }
+ continue;
+ }
+
+ if (strcmp(id, "TQ") == 0) {
+ uc_dbg("Parse TQ");
+ err = parse_string(n, &verb->tq);
+ if (err < 0)
+ return err;
+ continue;
+ }
+
+ if (strcmp(id, "CapturePCM") == 0) {
+ uc_dbg("Parse CapturePCM");
+ err = parse_string(n, &verb->capture_pcm);
+ if (err < 0)
+ return err;
+ continue;
+ }
+
+ if (strcmp(id, "PlaybackPCM") == 0) {
+ uc_dbg("Parse PlaybackPCM");
+ err = parse_string(n, &verb->playback_pcm);
+ 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];
+ 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);
+ list_add_tail(&verb->list, &uc_mgr->verb_list);
+ verb->name = strdup(use_case_name);
+ if (verb->name == NULL)
+ return -ENOMEM;
+ verb->comment = strdup(comment);
+ if (verb->comment == NULL)
+ return -ENOMEM;
+
+ /* open Verb file for reading */
+ snprintf(filename, sizeof(filename), "%s/%s/%s", 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_device(uc_mgr, verb, n);
+ 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, 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 end %d", use_case_name, file, end);
+
+ /* 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 and execute controls
+ */
+static int parse_controls(snd_use_case_mgr_t *uc_mgr, snd_config_t *cfg)
+{
+ struct list_head list;
+ int err;
+
+ INIT_LIST_HEAD(&list);
+ err = parse_sequence(uc_mgr, &list, cfg);
+ if (err < 0) {
+ uc_error("Unable to parse SectionDefaults");
+ return err;
+ }
+ printf("parse_controls - not yet implemented\n");
+ 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>
+ *
+ * # 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."
+ * }
+ *
+ * # 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;
+ int ret;
+
+ 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) {
+ const char *id;
+ n = snd_config_iterator_entry(i);
+ if (snd_config_get_id(n, &id) < 0)
+ continue;
+
+ /* find use case section and parse it */
+ if (strcmp(id, "SectionUseCase") == 0) {
+ ret = parse_compound(uc_mgr, n,
+ parse_master_section,
+ NULL, NULL);
+ if (ret < 0)
+ return ret;
+ continue;
+ }
+
+ /* find default control values section and parse it */
+ if (strcmp(id, "SectionDefaults") == 0) {
+ ret = parse_controls(uc_mgr, n);
+ if (ret < 0)
+ return ret;
+ continue;
+ }
+ uc_error("uknown master file field %s", id);
+ }
+ return 0;
+}
+
+/* load master use case file for sound card */
+int uc_mgr_import_master_config(snd_use_case_mgr_t *uc_mgr)
+{
+ char filename[MAX_FILE];
+ snd_config_t *cfg;
+ int err;
+
+ snprintf(filename, sizeof(filename)-1,
+ "%s/%s/%s.conf", ALSA_USE_CASE_DIR,
+ uc_mgr->card_name, uc_mgr->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",
+ uc_mgr->card_name);
+ return err;
+ }
+
+ err = parse_master_file(uc_mgr, cfg);
+ snd_config_delete(cfg);
+ if (err < 0)
+ uc_mgr_free_verb(uc_mgr);
+
+ return err;
+}