From 4adb3c325bccf0dec9c37b7462832d17033caf93 Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Mon, 23 Aug 2010 18:17:17 +0200 Subject: [PATCH 01/30] ucm: header - ALSA Use Case Manager This patch adds the API header for alsa-lib Use Case Management support. This file defines the public interface exported by UCM to client applications. UCM development has been kindly sponsored by Texas Instruments Inc, Wolfson Microelectronics PLC and Slimlogic Ltd. CC: Mark Brown Signed-off-by: Stefan Schmidt Signed-off-by: Jaroslav Kysela Signed-off-by: Justin Xu Signed-off-by: Liam Girdwood --- include/use-case.h | 427 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 427 insertions(+) create mode 100644 include/use-case.h diff --git a/include/use-case.h b/include/use-case.h new file mode 100644 index 00000000..18fe4d60 --- /dev/null +++ b/include/use-case.h @@ -0,0 +1,427 @@ +/** + * \file include/use-case.h + * \brief use case interface for the ALSA driver + * \author Liam Girdwood + * \author Stefan Schmidt + * \author Jaroslav Kysela + * \author Justin Xu + * \date 2008-2010 + */ +/* + * + * 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.1 of + * the License, or (at your option) any later version. + * + * This program 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 + * + * Copyright (C) 2008-2010 SlimLogic Ltd + * Copyright (C) 2010 Wolfson Microelectronics PLC + * Copyright (C) 2010 Texas Instruments Inc. + * + * 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. + */ + +#ifndef __ALSA_USE_CASE_H +#define __ALSA_USE_CASE_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * \defgroup Use Case Interface + * The ALSA Use Case manager interface. + * See \ref Usecase page for more details. + * \{ + */ + +/** + * ALSA Use Case Interface + * + * The use case manager works by configuring the sound card ALSA kcontrols to + * change the hardware digital and analog audio routing to match the requested + * device use case. The use case manager kcontrol configurations are stored in + * easy to modify text files. + * + * An audio use case can be defined by a verb and device parameter. The verb + * describes the use case action i.e. a phone call, listening to music, recording + * a conversation etc. The device describes the physical audio capture and playback + * hardware i.e. headphones, phone handset, bluetooth headset, etc. + * + * It's intended clients will mostly only need to set the use case verb and + * device for each system use case change (as the verb and device parameters + * cover most audio use cases). + * + * However there are times when a use case has to be modified at runtime. e.g. + * + * o Incoming phone call when the device is playing music + * o Recording sections of a phone call + * o Playing tones during a call. + * + * In order to allow asynchronous runtime use case adaptations, we have a third + * optional modifier parameter that can be used to further configure + * the use case during live audio runtime. + * + * This interface allows clients to :- + * + * o Query the supported use case verbs, devices and modifiers for the machine. + * o Set and Get use case verbs, devices and modifiers for the machine. + * o Get the ALSA PCM playback and capture device PCMs for use case verb and + * modifier. + * o Get the QoS parameter for each use case verb and modifier. + * o Get the ALSA master playback and capture volume/switch kcontrols + * for each use case. + */ + + +/* + * Use Case Verb. + * + * The use case verb is the main device audio action. e.g. the "HiFi" use + * case verb will configure the audio hardware for HiFi Music playback + * and capture. + */ +#define SND_USE_CASE_VERB_INACTIVE "Inactive" +#define SND_USE_CASE_VERB_HIFI "HiFi" +#define SND_USE_CASE_VERB_HIFI_LOW_POWER "HiFi Low Power" +#define SND_USE_CASE_VERB_VOICE "Voice" +#define SND_USE_CASE_VERB_VOICE_LOW_POWER "Voice Low Power" +#define SND_USE_CASE_VERB_VOICECALL "Voice Call" +#define SND_USE_CASE_VERB_IP_VOICECALL "Voice Call IP" +#define SND_USE_CASE_VERB_ANALOG_RADIO "FM Analog Radio" +#define SND_USE_CASE_VERB_DIGITAL_RADIO "FM Digital Radio" +/* add new verbs to end of list */ + + +/* + * Use Case Device. + * + * Physical system devices the render and capture audio. Devices can be OR'ed + * together to support audio on similtanious devices. + */ +#define SND_USE_CASE_DEV_NONE "None" +#define SND_USE_CASE_DEV_SPEAKER "Speaker" +#define SND_USE_CASE_DEV_LINE "Line" +#define SND_USE_CASE_DEV_HEADPHONES "Headphones" +#define SND_USE_CASE_DEV_HEADSET "Headset" +#define SND_USE_CASE_DEV_HANDSET "Handset" +#define SND_USE_CASE_DEV_BLUETOOTH "Bluetooth" +#define SND_USE_CASE_DEV_EARPIECE "Earpiece" +#define SND_USE_CASE_DEV_SPDIF "SPDIF" +#define SND_USE_CASE_DEV_HDMI "HDMI" +/* add new devices to end of list */ + + +/* + * Use Case Modifiers. + * + * The use case modifier allows runtime configuration changes to deal with + * asynchronous events. + * + * e.g. to record a voice call :- + * 1. Set verb to SND_USE_CASE_VERB_VOICECALL (for voice call) + * 2. Set modifier SND_USE_CASE_MOD_CAPTURE_VOICE when capture required. + * 3. Call snd_use_case_get_verb_capture_pcm() to get ALSA source PCM + * with captured voice pcm data. + * + * e.g. to play a ring tone when listenin to MP3 Music :- + * 1. Set verb to SND_USE_CASE_VERB_HIFI (for MP3 playback) + * 2. Set modifier to SND_USE_CASE_MOD_PLAY_TONE when incoming call happens. + * 3. Call snd_use_case_get_verb_playback_pcm() to get ALSA PCM sink for + * ringtone pcm data. + */ +#define SND_USE_CASE_MOD_CAPTURE_VOICE "Capture Voice" +#define SND_USE_CASE_MOD_CAPTURE_MUSIC "Capture Music" +#define SND_USE_CASE_MOD_PLAY_MUSIC "Play Music" +#define SND_USE_CASE_MOD_PLAY_VOICE "Play Voice" +#define SND_USE_CASE_MOD_PLAY_TONE "Play Tone" +#define SND_USE_CASE_MOD_ECHO_REF "Echo Reference" +/* add new modifiers to end of list */ + + +/** + * QoS - Quality of Service + * + * The interface allows clients to determine the audio QoS required for each + * use case verb and modifier. It's intended as an optional hint to the + * audio driver in order to lower power consumption. + * + */ +enum snd_use_case_qos { + SND_USE_CASE_QOS_UNKNOWN, + SND_USE_CASE_QOS_MUSIC, + SND_USE_CASE_QOS_VOICE, + SND_USE_CASE_QOS_TONES, +}; + +/* + * Use Case Control Aliases. + * + * Use cases often use different internal hardware paths to route digital and + * analog audio. This can mean different controls are used to change volumes + * depending on the particular use case. This interface allows clients to + * find out the hardware controls associated with each use case. + */ +enum snd_use_case_control_alias { + SND_USE_CASE_ALIAS_PLAYBACK_VOLUME = 0, + SND_USE_CASE_ALIAS_PLAYBACK_SWITCH, + SND_USE_CASE_ALIAS_CAPTURE_VOLUME, + SND_USE_CASE_ALIAS_CAPTURE_SWITCH, +}; + +/** use case container */ +typedef struct snd_use_case_mgr snd_use_case_mgr_t; + +/** + * \brief List supported use case verbs for given soundcard + * \param uc_mgr Use case manager + * \param verb Returned list of supported use case verbs + * \return Number of use case verbs if success, otherwise a negative error code + */ +int snd_use_case_get_verb_list(snd_use_case_mgr_t *uc_mgr, const char **verb[]); + +/** + * \brief List supported use case devices for given use case verb + * \param uc_mgr Use case manager + * \param verb Verb name. + * \param device Returned list of supported use case devices + * \return Number of use case devices if success, otherwise a negative error code + */ +int snd_use_case_get_device_list(snd_use_case_mgr_t *uc_mgr, + const char *verb, const char **device[]); + +/** + * \brief List supported use case verb modifiers for given verb + * \param uc_mgr use case manager + * \param verb verb id. + * \param mod returned list of supported use case modifier id and names + * \return number of use case modifiers if success, otherwise a negative error code + */ +int snd_use_case_get_mod_list(snd_use_case_mgr_t *uc_mgr, + const char *verb, const char **mod[]); + +/** + * \brief Get current use case verb for sound card + * \param uc_mgr Use case manager + * \return Verb if success, otherwise NULL + */ +const char *snd_use_case_get_verb(snd_use_case_mgr_t *uc_mgr); + +/** + * \brief Set new use case verb for sound card + * \param uc_mgr Use case manager + * \param verb Verb + * \return Zero if success, otherwise a negative error code + */ +int snd_use_case_set_verb(snd_use_case_mgr_t *uc_mgr, const char *verb); + +/** + * \brief Enable use case device for current use case verb + * \param uc_mgr Use case manager + * \param device Device + * \return Zero if success, otherwise a negative error code + */ +int snd_use_case_enable_device(snd_use_case_mgr_t *uc_mgr, const char *device); + +/** + * \brief Disable use case device for current use case verb + * \param uc_mgr Use case manager + * \param device Device + * \return Zero if success, otherwise a negative error code + */ +int snd_use_case_disable_device(snd_use_case_mgr_t *uc_mgr, const char *device); + +/** + * \brief Disable old_device and then enable new_device. + * If from_device is not enabled just return. + * Check transmit sequence firstly. + * \param uc_mgr Use case manager + * \param old the device to be closed + * \param new the device to be opened + * \return 0 = successful negative = error + */ +int snd_use_case_switch_device(snd_use_case_mgr_t *uc_mgr, + const char *old, const char *new); + + +/** + * \brief Enable use case modifier for current use case verb + * \param uc_mgr Use case manager + * \param modifier Modifier + * \return Zero if success, otherwise a negative error code + */ +int snd_use_case_enable_modifier(snd_use_case_mgr_t *uc_mgr, + const char *modifier); + +/** + * \brief Disable use case modifier for curent use case verb + * \param uc_mgr Use case manager + * \param modifier Modifier + * \return Zero if success, otherwise a negative error code + */ +int snd_use_case_disable_modifier(snd_use_case_mgr_t *uc_mgr, + const char *modifier); + +/** + * \brief Disable old_modifier and then enable new_modifier. + * If old_modifier is not enabled just return. + * Check transmit sequence firstly. + * \param uc_mgr Use case manager + * \param old the modifier to be closed + * \param new the modifier to be opened + * \return 0 = successful negative = error + */ +int snd_use_case_switch_modifier(snd_use_case_mgr_t *uc_mgr, + const char *old, const char *new); + +/** + * \brief Get device status for current use case verb + * \param uc_mgr Use case manager + * \param device_name The device we are interested in. + * \return - 1 = enabled, 0 = disabled, negative = error + */ +int snd_use_case_get_device_status(snd_use_case_mgr_t *uc_mgr, + const char *device_name); + +/** + * \brief Get modifier status for current use case verb + * \param uc_mgr Use case manager + * \param device_name The device we are interested in. + * \return - 1 = enabled, 0 = disabled, negative = error + */ +int snd_use_case_get_modifier_status(snd_use_case_mgr_t *uc_mgr, + const char *modifier_name); + +/** + * \brief Get the current use case verb QoS + * \param uc_mgr Use case manager + * \return QoS level + */ +enum snd_use_case_qos snd_use_case_get_verb_qos(snd_use_case_mgr_t *uc_mgr); + +/** + * \brief Get use case modifier QoS + * \param uc_mgr use case manager + * \param modifier Modifier + * \return QoS level + */ +enum snd_use_case_qos snd_use_case_get_mod_qos(snd_use_case_mgr_t *uc_mgr, + const char *modifier); + +/** + * \brief Get the current use case verb playback PCM sink ID. + * \param uc_mgr use case manager + * \return PCM number if success, otherwise negative + */ +int snd_use_case_get_verb_playback_pcm(snd_use_case_mgr_t *uc_mgr); + +/** + * \brief Get the current use case verb capture PCM source ID + * \param uc_mgr Use case manager + * \return PCM number if success, otherwise negative + */ +int snd_use_case_get_verb_capture_pcm(snd_use_case_mgr_t *uc_mgr); + +/** + * \brief Get use case modifier playback PCM sink ID + * \param uc_mgr Use case manager + * \param modifier Modifier + * \return PCM number if success, otherwise negative + */ +int snd_use_case_get_mod_playback_pcm(snd_use_case_mgr_t *uc_mgr, + const char *modifier); + +/** + * \brief Get use case modifier capture PCM source ID + * \param uc_mgr Use case manager + * \param modifier Modifier + * \return PCM number if success, otherwise negative + */ +int snd_use_case_get_mod_capture_pcm(snd_use_case_mgr_t *uc_mgr, + const char *modifier); + +/** + * \brief Get ALSA volume/mute control names depending on use case device. + * \param uc_mgr Use case manager + * \param type The control type we are looking for + * \param device The device we are interested in. + * \return name if success, otherwise NULL + * + * Get the control name for common volume and mute controls that are aliased + * in the current use case verb. + */ +const char *snd_use_case_get_device_ctl_elem_id(snd_use_case_mgr_t *uc_mgr, + enum snd_use_case_control_alias type, const char *device); + +/** + * \brief Get ALSA volume/mute control names depending on use case modifier. + * \param uc_mgr Use case manager + * \param type The control type we are looking for + * \param modifier The modifier we are interested in. + * \return name if success, otherwise NULL + * + * Get the control name for common volume and mute controls that are aliased + * in the current use case modifier. + */ +const char *snd_use_case_get_modifier_ctl_elem_id(snd_use_case_mgr_t *uc_mgr, + enum snd_use_case_control_alias type, const char *modifier); + +/** + * \brief Open and initialise use case core for sound card + * \param card_name Sound card name. + * \return Use case handle if success, otherwise NULL + */ +snd_use_case_mgr_t *snd_use_case_mgr_open(const char *card_name); + + +/** + * \brief Reload and re-parse use case configuration files for sound card. + * \param uc_mgr Use case manager + * \return zero if success, otherwise a negative error code + */ +int snd_use_case_mgr_reload(snd_use_case_mgr_t *uc_mgr); + +/** + * \brief Close use case manager + * \param uc_mgr Use case manager + * \return zero if success, otherwise a negative error code + */ +int snd_use_case_mgr_close(snd_use_case_mgr_t *uc_mgr); + +/** + * \brief Reset use case manager verb, device, modifier to deafult settings. + * \param uc_mgr Use case manager + * \return zero if success, otherwise a negative error code + */ +int snd_use_case_mgr_reset(snd_use_case_mgr_t *uc_mgr); + +/** + * \brief Dump current sound card use case control settings + * \param card_name Sound card name + * \return zero if success, otherwise a negative error code + */ +int snd_use_case_dump(const char *card_name); + +/** + * \} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* __ALSA_USE_CASE_H */ From 7699c92bd4bcd65098af631f6f5e2e7e9235caeb Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Mon, 23 Aug 2010 18:17:18 +0200 Subject: [PATCH 02/30] ucm: core - Add initial Use Case Manager support. This patch adds audio Use Case management support to alsa-lib. UCM is designed to abstract the audio device hardware mixer controls into high level abstract use cases. The use case manager works by configuring the sound card ALSA kcontrols to change the hardware digital and analog audio routing to match the requested device use case. The use case manager kcontrol configurations are stored in easy to modify text files. UCM development has been kindly sponsored by Texas Instruments Inc, Wolfson Microelectronics PLC and Slimlogic Ltd. CC: Mark Brown Signed-off-by: Stefan Schmidt Signed-off-by: Justin Xu Signed-off-by: Liam Girdwood --- src/use-case.c | 3515 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 3515 insertions(+) create mode 100644 src/use-case.c diff --git a/src/use-case.c b/src/use-case.c new file mode 100644 index 00000000..b45fe1f1 --- /dev/null +++ b/src/use-case.c @@ -0,0 +1,3515 @@ +/* + * 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. + * Authors: Liam Girdwood + * Stefan Schmidt + * Justin Xu + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../include/use-case.h" +#include "../include/iatomic.h" + +#define PRE_SEQ 0 +#define POST_SEQ 1 +#define MAX_VERB 32 +#define MAX_DEVICE 64 +#define MAX_MODIFIER 64 +#define MAX_NAME 64 +#define MAX_FILE 256 +#define MAX_BUF 256 +#define ALSA_USE_CASE_DIR "/usr/share/alsa/ucm" +#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0])) +#define VERB_NOT_INITIALISED -1 + +/* + * Stores all use case settings for 1 kcontrol. Hence we have a + * control_settings for each kcontrol in card. + */ +struct control_settings { + char name[MAX_NAME]; + unsigned int id; + snd_ctl_elem_type_t type; + short count; /* 1 = mono, 2 = stereo, etc */ + unsigned short *value; +}; + +/* + * If sleep is 0 the element contains the settings in control. Else sleep + * contains the sleep time in micro seconds. + */ +struct sequence_element { + unsigned int sleep; /* Sleep time in msecs if sleep element, else 0 */ + struct control_settings *control; + struct sequence_element *next; /* Pointer to next list element */ +}; + +/* + * Transition sequences. i.e. transition between one verb, device, mod to another + */ +struct transition_sequence { + char *name; + struct sequence_element *transition; + struct transition_sequence *next; +}; + +/* + * Modifier Supported Devicees. + */ +struct dev_list { + char *name; + struct dev_list *next; +}; + + +/* + * Describes a Use Case Modifier and it's enable and disable sequences. + * A use case verb can have N modifiers. + */ +struct use_case_modifier { + char *name; + char *comment; + + /* modifier enable and disable sequences */ + struct sequence_element *enable; + struct sequence_element *disable; + + /* modifier transition list */ + struct transition_sequence *transition_list; + + /* list of supported devices per modifier */ + struct dev_list *dev_list; + + /* ALSA PCM devices associated with any modifier PCM streams */ + int capture_pcm; + int playback_pcm; + + /* Any modifier stream QoS */ + enum snd_use_case_qos qos; + + /* aliased controls */ + char *playback_volume_id; + char *playback_switch_id; + char *capture_volume_id; + char *capture_switch_id; +}; + +/* + * Describes a Use Case Device and it's enable and disable sequences. + * A use case verb can have N devices. + */ +struct use_case_device { + char *name; + char *comment; + int idx; /* index for similar devices i.e. 2 headphone jacks */ + + /* device enable and disable sequences */ + struct sequence_element *enable; + struct sequence_element *disable; + + /* device transition list */ + struct transition_sequence *transition_list; + + /* aliased controls */ + char *playback_volume_id; + char *playback_switch_id; + char *capture_volume_id; + char *capture_switch_id; +}; + +/* + * Describes a Use Case Verb and it's enable and disable sequences. + * A use case verb can have N devices and N modifiers. + */ +struct use_case_verb { + char *name; + char *comment; + + /* verb enable and disable sequences */ + struct sequence_element *enable; + struct sequence_element *disable; + + /* verb transition list */ + struct transition_sequence *transition_list; + + /* verb PCMs and QoS */ + enum snd_use_case_qos qos; + int capture_pcm; + int playback_pcm; + + /* hardware devices that can be used with this use case */ + int num_devices; + struct use_case_device *device; + /* + * device_list[i] shares string with device[i].name, + * so device_list don't need not be freed + */ + const char *device_list[MAX_DEVICE]; + + /* modifiers that can be used with this use case */ + int num_modifiers; + struct use_case_modifier *modifier; + /* + * modifier_list[i] shares string with modifier[i].name, + * so modifier_list don't need not be freed + */ + const char *modifier_list[MAX_MODIFIER]; +}; + +struct ucm_card { + int current_verb; + int current_device[MAX_DEVICE]; + int current_modifier[MAX_MODIFIER]; +}; + +/* + * Manages a sound card and all its use cases. + */ +struct snd_use_case_mgr { + pthread_mutex_t mutex; + char *card_name; + char *ctl_name; + struct ucm_card card; + + /* use case verb, devices and modifier configs parsed from files */ + int num_verbs; /* number of supported use case verbs */ + struct use_case_verb *verb; /* var len array of use case info */ + /* + * verb_list[i] shares string with verb[i].name, + * so verb_list don't need not be freed + */ + const char *verb_list[MAX_VERB]; + + /* sound card ALSA kcontrol read from sound card device */ + struct control_settings *control; /* var len array of controls */ + + /* snd ctl */ + int count; + snd_ctl_t *handle; + snd_ctl_card_info_t *info; + snd_ctl_elem_list_t *list; + snd_ctl_elem_id_t *id; +}; + +static void uc_mgr_error(const char *fmt,...) +{ + va_list va; + va_start(va, fmt); + fprintf(stderr, "ucm: "); + vfprintf(stderr, fmt, va); + va_end(va); +} + +#define uc_error(fmt, arg...) do { \ + uc_mgr_error("%s() - " fmt "\n", __FUNCTION__ , ## arg); \ +} while (0) + +static void uc_mgr_stdout(const char *fmt,...) +{ + va_list va; + va_start(va, fmt); + vfprintf(stdout, fmt, va); + va_end(va); +} + +#undef UC_MGR_DEBUG + +#ifdef UC_MGR_DEBUG +#define uc_dbg(fmt, arg...) do { \ + uc_mgr_stdout("%s() - " fmt "\n", __FUNCTION__ , ## arg); \ +} while (0) +#else +#define uc_dbg(fmt, arg...) +#endif + +static int set_control(snd_ctl_t *handle, snd_ctl_elem_id_t *id, + snd_use_case_mgr_t *uc_mgr, unsigned short value[]); + +static inline void set_value(struct control_settings *control, + int count, unsigned short val) +{ + uc_dbg("value %d, count %d", val, count); + control->value[count] = val; +} + +static inline unsigned short get_value(struct control_settings *control, + int count) +{ + return control->value[count]; +} + +static inline void set_device_status(struct snd_use_case_mgr *uc_mgr, + int device_id, int status) +{ + struct use_case_verb *verb; + + verb = &uc_mgr->verb[uc_mgr->card.current_verb]; + uc_mgr->card.current_device[device_id] = status; +} + +static inline void set_modifier_status(struct snd_use_case_mgr *uc_mgr, + int modifier_id, int status) +{ + struct use_case_verb *verb; + + verb = &uc_mgr->verb[uc_mgr->card.current_verb]; + uc_mgr->card.current_modifier[modifier_id] = status; +} + +static inline int get_device_status(struct snd_use_case_mgr *uc_mgr, + int device_id) +{ + struct use_case_verb *verb; + + verb = &uc_mgr->verb[uc_mgr->card.current_verb]; + return uc_mgr->card.current_device[device_id]; +} + +static inline int get_modifier_status(struct snd_use_case_mgr *uc_mgr, + int modifier_id) +{ + struct use_case_verb *verb; + + verb = &uc_mgr->verb[uc_mgr->card.current_verb]; + return uc_mgr->card.current_modifier[modifier_id]; +} + +static int dump_control(snd_ctl_t *handle, snd_ctl_elem_id_t *id) +{ + int err, count, i; + snd_ctl_elem_info_t *info; + snd_ctl_elem_type_t type; + snd_ctl_elem_value_t *control; + + snd_ctl_elem_info_alloca(&info); + snd_ctl_elem_value_alloca(&control); + + snd_ctl_elem_info_set_id(info, id); + err = snd_ctl_elem_info(handle, info); + if (err < 0) { + uc_error("error: failed to get ctl info: %s\n", + snd_strerror(err)); + return err; + } + + snd_ctl_elem_value_set_id(control, id); + snd_ctl_elem_read(handle, control); + + type = snd_ctl_elem_info_get_type(info); + count = snd_ctl_elem_info_get_count(info); + if (count == 0) + return 0; + + uc_mgr_stdout("'%s':%d:", + snd_ctl_elem_id_get_name(id), count); + + switch (type) { + case SND_CTL_ELEM_TYPE_BOOLEAN: + for (i = 0; i < count - 1; i++) + uc_mgr_stdout("%d,", + snd_ctl_elem_value_get_boolean(control, i)); + uc_mgr_stdout("%d", + snd_ctl_elem_value_get_boolean(control, i)); + break; + case SND_CTL_ELEM_TYPE_INTEGER: + for (i = 0; i < count - 1; i++) + uc_mgr_stdout("%d,", + snd_ctl_elem_value_get_integer(control, i)); + uc_mgr_stdout("%d", + snd_ctl_elem_value_get_integer(control, i)); + break; + case SND_CTL_ELEM_TYPE_INTEGER64: + for (i = 0; i < count - 1; i++) + uc_mgr_stdout("%ld,", + snd_ctl_elem_value_get_integer64(control, i)); + uc_mgr_stdout("%ld", + snd_ctl_elem_value_get_integer64(control, i)); + break; + case SND_CTL_ELEM_TYPE_ENUMERATED: + for (i = 0; i < count - 1; i++) + uc_mgr_stdout("%d,", + snd_ctl_elem_value_get_enumerated(control, i)); + uc_mgr_stdout("%d", + snd_ctl_elem_value_get_enumerated(control, i)); + break; + case SND_CTL_ELEM_TYPE_BYTES: + for (i = 0; i < count - 1; i++) + uc_mgr_stdout("%2.2x,", + snd_ctl_elem_value_get_byte(control, i)); + uc_mgr_stdout("%2.2x", + snd_ctl_elem_value_get_byte(control, i)); + break; + default: + break; + } + uc_mgr_stdout("\n"); + return 0; +} + +/* + * Add new kcontrol from sound card into memory database. + */ +static int add_control(snd_ctl_t *handle, snd_ctl_elem_id_t *id, + struct control_settings *control_settings) +{ + int err; + snd_ctl_elem_info_t *info; + snd_ctl_elem_value_t *control; + + snd_ctl_elem_info_alloca(&info); + snd_ctl_elem_value_alloca(&control); + + snd_ctl_elem_info_set_id(info, id); + err = snd_ctl_elem_info(handle, info); + if (err < 0) { + uc_error("error: failed to get ctl info: %s\n", + snd_strerror(err)); + return err; + } + + snd_ctl_elem_value_set_id(control, id); + snd_ctl_elem_read(handle, control); + + strncpy(control_settings->name, snd_ctl_elem_id_get_name(id), + MAX_NAME); + + control_settings->name[MAX_NAME - 1] = 0; + control_settings->count = snd_ctl_elem_info_get_count(info); + control_settings->type = snd_ctl_elem_info_get_type(info); + control_settings->id = snd_ctl_elem_id_get_numid(id); + uc_dbg("control name %s", control_settings->name); + uc_dbg("control count %d", control_settings->count); + uc_dbg("control type %d", control_settings->type); + uc_dbg("control id %d", control_settings->id); + return 0; +} + +static int set_control_default(snd_use_case_mgr_t *uc_mgr, + struct control_settings *control) +{ + snd_ctl_elem_id_t *id; + int i, ret = -ENODEV; + unsigned int numid; + + snd_ctl_elem_id_alloca(&id); + + /* Where is id lookup from numid if you need it? */ + for (i = 0; i < uc_mgr->count; ++i) { + snd_ctl_elem_list_get_id(uc_mgr->list, i, id); + numid = snd_ctl_elem_id_get_numid(id); + if (numid == control->id) { + ret = set_control(uc_mgr->handle, id, uc_mgr, + control->value); + goto out; + } + } + uc_error("error: could not find control ID %s : %d", + control->name, control->id); +out: + return ret; +} + +static int get_control_id(snd_use_case_mgr_t *uc_mgr, + struct control_settings *control) +{ + int i = 0; + struct control_settings *card_control; + + uc_dbg("name %s count %d", + control->name, control->count); + + for (i = 0; i < uc_mgr->count; i++) { + card_control = &uc_mgr->control[i]; + if (!strcmp(card_control->name, control->name)) { + control->id = uc_mgr->control[i].id; + uc_dbg("Get id %d", control->id); + return 0; + } + } + + uc_error("error: control name %s is not available", control->name); + + return -EINVAL; +} + +static char *get_control_name(char *buf, int line, char *file) +{ + char name[MAX_NAME]; + char *name_start, *name_end, *tbuf = buf; + + /* get name start */ + while (*tbuf != 0 && *tbuf != '\'') + tbuf++; + if (*tbuf == 0) + return NULL; + name_start = ++tbuf; + + /* get name end */ + while (*tbuf != 0 && *tbuf != '\'') + tbuf++; + if (*tbuf == 0) + return NULL; + name_end = tbuf++; + + /* copy name */ + if ((name_end - name_start) > MAX_NAME) { + uc_error("error: %s:%d name too big at %d chars", + file, line, name_end - name_start); + return NULL; + } + strncpy(name, name_start, name_end - name_start); + name[name_end - name_start] = 0; + return strdup(name); +} + +/* + * Parse a single control from file. + * + * Controls are in the following form:- + * + * 'name':channels:value0,value1,...,valueN + * + * e.g. + * 'Master Playback Switch':2:0,0 + */ +static int parse_control(snd_use_case_mgr_t *uc_mgr, + struct control_settings *control, char *buf, + int line, char *file) +{ + char name[MAX_NAME]; + int count, i; + char *name_start, *name_end, *tbuf; + + uc_dbg("%s", buf); + + tbuf = buf; + + /* get name start */ + while (*tbuf != 0 && *tbuf != '\'') + tbuf++; + if (*tbuf == 0) + return -EINVAL; + name_start = ++tbuf; + + /* get name end */ + while (*tbuf != 0 && *tbuf != '\'') + tbuf++; + if (*tbuf == 0) + return -EINVAL; + name_end = tbuf++; + + /* copy name */ + if ((name_end - name_start) > MAX_NAME) { + uc_error("error: %s:%d name too big at %d chars", + file, line, name_end - name_start); + return -EINVAL; + } + strncpy(name, name_start, name_end - name_start); + name[name_end - name_start] = 0; + strncpy(control->name, name, name_end - name_start +1); + + /* get count */ + uc_dbg("%s", tbuf); + tbuf++; + count = atoi(tbuf); + if (count == 0) { + uc_error("error: %s:%d count == 0 on line %d", file, line); + return -EINVAL; + } + control->count = count; + + /* get vals */ + control->value = calloc(count, sizeof(unsigned short)); + if (control->value == NULL) + return -ENOMEM; + + while (*tbuf != 0 && *tbuf != ':') + tbuf++; + if (*tbuf == 0) + return -EINVAL; + tbuf++; + + for (i = 0; i < count; i++) { + set_value(control, i, atoi(tbuf)); + while (*tbuf != 0 && *tbuf != ',') + tbuf++; + + if (*tbuf++ == 0 && i < (count - 1)) + return -EINVAL; + } + + return get_control_id(uc_mgr, control); +} + +static int parse_controls(snd_use_case_mgr_t *uc_mgr, FILE *f, int *line_, + char *file) +{ + struct control_settings *control = NULL; + char buf[MAX_BUF], name[MAX_NAME]; + int count, i, ret = 0, line = *line_; + char *name_start, *name_end, *tbuf; + + while (fgets(buf, MAX_BUF, f) != NULL) { + + uc_dbg("%s: get line %d\n%s", file, line, buf); + + tbuf = buf; + while (isblank(*tbuf)) + tbuf++; + line ++; + + /* end of section ?*/ + if (strncmp(tbuf, "EndSectionDefaults", 18) == 0) + return 0; + + /* get name start */ + while (*tbuf != 0 && *tbuf != '\'') + tbuf++; + if (*tbuf == 0) + return -EINVAL; + name_start = ++tbuf; + + /* get name end */ + while (*tbuf != 0 && *tbuf != '\'') + tbuf++; + if (*tbuf == 0) + return -EINVAL; + name_end = tbuf++; + + /* copy name */ + if ((name_end - name_start) > MAX_NAME) { + uc_error("error: %s:%d name too big at %d chars", + file, line, name_end - name_start); + return -EINVAL; + } + strncpy(name, name_start, name_end - name_start); + name[name_end - name_start] = 0; + + for (i = 0; i < uc_mgr->count; i++) { + struct control_settings *card_control = + &uc_mgr->control[i]; + if (!strcmp(card_control->name, name)) + control = &uc_mgr->control[i]; + } + + uc_dbg("control id %d", control->id); + + /* get count */ + tbuf++; + count = atoi(tbuf); + if (count == 0) { + uc_error("error: %s:%d count == 0", file, line); + return -EINVAL; + } + if (count != control->count) { + uc_error("error: %s:%d count %d does not match card count" + " %d", file, line, count, control->count); + return -EINVAL; + } + + /* get vals */ + control->value = malloc(control->count *uc_mgr->num_verbs * + sizeof(unsigned short)); + if (control->value == NULL) + return -ENOMEM; + + while (*tbuf != 0 && *tbuf != ':') + tbuf++; + if (*tbuf == 0) + return -EINVAL; + tbuf++; + + for (i = 0; i < count; i++) { + set_value(control, i, atoi(tbuf)); + while (*tbuf != 0 && *tbuf != ',') + tbuf++; + + if (*tbuf++ == 0 && i < (count - 1)) + return -EINVAL; + } + } + + *line_ = line; + return ret; +} + +static char *get_string (char *buf) +{ + char *str, *end; + + uc_dbg("%s", buf); + + while (isblank(*buf)) + buf++; + + /* find leading '"' */ + if (*buf == 0 || *buf != '"') { + uc_error("error: missing start '\"'"); + return NULL; + } + str = ++buf; + + /* get value */ + while (*buf != 0 && *buf != '"') + buf++; + end = buf; + + /* find '"' terminator */ + if (*buf == 0 || *buf != '"') { + uc_error("error: missing terminator '\"' %s", buf); + return NULL; + } + + *end = 0; + return strdup(str); +} + +static enum snd_use_case_qos get_enum (char *buf) +{ + while (isblank(*buf)) + buf++; + + if (!strncmp(buf, "Music", 5)) + return SND_USE_CASE_QOS_MUSIC; + if (!strncmp(buf, "Voice", 5)) + return SND_USE_CASE_QOS_VOICE; + if (!strncmp(buf, "Tones", 5)) + return SND_USE_CASE_QOS_TONES; + + return SND_USE_CASE_QOS_MUSIC; +} + +static void seq_list_append(struct sequence_element **base, + struct sequence_element *curr) +{ + struct sequence_element *last, *tmp; + + if (!*base) + *base = curr; + else { + tmp = *base; + while (tmp) { + last = tmp; + tmp = tmp->next; + } + last->next = curr; + curr->next = NULL; + } +} +/* + * Parse sequences. + * + * Sequence controls elements are in the following form:- + * + * 'name':value0,value1,...,valueN + * + * e.g. + * 'Master Playback Switch':0,0 + */ +static int parse_sequence(snd_use_case_mgr_t *uc_mgr, + struct sequence_element **base, FILE *f, + int *line_, char *file) +{ + char buf[MAX_BUF], *tbuf; + int ret, line = *line_; + struct sequence_element *curr; + + /* read line by line */ + while (fgets(buf, MAX_BUF, f) != NULL) { + + uc_dbg("%s", buf); + line++; + + /* Check for lines with comments and ignore */ + if (buf[0] == '#') + continue; + + /* Parse current line and skip blanks */ + tbuf = buf; + while (isblank(*tbuf)) + tbuf++; + + /* end of sequence ? */ + if (strncmp(tbuf, "EndSequence", 11) == 0) + goto out; + + if (strncmp(tbuf, "EndTransition", 13) == 0) + goto out; + + /* alloc new sequence element */ + curr = calloc(1, sizeof(struct sequence_element)); + if (curr == NULL) + return -ENOMEM; + + /* is element a sleep ? */ + if (strncmp(tbuf, "msleep", 6) == 0) { + curr->sleep = atoi(tbuf + 6); + uc_dbg("msleep %d", curr->sleep); + seq_list_append(base, curr); + continue; + } + + /* alloc new sequence control */ + curr->control = calloc(1, sizeof(struct control_settings)); + if (curr->control == NULL) + return -ENOMEM; + + ret = parse_control(uc_mgr, curr->control, tbuf, line, file); + if (ret < 0) { + uc_error("error: %s:%d failed to get parse sequence" + " controls", file, line); + goto err; + } + + uc_dbg("name %s, id %d, count %d", curr->control->name, + curr->control->id, curr->control->count); + seq_list_append(base, curr); + } + +out: + *line_ = line; + return 0; + +err: + free(curr); + return ret; +} + +static void prepend_transition( + struct transition_sequence **transition_list, + struct transition_sequence *trans_seq) +{ + if (*transition_list == NULL) + *transition_list = trans_seq; + else { + trans_seq->next = *transition_list; + *transition_list = trans_seq; + } + +} + +static void prepend_dev(struct dev_list **dev_list, + struct dev_list *sdev) +{ + if (*dev_list == NULL) + *dev_list = sdev; + else { + sdev->next = *dev_list; + *dev_list = sdev; + } + +} + +/* + * Parse Modifier Use cases + * + * # Each modifier is described in new section. N modifier are allowed + * SectionModifier + * + * Name "Capture Voice" + * Comment "Record voice call" + * SupportedDevice "x" + * SupportedDevice "y" + * + * EnableSequence + * .... + * EndSequence + * + * DisableSequence + * ... + * EndSequence + * + * # Optional QoS and ALSA PCMs + * QoS Voice + * CapturePCM 1 + * MasterPlaybackVolume 'Device Master Playback Volume' + * MasterPlaybackSwitch 'Device Master Playback Switch' + * + * EndSection + */ +static int parse_modifier(snd_use_case_mgr_t *uc_mgr, + struct use_case_verb *verb, FILE *f, int *line_, char *file) +{ + struct use_case_modifier *modifier; + int line = *line_, end = 0, en_seq = 0, dis_seq = 0; + int id = 0, dev = 0, ret; + char buf[MAX_BUF], *tbuf; + char *name = NULL, *comment = NULL; + + /* allocate modifier */ + verb->modifier = realloc(verb->modifier, + (verb->num_modifiers + 1) * sizeof(struct use_case_modifier)); + if (verb->modifier == NULL) + return -ENOMEM; + modifier = verb->modifier + verb->num_modifiers; + bzero(modifier, sizeof(struct use_case_modifier)); + + /* read line by line */ + while(fgets(buf, MAX_BUF, f) != NULL) { + + line++; + + /* skip comments */ + if (buf[0] == '#') + continue; + + tbuf = buf; + /* skip spaces */ + while (isblank(*tbuf)) + tbuf++; + + /* get use case modifier name */ + if (strncmp(tbuf, "Name", 4) == 0) { + name = get_string(tbuf + 4); + if (name == NULL) { + uc_error("error: %s:%d failed to get modifier name", + file, line); + goto err; + } + modifier->name = name; + id = 1; + continue; + } + + if (strncmp(tbuf, "Comment", 8) == 0) { + name = get_string(tbuf + 8); + if (name == NULL) { + uc_error("error: %s:%d failed to get modifier comment", + file, line); + goto err; + } + modifier->comment = comment; + continue; + } + + if (strncmp(tbuf, "SupportedDevice", 15) == 0) { + struct dev_list *sdev; + name = get_string(tbuf + 15); + if (name == NULL) { + uc_error("error: %s:%d failed to get modifier" + " supported device", file, line); + goto err; + } + + sdev = calloc(1, sizeof(struct dev_list)); + if (sdev == NULL) + goto err; + + dev = 1; + sdev->name = name; + prepend_dev(&modifier->dev_list, sdev); + continue; + } + + if (strncmp(tbuf, "EnableSequence", 14) == 0) { + ret = parse_sequence(uc_mgr, &modifier->enable, f, + &line, file); + if (ret < 0) { + uc_error("error: %s:%d failed to parse modifier" + " enable sequence", file, line); + goto err; + } + en_seq = 1; + continue; + } + + if (strncmp(tbuf, "DisableSequence", 15) == 0) { + ret = parse_sequence(uc_mgr, &modifier->disable, f, + &line, file); + if (ret < 0) { + uc_error("error: %s:%d failed to parse modifier" + " disable sequence", file, line); + goto err; + } + dis_seq = 1; + continue; + } + + if (strncmp(tbuf, "TransitionModifier", 16) == 0) { + struct transition_sequence *trans_seq; + + name = get_string(tbuf + 16); + if (name == NULL) + continue; + + trans_seq = calloc(1, sizeof(struct transition_sequence)); + if (trans_seq == NULL) + return -ENOMEM; + + trans_seq->name = name; + + ret = parse_sequence(uc_mgr, &trans_seq->transition, f, + &line, file); + if (ret < 0) { + uc_error("error: %s:%d failed to parse transition" + " modifier", file, line); + goto err; + } + + prepend_transition(&modifier->transition_list, trans_seq); + continue; + } + + if (strncmp(tbuf, "QoS", 3) == 0) { + modifier->qos = get_enum(tbuf + 3); + continue; + } + + if (strncmp(tbuf, "CapturePCM", 3) == 0) { + modifier->capture_pcm = atoi(tbuf + 3); + if (modifier->capture_pcm < 0) { + uc_error("error: %s:%d failed to get Capture PCM ID", + file, line); + goto err; + } + continue; + } + + if (strncmp(tbuf, "PlaybackPCM", 3) == 0) { + modifier->playback_pcm = atoi(tbuf + 3); + if (modifier->playback_pcm < 0) { + uc_error("error: %s:%d failed to get Playback PCM ID", + file, line); + goto err; + } + continue; + } + + if (strncmp(tbuf, "MasterPlaybackVolume", 20) == 0) { + modifier->playback_volume_id = + get_control_name(tbuf + 20, line, file); + if (modifier->playback_volume_id == NULL) { + uc_error("error: %s:%d failed to get MasterPlaybackVolume", + file, line); + goto err; + } + continue; + } + + if (strncmp(tbuf, "MasterPlaybackSwitch", 20) == 0) { + modifier->playback_switch_id = + get_control_name(tbuf + 20, line, file); + if (modifier->playback_switch_id == NULL) { + uc_error("error: %s:%d failed to get MasterPlaybackSwitch", + file, line); + goto err; + } + continue; + } + + if (strncmp(tbuf, "MasterCaptureVolume", 19) == 0) { + modifier->capture_volume_id = + get_control_name(tbuf + 19, line, file); + if (modifier->capture_volume_id == NULL) { + uc_error("error: %s:%d failed to get MasterCaptureVolume", + file, line); + goto err; + } + continue; + } + + if (strncmp(tbuf, "MasterCaptureSwitch", 19) == 0) { + modifier->capture_switch_id = + get_control_name(tbuf + 19, line, file); + if (modifier->capture_switch_id == NULL) { + uc_error("error: %s:%d failed to get MasterCaptureSwitch", + file, line); + goto err; + } + continue; + } + + /* end of section ?*/ + if (strncmp(tbuf, "EndSection", 10) == 0) { + end = 1; + break; + } + } + + /* do we have the modifier basics ? */ + if (!en_seq || !dis_seq || !end || !dev) { + uc_error("error: invalid modifier"); + if (!en_seq) + uc_error("error: %s: modifier missing enable sequence", + file); + if (!dis_seq) + uc_error("error: %s: modifier missing disable sequence", + file); + if (!dev) + uc_error("error: %s: modifier missing supported device" + " sequence", file); + if (!end) + uc_error("error: %s: modifier missing end", file); + return -EINVAL; + } + + verb->modifier_list[verb->num_modifiers++] = modifier->name; + *line_ = line; + return 0; + +err: + if (ferror(f)) { + uc_error("error: %s: failed to read modifier master section", + file); + return ferror(f); + } + return -ENOMEM; +} + +/* + * Parse Device Use Cases + * + *# Each device is described in new section. N devices are allowed + *SectionDevice + * + * Name "Headphones" + * Comment "Headphones connected to 3.5mm jack" + * Index 0 + * + * EnableSequence + * .... + * EndSequence + * + * DisableSequence + * ... + * EndSequence + * + * #Optional control aliases + * MasterPlaybackVolume 'Device Master Playback Volume' + * MasterPlaybackSwitch 'Device Master Playback Switch' + * + * EndSection + */ +static int parse_device(snd_use_case_mgr_t *uc_mgr, + struct use_case_verb *verb, FILE *f, int *line_, char *file) +{ + struct use_case_device *device; + int line = *line_, end = 0, en_seq = 0, dis_seq = 0, id = 0, ret; + char buf[MAX_BUF], *tbuf; + char *name = NULL, *comment; + + /* allocate device */ + verb->device = realloc(verb->device, + (verb->num_devices + 1) * sizeof(struct use_case_device)); + if (verb->device == NULL) + return -ENOMEM; + device = verb->device + verb->num_devices; + bzero(device, sizeof(struct use_case_device)); + + /* read line by line */ + while(fgets(buf, MAX_BUF, f) != NULL) { + + line++; + + /* skip comments */ + if (buf[0] == '#') + continue; + + tbuf = buf; + /* skip spaces */ + while (isblank(*tbuf)) + tbuf++; + + /* get use case device name */ + if (strncmp(tbuf, "Name", 4) == 0) { + name = get_string(tbuf + 4); + if (name == NULL) { + uc_error("error: %s:%d failed to get device name", + file, line); + goto err; + } + device->name = name; + id = 1; + continue; + } + + if (strncmp(tbuf, "Comment", 8) == 0) { + comment = get_string(tbuf + 8); + if (name == NULL) { + uc_error("error: %s: %d failed to get device comment", + file, line); + goto err; + } + + device->comment = comment; + continue; + } + + if (strncmp(tbuf, "Index", 5) == 0) { + device->idx = atoi(tbuf + 5); + if (device->idx < 0) { + uc_error("error: %s:%d failed to get device index", + file, line); + goto err; + } + continue; + } + + if (strncmp(tbuf, "EnableSequence", 14) == 0) { + ret = parse_sequence(uc_mgr, &device->enable, f, + &line, file); + if (ret < 0) { + uc_error("error: %s:%d failed to parse device enable" + " sequence", file, line); + goto err; + } + en_seq = 1; + continue; + } + + if (strncmp(tbuf, "DisableSequence", 15) == 0) { + uc_dbg("DisableSequence"); + ret = parse_sequence(uc_mgr, &device->disable, f, + &line, file); + if (ret < 0) { + uc_error("error: %s:%d failed to parse device disable sequence", + file, line); + goto err; + } + dis_seq = 1; + continue; + } + + if (strncmp(tbuf, "TransitionDevice", 14) == 0) { + struct transition_sequence *trans_seq; + + name = get_string(tbuf + 14); + if (name == NULL) + continue; + + uc_dbg("TransitionDevice %s", name); + + trans_seq = calloc(1, sizeof(struct transition_sequence)); + if (trans_seq == NULL) + return -ENOMEM; + + trans_seq->name = name; + + ret = parse_sequence(uc_mgr, &trans_seq->transition, f, + &line, file); + if (ret < 0) { + uc_error("error: %s:%d failed to parse transition" + " device", file, line); + goto err; + } + + prepend_transition(&device->transition_list, trans_seq); + continue; + } + + if (strncmp(tbuf, "MasterPlaybackVolume", 20) == 0) { + device->playback_volume_id = + get_control_name(tbuf + 20, line, file); + if (device->playback_volume_id == NULL) { + uc_error("error: %s:%d failed to get MasterPlaybackVolume", + file, line); + goto err; + } + continue; + } + + if (strncmp(tbuf, "MasterPlaybackSwitch", 20) == 0) { + device->playback_switch_id = + get_control_name(tbuf + 20, line, file); + if (device->playback_switch_id == NULL) { + uc_error("error: %s:%d failed to get MasterPlaybackSwitch", + file, line); + goto err; + } + continue; + } + + if (strncmp(tbuf, "MasterCaptureVolume", 19) == 0) { + device->capture_volume_id = + get_control_name(tbuf + 19, line, file); + if (device->capture_volume_id == NULL) { + uc_error("error: %s:%d failed to get MasterCaptureVolume%d", + file, line); + goto err; + } + continue; + } + + if (strncmp(tbuf, "MasterCaptureSwitch", 19) == 0) { + device->capture_switch_id = + get_control_name(tbuf + 19, line, file); + if (device->capture_switch_id == NULL) { + uc_error("error: %s:%d failed to get MasterCaptureSwitch", + file, line); + goto err; + } + continue; + } + + /* end of section ?*/ + if (strncmp(tbuf, "EndSection", 10) == 0) { + end = 1; + break; + } + } + + /* do we have the basics for this device ? */ + if (!en_seq || !dis_seq || !end || !id) { + uc_error("error: invalid device"); + if (!en_seq) + uc_error("error: %s: device missing enable sequence", file); + if (!dis_seq) + uc_error("error: %s: device missing disable sequence", file); + if (!end) + uc_error("error: %s: device missing end", file); + return -EINVAL; + } + + verb->device_list[verb->num_devices++] = device->name; + *line_ = line; + return 0; + +err: + if (ferror(f)) { + uc_error("%s: failed to read device section", file); + return ferror(f); + } + return -ENOMEM; +} + +/* + * Parse Verb Section + * + * # Example Use case verb section for Voice call blah + * # By Joe Blogs + * + * SectionVerb + * # enable and disable sequences are compulsory + * EnableSequence + * 'Master Playback Switch':2:0,0 + * 'Master Playback Volume':2:25,25 + * msleep 50 + * 'Master Playback Switch':2:1,1 + * 'Master Playback Volume':2:50,50 + * EndSequence + * + * DisableSequence + * 'Master Playback Switch':2:0,0 + * 'Master Playback Volume':2:25,25 + * msleep 50 + * 'Master Playback Switch':2:1,1 + * 'Master Playback Volume':2:50,50 + * EndSequence + * + * # Optional QoS and ALSA PCMs + * QoS HiFi + * CapturePCM 0 + * PlaybackPCM 0 + * + * EndSection + */ +static int parse_verb(snd_use_case_mgr_t *uc_mgr, + struct use_case_verb *verb, FILE *f, int *line_, char *file) +{ + int line = *line_, end = 0, en_seq = 0, dis_seq = 0, ret; + char buf[MAX_BUF], *tbuf; + + /* read line by line */ + while(fgets(buf, MAX_BUF, f) != NULL) { + + line++; + + /* skip comments */ + if (buf[0] == '#') + continue; + + tbuf = buf; + /* skip spaces */ + while (isblank(*tbuf)) + tbuf++; + + if (strncmp(tbuf, "EnableSequence", 14) == 0) { + uc_dbg("Parse EnableSequence"); + ret = parse_sequence(uc_mgr, &verb->enable, f, &line, file); + if (ret < 0) { + uc_error("error: %s:%d failed to parse verb enable sequence", + file, line); + goto err; + } + en_seq = 1; + continue; + } + + if (strncmp(tbuf, "DisableSequence", 15) == 0) { + uc_dbg("Parse DisableSequence"); + ret = parse_sequence(uc_mgr, &verb->disable, f, &line, file); + if (ret < 0) { + uc_error("error: %s:%d failed to parse verb disable sequence", + file, line); + goto err; + } + dis_seq = 1; + continue; + } + + if (strncmp(tbuf, "TransitionVerb", 12) == 0) { + struct transition_sequence *trans_seq; + char *name; + + name = get_string(tbuf + 12); + if (name == NULL) + continue; + + uc_dbg("TransitionVerb %s", name); + + trans_seq = calloc(1, sizeof(struct transition_sequence)); + if (trans_seq == NULL) + return -ENOMEM; + + trans_seq->name = name; + + ret = parse_sequence(uc_mgr, &trans_seq->transition, f, + &line, file); + if (ret < 0) { + uc_error("error: %s:%d failed to parse transition verb", + file, line); + goto err; + } + + prepend_transition(&verb->transition_list, trans_seq); + continue; + } + + if (strncmp(tbuf, "QoS", 3) == 0) { + uc_dbg("Parse Qos"); + verb->qos = get_enum(tbuf + 3); + continue; + } + + if (strncmp(tbuf, "CapturePCM", 3) == 0) { + uc_dbg("Parse CapTurePCM"); + verb->capture_pcm = atoi(tbuf + 3); + if (verb->capture_pcm < 0) { + uc_error("error: %s:%d failed to get Capture PCM ID", + file, line); + goto err; + } + continue; + } + + if (strncmp(tbuf, "PlaybackPCM", 3) == 0) { + uc_dbg("Parse PlaybackPCM"); + verb->playback_pcm = atoi(tbuf + 3); + if (verb->playback_pcm < 0) { + uc_error("error: %s: %d failed to get Playback PCM ID", + file, line); + goto err; + } + continue; + } + + /* end of section ?*/ + if (strncmp(tbuf, "EndSection", 10) == 0) { + end = 1; + break; + } + } + + /* do we have both use case name and file ? */ + if (!en_seq || !dis_seq || !end) { + uc_error("error: invalid verb"); + if (!en_seq) + uc_error("error: %s: verb missing enable sequence", file); + if (!dis_seq) + uc_error("error: %s: verb missing disable sequence", file); + if (!end) + uc_error("error: %s: verb missing end", file); + return -EINVAL; + } + + *line_ = line; + return 0; + +err: + if (ferror(f)) { + uc_error("error: failed to read verb master section"); + return ferror(f); + } + return -ENOMEM; +} + +/* + * 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, + char *use_case_name, char *file) +{ + struct use_case_verb *verb; + FILE *f; + int line = 0, ret = 0, dev_idx = 0, verb_idx = 0, mod_idx = 0; + char buf[MAX_BUF], *tbuf; + char filename[MAX_FILE]; + + /* allocate verb */ + uc_mgr->verb = realloc(uc_mgr->verb, + (uc_mgr->num_verbs + 1) * sizeof(struct use_case_verb)); + if (uc_mgr->verb == NULL) + return -ENOMEM; + verb = uc_mgr->verb +uc_mgr->num_verbs; + bzero(verb, sizeof(struct use_case_verb)); + + /* open Verb file for reading */ + sprintf(filename, "%s/%s/%s", ALSA_USE_CASE_DIR, + uc_mgr->card_name, file); + + f = fopen(filename, "r"); + if (f == NULL) { + uc_error("error: failed to open verb file %s : %d", + filename, -errno); + return -errno; + } + + /* read line by line */ + while(fgets(buf, MAX_BUF, f) != NULL) { + + line++; + + /* skip comments */ + if (buf[0] == '#') + continue; + + /* skip leading spaces */ + tbuf = buf; + while (isblank(*tbuf)) + tbuf++; + + /* find verb section and parse it */ + if (strncmp(tbuf, "SectionVerb", 11) == 0) { + ret = parse_verb(uc_mgr, verb, f, &line, file); + if (ret < 0) { + uc_error("error: %s:%d failed to parse verb %s", + file, line, use_case_name); + goto err; + } + verb_idx++; + continue; + } + + /* find device sections and parse them */ + if (strncmp(tbuf, "SectionDevice", 13) == 0) { + + if (verb->num_devices >= MAX_DEVICE) { + uc_error("error: %s:%d verb number of devices %d" + "exceeds max %d", file, line, + verb->num_devices, MAX_DEVICE); + goto err; + } + ret = parse_device(uc_mgr, verb, f, &line, file); + if (ret < 0) { + uc_error("error: %s:%d failed to parse device", + file, line); + goto err; + } + dev_idx++; + continue; + } + + /* find modifier sections and parse them */ + if (strncmp(tbuf, "SectionModifier", 15) == 0) { + if (verb->num_modifiers >= MAX_MODIFIER) { + uc_error("error: %s:%d verb number of modifiers %d" + " exceeds max %d", file, line, + verb->num_modifiers, MAX_MODIFIER); + goto err; + } + + ret = parse_modifier(uc_mgr, verb, f, &line, file); + if (ret < 0) { + uc_error("error: %s:%d failed to parse modifier", + file, line); + goto err; + } + mod_idx++; + continue; + } + } + + /* use case verb must have at least verb and 1 device */ + if (verb_idx && dev_idx) { + uc_mgr->verb_list[uc_mgr->num_verbs++] = use_case_name; + verb->name = use_case_name; + } + else { + uc_error("error: failed to parse use case %s", file); + if (verb_idx == 0) + uc_error("error: no use case verb defined", file); + if (dev_idx == 0) + uc_error("error: no use case device defined", file); + ret = -EINVAL; + } + +err: + fclose(f); + return ret; +} + +/* + * Parse master section for "Use Case" and "File" tags. + */ +static int parse_master_section(snd_use_case_mgr_t *uc_mgr, FILE *f, + int *line_) +{ + int line = *line_ - 1, end = 0; + char buf[MAX_BUF], *tbuf; + char *file = NULL, *use_case_name = NULL, *comment; + + /* read line by line */ + while(fgets(buf, MAX_BUF, f) != NULL) { + + line++; + + /* skip comments */ + if (buf[0] == '#') + continue; + + uc_dbg("%s", buf); + + /* skip leading spaces */ + tbuf = buf; + while (isblank(*tbuf)) + tbuf++; + + /* get use case name */ + if (strncmp(tbuf, "Use Case", 8) == 0) { + use_case_name = get_string(tbuf + 8); + if (use_case_name == NULL) { + uc_error("error: failed to get Use Case at line %d", + line); + goto err; + } + + if (uc_mgr->num_verbs >= MAX_VERB) { + uc_error("error: verb number exceed max %d", + uc_mgr->num_verbs, MAX_VERB); + goto err; + } + continue; + } + + /* get use case verb file name */ + if (strncmp(tbuf, "File", 4) == 0) { + file = get_string(tbuf + 4); + if (file == NULL) { + uc_error("error: failed to get File at line %d", line); + goto err; + } + continue; + } + + /* get optional use case comment */ + if (strncmp(tbuf, "Comment", 7) == 0) { + comment = get_string(tbuf + 7); + if (comment == NULL) { + uc_error("error: failed to get Comment at line %d", + line); + goto err; + } + continue; + } + + /* end of section ?*/ + if (strncmp(tbuf, "EndSectionUseCase", 10) == 0) { + end = 1; + break; + } + } + + 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 (!use_case_name || !file || !end) { + uc_error("error: failed to find use case\n"); + if (!use_case_name) + uc_error("error: use case missing name"); + if (!file) + uc_error("error: use case missing file"); + if (!end) + uc_error("error: use case missing end"); + return -EINVAL; + } + + *line_ = line; + + /* parse verb file */ + return parse_verb_file(uc_mgr, use_case_name, file); + +err: + if (ferror(f)) { + uc_error("error: failed to read use case master section"); + return ferror(f); + } + return -ENOMEM; +} + +/* + * 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 + * + * # The file is divided into Use case sections. One section per use case verb. + * + * SectionUseCase + * Use Case "Voice Call" + * File "voice_call_blah" + * Comment "Make a voice phone call." + * EndSectionUseCase + * + * SectionUseCase + * Use Case "HiFi" + * File "hifi_blah" + * Comment "Play and record HiFi quality Music." + * EndSectionUseCase + * + * # This file also stores the default sound card state. + * + * SectionDefaults + * 'Master Playback Switch':2:1,1 + * 'Master Playback Volume':2:25,25 + * 'Master Mono Playback Switch':1:0 + * 'Master Mono Playback Volume':1:0 + * 'PCM Switch':2:1,1 + * ........ + * EndSectionDefaults + * + * # End of example file. + */ +static int parse_master_file(snd_use_case_mgr_t *uc_mgr, FILE *f, + char *file) +{ + int line = 0, ret = 0; + char buf[MAX_BUF], *tbuf; + + /* parse master config sections */ + while(fgets(buf, MAX_BUF, f) != NULL) { + + line++; + + /* ignore comments */ + if (buf[0] == '#') { + line++; + continue; + } + + /* find use case section and parse it */ + if (strncmp(buf, "SectionUseCase", 14) == 0) { + tbuf = buf + 14; + while (isblank(*tbuf)) + tbuf++; + ret = parse_master_section(uc_mgr, f, &line); + if (ret < 0) + goto err; + } + + /* find default control values section and parse it */ + if (strncmp(buf, "SectionDefaults", 15) == 0) { + tbuf = buf + 15; + while (isblank(*tbuf)) + tbuf++; + ret = parse_controls(uc_mgr, f, &line, file); + if (ret < 0) + goto err; + } + } + +err: + if (ferror(f)) { + uc_error("error: %s: failed to read master file", file); + return ferror(f); + } + return ret; +} + +/* load master use case file for sound card */ +static int import_master_config(snd_use_case_mgr_t *uc_mgr) +{ + int ret; + FILE *f; + char filename[MAX_FILE]; + + sprintf(filename, "%s/%s/%s.conf", ALSA_USE_CASE_DIR, + uc_mgr->card_name, uc_mgr->card_name); + + uc_dbg("master config file %s", filename); + + f = fopen(filename, "r"); + if (f == NULL) { + uc_error("error: couldn't open %s configuration file %s", + uc_mgr->card_name, filename); + return -errno; + } + + ret = parse_master_file(uc_mgr, f, uc_mgr->card_name); + fclose(f); + return ret; +} + +static int parse_card_controls(snd_use_case_mgr_t *uc_mgr) +{ + struct control_settings *control; + int i, ret = 0; + snd_ctl_elem_id_t *id; + + /* allocate memory for controls */ + uc_mgr->control = calloc(uc_mgr->count, + sizeof(struct control_settings)); + if (uc_mgr->control == NULL) { + uc_error("error: not enough memory to store controls.\n"); + return -ENOMEM; + } + control = uc_mgr->control; + snd_ctl_elem_id_alloca(&id); + + /* iterate through each kcontrol and add to manager */ + for (i = 0; i < uc_mgr->count; ++i) { + + snd_ctl_elem_list_get_id(uc_mgr->list, i, id); + ret = add_control(uc_mgr->handle, id, control++); + if (ret < 0) { + uc_error("error: failed to add control error %s\n", + __func__, snd_strerror(ret)); + break; + } + } + + return ret; +} + +static int import_use_case_files(snd_use_case_mgr_t *uc_mgr) +{ + int ret; + + /* import the master file and default kcontrol settings */ + ret = import_master_config(uc_mgr); + if (ret < 0) { + uc_error("error: failed to parse master use case config %s\n", + uc_mgr->card_name); + return ret; + } + + return 0; +} + + +static void free_sequence(struct sequence_element *sequence) +{ + struct sequence_element *element = sequence; + + while (element) { + struct sequence_element *pre_element; + + if (element->control) { + free(element->control->value); + free(element->control); + } + pre_element = element; + element = element->next; + free(pre_element); + } +} + +static void free_transition_sequence_element(struct transition_sequence *trans) +{ + free(trans->name); + free_sequence(trans->transition); + free(trans); +} + +static void free_transition_sequence(struct transition_sequence *transition_list) +{ + struct transition_sequence *last, *trans_sequence = transition_list; + + while (trans_sequence) { + last = trans_sequence; + trans_sequence = trans_sequence->next; + free_transition_sequence_element(last); + } +} + +static void free_modifier(struct use_case_modifier *modifier) +{ + if (!modifier) + return; + + free(modifier->name); + free(modifier->comment); + free(modifier->playback_volume_id); + free(modifier->playback_switch_id); + free(modifier->capture_volume_id); + free(modifier->capture_switch_id); + + free_sequence(modifier->enable); + free_sequence(modifier->disable); + free_transition_sequence(modifier->transition_list); +} + +static void free_device(struct use_case_device *device) +{ + if (!device) + return; + + free(device->name); + free(device->comment); + free(device->playback_volume_id); + free(device->playback_switch_id); + free(device->capture_volume_id); + free(device->capture_switch_id); + + free_sequence(device->enable); + free_sequence(device->disable); + free_transition_sequence(device->transition_list); +} + +static void free_devices(struct use_case_device *devices, int num_devices) +{ + int i; + + if (!devices) + return; + + for (i = 0; i< num_devices; i++) + free_device(devices + i); + + free(devices); +} + +static void free_modifiers(struct use_case_modifier *modifiers, + int num_modifiers) +{ + int i; + + if (!modifiers) + return; + + for (i = 0; i < num_modifiers; i++) + free_modifier(modifiers + i); + + free(modifiers); +} + +static void free_verb(struct use_case_verb *verb) +{ + if (!verb) + return; + + free(verb->name); + free(verb->comment); + + free_sequence(verb->enable); + free_sequence(verb->disable); + free_transition_sequence(verb->transition_list); + + free_devices(verb->device, verb->num_devices); + free_modifiers(verb->modifier, verb->num_modifiers); +} + +static void free_verbs(struct use_case_verb *verbs, int num_verbs) +{ + int i; + + if (!verbs) + return; + + for (i = 0; i < num_verbs; i++) + free_verb(verbs + i); + + free(verbs); +} + +static void free_controls(struct control_settings *controls, int num_controls) +{ + int i = 0; + + if (!controls) + return; + + for(i = 0; i < num_controls; i++){ + struct control_settings *control = controls + i; + free(control->value); + } + free(controls); +} + +/* + * Free all use case manager resources. + * Callers holds locks. + */ +static void free_uc_mgr(snd_use_case_mgr_t *uc_mgr) +{ + if (uc_mgr == NULL) + return; + + if (uc_mgr->info) + snd_ctl_card_info_free(uc_mgr->info); + if (uc_mgr->list) + snd_ctl_elem_list_free(uc_mgr->list); + if (uc_mgr->id) + snd_ctl_elem_id_free(uc_mgr->id); + + if (uc_mgr->handle) + snd_ctl_close(uc_mgr->handle); + + free(uc_mgr->card_name); + free(uc_mgr->ctl_name); + + free_verbs(uc_mgr->verb, uc_mgr->num_verbs); + + free_controls(uc_mgr->control, uc_mgr->count); + + pthread_mutex_destroy(&uc_mgr->mutex); + + free(uc_mgr); +} + + /** + * \brief Init sound card use case manager. + * \param uc_mgr Use case manager + * \return zero on success, otherwise a negative error code + */ +snd_use_case_mgr_t *snd_use_case_mgr_open(const char *card_name) +{ + snd_use_case_mgr_t *uc_mgr; + char ctl_name[8]; + int err, idx; + + idx = snd_card_get_index(card_name); + if (idx < 0) { + uc_error("error: can't get sound card %s: %s", + card_name, snd_strerror(idx)); + return NULL; + } + + /* create a new UCM */ + uc_mgr = calloc(1, sizeof(snd_use_case_mgr_t)); + if (uc_mgr == NULL) + return NULL; + + uc_mgr->card_name = strdup(card_name); + if (uc_mgr->card_name == NULL) { + free(uc_mgr); + return NULL; + } + sprintf(ctl_name, "hw:%d", idx); + uc_mgr->ctl_name = strdup(ctl_name); + if (uc_mgr->ctl_name == NULL) { + free_uc_mgr(uc_mgr); + return NULL; + } + if (snd_ctl_card_info_malloc(&uc_mgr->info) < 0) { + free_uc_mgr(uc_mgr); + return NULL; + } + if (snd_ctl_elem_list_malloc(&uc_mgr->list) < 0) { + free_uc_mgr(uc_mgr); + return NULL; + } + if (snd_ctl_elem_id_malloc(&uc_mgr->id) < 0) { + free_uc_mgr(uc_mgr); + return NULL; + } + + /* open and init CTLs */ + err = snd_ctl_open(&uc_mgr->handle, uc_mgr->ctl_name, 0); + if (err) { + uc_error("error: can't open sound card %s: %s", + uc_mgr->card_name, snd_strerror(err)); + goto err; + } + + err = snd_ctl_card_info(uc_mgr->handle, uc_mgr->info); + if (err < 0) { + uc_error("error: can't get sound card %s control info: %s", + uc_mgr->card_name, snd_strerror(err)); + goto err; + } + + err = snd_ctl_elem_list(uc_mgr->handle, uc_mgr->list); + if (err < 0) { + uc_error("error: can't get sound card %s elements: %s", + uc_mgr->card_name, snd_strerror(err)); + goto err; + } + + uc_mgr->count = snd_ctl_elem_list_get_count(uc_mgr->list); + if (uc_mgr->count < 0) { + uc_error("error: can't get sound card %s controls %s:", + uc_mgr->card_name, snd_strerror(err)); + goto err; + } + + snd_ctl_elem_list_set_offset(uc_mgr->list, 0); + if (snd_ctl_elem_list_alloc_space(uc_mgr->list, uc_mgr->count) < 0) { + uc_error("error: could not allocate elements for %s", + uc_mgr->card_name); + goto err; + } + + err = snd_ctl_elem_list(uc_mgr->handle, uc_mgr->list); + if (err < 0) { + uc_error("error: could not get elements for %s : %s", + uc_mgr->card_name, snd_strerror(err)); + goto err; + } + + /* get info about sound card */ + err = parse_card_controls(uc_mgr); + if (err < 0) { + uc_error("error: failed to parse sound device %s controls %d", + card_name, err); + goto err; + } + + /* get info on use_cases and verify against card */ + err = import_use_case_files(uc_mgr); + if (err < 0) { + uc_error("error: failed to import %s use case configuration %d", + card_name, err); + goto err; + } + + pthread_mutex_init(&uc_mgr->mutex, NULL); + uc_mgr->card.current_verb = VERB_NOT_INITIALISED; + + return uc_mgr; + +err: + free_uc_mgr(uc_mgr); + return NULL; +} + + /** + * \brief Reload and reparse all use case files. + * \param uc_mgr Use case manager + * \return zero on success, otherwise a negative error code + */ +int snd_use_case_mgr_reload(snd_use_case_mgr_t *uc_mgr) +{ + int ret; + + pthread_mutex_lock(&uc_mgr->mutex); + + free_verbs(uc_mgr->verb, uc_mgr->num_verbs); + free_controls(uc_mgr->control, uc_mgr->count); + + /* reload all sound card controls */ + ret = parse_card_controls(uc_mgr); + if (ret <= 0) { + uc_error("error: failed to reload sound card controls %d\n", + ret); + free_uc_mgr(uc_mgr); + return -EINVAL; + } + + /* reload all use cases */ + uc_mgr->num_verbs = import_use_case_files(uc_mgr); + if (uc_mgr->num_verbs <= 0) { + uc_error("error: failed to reload use cases\n"); + return -EINVAL; + } + + pthread_mutex_unlock(&uc_mgr->mutex); + return ret; +} + + /** + * \brief Close use case manager. + * \param uc_mgr Use case manager + * \return zero on success, otherwise a negative error code + */ +int snd_use_case_mgr_close(snd_use_case_mgr_t *uc_mgr) +{ + free_uc_mgr(uc_mgr); + + return 0; +} + + /** + * \brief Reset sound card controls to default values. + * \param uc_mgr Use case manager + * \return zero on success, otherwise a negative error code + */ +int snd_use_case_mgr_reset(snd_use_case_mgr_t *uc_mgr) +{ + int ret = 0, i; + struct control_settings *control; + + pthread_mutex_lock(&uc_mgr->mutex); + + for (i = 0; i < uc_mgr->count; i++) { + control = &uc_mgr->control[i]; + + /* Only set default value specified in master config file */ + if (control->value == NULL) + continue; + + ret = set_control_default(uc_mgr, control); + if (ret < 0) + goto out; + } + uc_mgr->card.current_verb = VERB_NOT_INITIALISED; +out: + pthread_mutex_unlock(&uc_mgr->mutex); + return ret; +} + + +/* + * Change a sound card control to a new value. + */ +static int set_control(snd_ctl_t *handle, snd_ctl_elem_id_t *id, + snd_use_case_mgr_t *uc_mgr, unsigned short value[]) +{ + struct control_settings *setting; + int ret, count, i; + unsigned int idnum; + snd_ctl_elem_info_t *info; + snd_ctl_elem_type_t type; + snd_ctl_elem_value_t *control; + + snd_ctl_elem_info_alloca(&info); + snd_ctl_elem_value_alloca(&control); + + snd_ctl_elem_info_set_id(info, id); + ret = snd_ctl_elem_info(handle, info); + if (ret < 0) { + uc_error("error: failed to get ctl elem info %d: ", + snd_strerror(ret)); + return ret; + } + + snd_ctl_elem_value_set_id(control, id); + snd_ctl_elem_read(handle, control); + + idnum = snd_ctl_elem_id_get_numid(id); + for (i = 0; i < uc_mgr->count; i++) { + setting = &uc_mgr->control[i]; + if (setting->id == idnum) + goto set_val; + } + uc_error("error: failed to find control at id %d", idnum); + return 0; + +set_val: + uc_dbg("set control %s id %d count %d", setting->name, setting->id, + setting->count); + + type = snd_ctl_elem_info_get_type(info); + count = snd_ctl_elem_info_get_count(info); + if (count == 0) + return 0; + + uc_dbg("type %d count %d", type, count); + + switch (type) { + case SND_CTL_ELEM_TYPE_BOOLEAN: + for (i = 0; i < count; i++) { + uc_dbg("count %d value %u", i, *(value + i)); + snd_ctl_elem_value_set_boolean(control, i, value[i]); + } + break; + case SND_CTL_ELEM_TYPE_INTEGER: + uc_dbg("int"); + for (i = 0; i < count; i++) { + uc_dbg("count %d value %u", i, value[i]); + snd_ctl_elem_value_set_integer(control, i, value[i]); + } + break; + case SND_CTL_ELEM_TYPE_INTEGER64: + uc_dbg("int64"); + for (i = 0; i < count; i++) { + uc_dbg("count %d value %u", i, value[i]); + snd_ctl_elem_value_set_integer64(control, i, value[i]); + } + + break; + case SND_CTL_ELEM_TYPE_ENUMERATED: + uc_dbg("enumerated"); + for (i = 0; i < count; i++) { + uc_dbg("count %d value %u", i, value[i]); + snd_ctl_elem_value_set_enumerated(control, i, value[i]); + } + break; + case SND_CTL_ELEM_TYPE_BYTES: + uc_dbg("bytes"); + for (i = 0; i < count; i++) { + uc_dbg("count %d value %u", i, value[i]); + snd_ctl_elem_value_set_byte(control, i, value[i]); + } + break; + default: + break; + } + + ret = snd_ctl_elem_write(handle, control); + if (ret < 0) { + uc_error("error: failed to set control %s: %s", + setting->name, snd_strerror(ret)); + uc_error("error: count %d type: %d", + count, type); + for (i = 0; i < count; i++) + fprintf(stderr, "%d ", get_value(setting, i)); + return ret; + } + return 0; +} + +/* + * Execute a sequence of control writes. + */ +static int exec_sequence(struct sequence_element *seq, snd_use_case_mgr_t + *uc_mgr, snd_ctl_elem_list_t *list, snd_ctl_t *handle) +{ + int count = snd_ctl_elem_list_get_count(list); + int ret, i; + + uc_dbg(""); + + /* keep going until end of sequence */ + while (seq) { + /* do we need to sleep */ + if (seq->sleep) { + uc_dbg("msleep %d", seq->sleep); + usleep(seq->sleep); + } else { + uc_dbg("control name %s, id %d, count %d, vale[1] %u", + seq->control->name, seq->control->id, + seq->control->count, seq->control->value[0]); + + /* control write */ + snd_ctl_elem_id_t *id; + snd_ctl_elem_id_alloca(&id); + unsigned int numid; + + /* Where is id lookup from numid if you need it? */ + for (i = 0; i < count; ++i) { + + snd_ctl_elem_list_get_id(list, i, id); + numid = snd_ctl_elem_id_get_numid(id); + + if (numid == seq->control->id) { + ret = set_control(handle, id, uc_mgr, seq->control->value); + if (ret < 0) { + uc_error("error: failed to set control %s", + __func__, uc_mgr->card_name); + return ret; + } + break; + } + } + } + seq = seq->next; + } + return 0; +} + +static int enable_use_case_verb(snd_use_case_mgr_t *uc_mgr, int verb_id, + snd_ctl_elem_list_t *list, snd_ctl_t *handle) +{ + struct use_case_verb *verb; + int ret; + + if (verb_id >= uc_mgr->num_verbs) { + uc_error("error: invalid verb id %d", verb_id); + return -EINVAL; + } + verb = &uc_mgr->verb[verb_id]; + + uc_dbg("verb %s", verb->name); + ret = exec_sequence(verb->enable, uc_mgr, list, handle); + if (ret < 0) { + uc_error("error: could not enable verb %s", verb->name); + return ret; + } + uc_mgr->card.current_verb = verb_id; + + return 0; +} + +static int disable_use_case_verb(snd_use_case_mgr_t *uc_mgr, int verb_id, + snd_ctl_elem_list_t *list, snd_ctl_t *handle) +{ + struct use_case_verb *verb; + int ret; + + if (verb_id >= uc_mgr->num_verbs) { + uc_error("error: invalid verb id %d", verb_id); + return -EINVAL; + } + verb = &uc_mgr->verb[verb_id]; + + /* we set the invalid verb at open() but we should still + * check that this succeeded */ + if (verb == NULL) + return 0; + + uc_dbg("verb %s", verb->name); + ret = exec_sequence(verb->disable, uc_mgr, list, handle); + if (ret < 0) { + uc_error("error: could not disable verb %s", verb->name); + return ret; + } + + return 0; +} + +static int enable_use_case_device(snd_use_case_mgr_t *uc_mgr, + int device_id, snd_ctl_elem_list_t *list, snd_ctl_t *handle) +{ + struct use_case_verb *verb = &uc_mgr->verb[uc_mgr->card.current_verb]; + struct use_case_device *device = &verb->device[device_id]; + int ret; + + if (uc_mgr->card.current_verb == VERB_NOT_INITIALISED) + return -EINVAL; + + uc_dbg("device %s", device->name); + ret = exec_sequence(device->enable, uc_mgr, list, handle); + if (ret < 0) { + uc_error("error: could not enable device %s", device->name); + return ret; + } + + set_device_status(uc_mgr, device_id, 1); + return 0; +} + +static int disable_use_case_device(snd_use_case_mgr_t *uc_mgr, + int device_id, snd_ctl_elem_list_t *list, snd_ctl_t *handle) +{ + struct use_case_verb *verb = &uc_mgr->verb[uc_mgr->card.current_verb]; + struct use_case_device *device = &verb->device[device_id]; + int ret; + + if (uc_mgr->card.current_verb == VERB_NOT_INITIALISED) + return -EINVAL; + + uc_dbg("device %s", device->name); + ret = exec_sequence(device->disable, uc_mgr, list, handle); + if (ret < 0) { + uc_error("error: could not disable device %s", device->name); + return ret; + } + + set_device_status(uc_mgr, device_id, 0); + return 0; +} + +static int enable_use_case_modifier(snd_use_case_mgr_t *uc_mgr, + int modifier_id, snd_ctl_elem_list_t *list, snd_ctl_t *handle) +{ + struct use_case_verb *verb = &uc_mgr->verb[uc_mgr->card.current_verb]; + struct use_case_modifier *modifier = &verb->modifier[modifier_id]; + int ret; + + if (uc_mgr->card.current_verb == VERB_NOT_INITIALISED) + return -EINVAL; + + uc_dbg("modifier %s", modifier->name); + ret = exec_sequence(modifier->enable, uc_mgr, list, handle); + if (ret < 0) { + uc_error("error: could not enable modifier %s", modifier->name); + return ret; + } + + set_modifier_status(uc_mgr, modifier_id, 1); + return 0; +} + +static int disable_use_case_modifier(snd_use_case_mgr_t *uc_mgr, + int modifier_id, snd_ctl_elem_list_t *list, snd_ctl_t *handle) +{ + struct use_case_verb *verb = &uc_mgr->verb[uc_mgr->card.current_verb]; + struct use_case_modifier *modifier = &verb->modifier[modifier_id]; + int ret; + + if (uc_mgr->card.current_verb == VERB_NOT_INITIALISED) + return -EINVAL; + + uc_dbg("modifier %s", modifier->name); + ret = exec_sequence(modifier->disable, uc_mgr, list, handle); + if (ret < 0) { + uc_error("error: could not disable modifier %s", modifier->name); + return ret; + } + + set_modifier_status(uc_mgr, modifier_id, 0); + return 0; +} + +/* + * Tear down current use case verb, device and modifier. + */ +static int dismantle_use_case(snd_use_case_mgr_t *uc_mgr, + snd_ctl_elem_list_t *list, snd_ctl_t *handle) +{ + struct use_case_verb *verb = &uc_mgr->verb[uc_mgr->card.current_verb]; + int ret, i; + + /* No active verb */ + if (uc_mgr->card.current_verb == VERB_NOT_INITIALISED) + return 0; + + /* disable all modifiers that are active */ + for (i = 0; i < verb->num_modifiers; i++) { + if (get_modifier_status(uc_mgr,i)) { + ret = disable_use_case_modifier(uc_mgr, i, list, handle); + if (ret < 0) + return ret; + } + } + + /* disable all devices that are active */ + for (i = 0; i < verb->num_devices; i++) { + if (get_device_status(uc_mgr,i)) { + ret = disable_use_case_device(uc_mgr, i, list, handle); + if (ret < 0) + return ret; + } + } + + /* disable verb */ + ret = disable_use_case_verb(uc_mgr, uc_mgr->card.current_verb, list, handle); + if (ret < 0) + return ret; + + return 0; +} + + /** + * \brief Dump sound card controls in format required for sequencer. + * \param card_name The name of the sound card to be dumped + * \return zero on success, otherwise a negative error code + */ +int snd_use_case_dump(const char *card_name) +{ + snd_ctl_t *handle; + snd_ctl_card_info_t *info; + snd_ctl_elem_list_t *list; + int ret, i, count, idx; + char ctl_name[8]; + + snd_ctl_card_info_alloca(&info); + snd_ctl_elem_list_alloca(&list); + + idx = snd_card_get_index(card_name); + if (idx < 0) + return idx; + sprintf(ctl_name, "hw:%d", idx); + + /* open and load snd card */ + ret = snd_ctl_open(&handle, ctl_name, SND_CTL_READONLY); + if (ret < 0) { + uc_error("error: could not open controls for %s: %s", + card_name, snd_strerror(ret)); + return ret; + } + + ret = snd_ctl_card_info(handle, info); + if (ret < 0) { + uc_error("error: could not get control info for %s:%s", + card_name, snd_strerror(ret)); + goto close; + } + + ret = snd_ctl_elem_list(handle, list); + if (ret < 0) { + uc_error("error: cannot determine controls for %s: %s", + card_name, snd_strerror(ret)); + goto close; + } + + count = snd_ctl_elem_list_get_count(list); + if (count < 0) { + ret = 0; + goto close; + } + + snd_ctl_elem_list_set_offset(list, 0); + if (snd_ctl_elem_list_alloc_space(list, count) < 0) { + uc_error("error: not enough memory for control elements"); + ret = -ENOMEM; + goto close; + } + if ((ret = snd_ctl_elem_list(handle, list)) < 0) { + uc_error("error: cannot determine controls: %s", + snd_strerror(ret)); + goto free; + } + + /* iterate through each kcontrol and add to use + * case manager control list */ + for (i = 0; i < count; ++i) { + snd_ctl_elem_id_t *id; + snd_ctl_elem_id_alloca(&id); + snd_ctl_elem_list_get_id(list, i, id); + + /* dump to stdout in friendly format */ + ret = dump_control(handle, id); + if (ret < 0) { + uc_error("error: control dump failed: %s", + snd_strerror(ret)); + goto free; + } + } +free: + snd_ctl_elem_list_free_space(list); +close: + snd_ctl_close(handle); + return ret; +} + +/** + * \brief List supported use case verbs for given soundcard + * \param uc_mgr use case manager + * \param verb returned list of supported use case verb id and names + * \return number of use case verbs if success, otherwise a negative error code + */ +int snd_use_case_get_verb_list(snd_use_case_mgr_t *uc_mgr, + const char **verb[]) +{ + int ret; + + pthread_mutex_lock(&uc_mgr->mutex); + + *verb = uc_mgr->verb_list; + ret = uc_mgr->num_verbs; + + pthread_mutex_unlock(&uc_mgr->mutex); + return ret; +} + +/** + * \brief List supported use case devices for given verb + * \param uc_mgr use case manager + * \param verb verb id. + * \param device returned list of supported use case device id and names + * \return number of use case devices if success, otherwise a negative error code + */ +int snd_use_case_get_device_list(snd_use_case_mgr_t *uc_mgr, + const char *verb_name, const char **device[]) +{ + struct use_case_verb *verb = NULL; + int i, ret = -EINVAL; + + pthread_mutex_lock(&uc_mgr->mutex); + + /* find verb name */ + for (i = 0; i < uc_mgr->num_verbs; i++) { + verb = &uc_mgr->verb[i]; + if (!strcmp(uc_mgr->verb[i].name, verb_name)) + goto found; + } + + uc_error("error: use case verb %s not found", verb_name); + goto out; + +found: + *device = verb->device_list; + ret = verb->num_devices; +out: + pthread_mutex_unlock(&uc_mgr->mutex); + return ret; +} + +/** + * \brief List supported use case verb modifiers for given verb + * \param uc_mgr use case manager + * \param verb verb id. + * \param mod returned list of supported use case modifier id and names + * \return number of use case modifiers if success, otherwise a negative error code + */ +int snd_use_case_get_mod_list(snd_use_case_mgr_t *uc_mgr, + const char *verb_name, const char **mod[]) +{ + struct use_case_verb *verb = NULL; + int i, ret = -EINVAL; + + pthread_mutex_lock(&uc_mgr->mutex); + + /* find verb name */ + for (i = 0; i num_verbs; i++) { + verb = &uc_mgr->verb[i]; + if (!strcmp(uc_mgr->verb[i].name, verb_name)) + goto found; + } + + uc_error("error: use case verb %s not found", verb_name); + goto out; + +found: + *mod = verb->modifier_list; + ret = verb->num_modifiers; +out: + pthread_mutex_unlock(&uc_mgr->mutex); + return ret; +} + +static struct sequence_element *get_transition_sequence( + struct transition_sequence *trans_list, const char *name) +{ + struct transition_sequence *trans = trans_list; + + while (trans) { + if (trans->name && !strcmp(trans->name, name)) + return trans->transition; + + trans = trans->next; + } + + return NULL; +} + +static int exec_transition_sequence(snd_use_case_mgr_t *uc_mgr, + struct sequence_element *trans_sequence) +{ + int ret; + + ret = exec_sequence(trans_sequence, uc_mgr, uc_mgr->list, + uc_mgr->handle); + if (ret < 0) + uc_error("error: could not exec transition sequence"); + + return ret; +} + +static int handle_transition_verb(snd_use_case_mgr_t *uc_mgr, + int new_verb_id) +{ + struct use_case_verb *old_verb = &uc_mgr->verb[uc_mgr->card.current_verb]; + struct use_case_verb *new_verb; + static struct sequence_element *trans_sequence; + + if (uc_mgr->card.current_verb == VERB_NOT_INITIALISED) + return -EINVAL; + + if (new_verb_id >= uc_mgr->num_verbs) { + uc_error("error: invalid new_verb id %d", new_verb_id); + return -EINVAL; + } + + new_verb = &uc_mgr->verb[new_verb_id]; + + uc_dbg("new verb %s", new_verb->name); + + trans_sequence = get_transition_sequence(old_verb->transition_list, + new_verb->name); + if (trans_sequence != NULL) { + int ret, i; + + uc_dbg("find transition sequence %s->%s", + old_verb->name, new_verb->name); + + /* disable all modifiers that are active */ + for (i = 0; i < old_verb->num_modifiers; i++) { + if (get_modifier_status(uc_mgr,i)) { + ret = disable_use_case_modifier(uc_mgr, i, + uc_mgr->list, uc_mgr->handle); + if (ret < 0) + return ret; + } + } + + /* disable all devices that are active */ + for (i = 0; i < old_verb->num_devices; i++) { + if (get_device_status(uc_mgr,i)) { + ret = disable_use_case_device(uc_mgr, i, + uc_mgr->list, uc_mgr->handle); + if (ret < 0) + return ret; + } + } + + ret = exec_transition_sequence(uc_mgr, trans_sequence); + if (ret) + return ret; + + uc_mgr->card.current_verb = new_verb_id; + + return 0; + } + + return-EINVAL; +} + +/** + * \brief Set new use case verb for sound card + * \param uc_mgr use case manager + * \param verb verb id + * \return zero if success, otherwise a negative error code + */ +int snd_use_case_set_verb(snd_use_case_mgr_t *uc_mgr, + const char *verb_name) +{ + int i = 0, ret = -EINVAL, inactive = 0; + + pthread_mutex_lock(&uc_mgr->mutex); + + uc_dbg("uc_mgr %p, verb_name %s", uc_mgr, verb_name); + + /* check for "Inactive" */ + if (!strcmp(verb_name, SND_USE_CASE_VERB_INACTIVE)) { + inactive = 1; + goto found; + } + + /* find verb name */ + for (i = 0; i num_verbs; i++) { + if (!strcmp(uc_mgr->verb[i].name, verb_name)) + goto found; + } + + uc_error("error: use case verb %s not found", verb_name); + goto out; +found: + /* use case verb found - check that we actually changing the verb */ + if (i == uc_mgr->card.current_verb) { + uc_dbg("current verb ID %d", i); + ret = 0; + goto out; + } + + if (handle_transition_verb(uc_mgr, i) == 0) + goto out; + + /* + * Dismantle the old use cases by running it's verb, device and modifier + * disable sequences + */ + ret = dismantle_use_case(uc_mgr, uc_mgr->list, uc_mgr->handle); + if (ret < 0) { + uc_error("error: failed to dismantle current use case: %s", + uc_mgr->verb[i].name); + goto out; + } + + /* we don't need to initialise new verb if inactive */ + if (inactive) + goto out; + + /* Initialise the new use case verb */ + ret = enable_use_case_verb(uc_mgr, i, uc_mgr->list, uc_mgr->handle); + if (ret < 0) + uc_error("error: failed to initialise new use case: %s", + verb_name); +out: + pthread_mutex_unlock(&uc_mgr->mutex); + + return ret; +} + +static int config_use_case_device(snd_use_case_mgr_t *uc_mgr, + const char *device_name, int enable) +{ + struct use_case_verb *verb; + int ret, i; + + pthread_mutex_lock(&uc_mgr->mutex); + + if (uc_mgr->card.current_verb == VERB_NOT_INITIALISED) { + uc_error("error: no valid use case verb set\n"); + ret = -EINVAL; + goto out; + } + + verb = &uc_mgr->verb[uc_mgr->card.current_verb]; + + uc_dbg("current verb %s", verb->name); + uc_dbg("uc_mgr %p device_name %s", uc_mgr, device_name); + + /* find device name and index */ + for (i = 0; i num_devices; i++) { + uc_dbg("verb->num_devices %s", verb->device[i].name); + if (!strcmp(verb->device[i].name, device_name)) + goto found; + } + + uc_error("error: use case device %s not found", device_name); + ret = -EINVAL; + goto out; + +found: + if (enable) { + /* Initialise the new use case device */ + ret = enable_use_case_device(uc_mgr, i, uc_mgr->list, + uc_mgr->handle); + if (ret < 0) + goto out; + } else { + /* disable the old device */ + ret = disable_use_case_device(uc_mgr, i, uc_mgr->list, + uc_mgr->handle); + if (ret < 0) + goto out; + } + +out: + pthread_mutex_unlock(&uc_mgr->mutex); + return ret; +} + +/** + * \brief Enable use case device + * \param uc_mgr Use case manager + * \param device the device to be enabled + * \return 0 = successful negative = error + */ +int snd_use_case_enable_device(snd_use_case_mgr_t *uc_mgr, + const char *device) +{ + return config_use_case_device(uc_mgr, device, 1); +} + +/** + * \brief Disable use case device + * \param uc_mgr Use case manager + * \param device the device to be disabled + * \return 0 = successful negative = error + */ +int snd_use_case_disable_device(snd_use_case_mgr_t *uc_mgr, + const char *device) +{ + return config_use_case_device(uc_mgr, device, 0); +} + +static struct use_case_device *get_device(snd_use_case_mgr_t *uc_mgr, + const char *name, int *id) +{ + struct use_case_verb *verb; + int i; + + if (uc_mgr->card.current_verb == VERB_NOT_INITIALISED) + return NULL; + + verb = &uc_mgr->verb[uc_mgr->card.current_verb]; + + uc_dbg("current verb %s", verb->name); + + for (i = 0; i < verb->num_devices; i++) { + uc_dbg("device %s", verb->device[i].name); + + if (!strcmp(verb->device[i].name, name)) { + if (id) + *id = i; + return &verb->device[i]; + } + } + + return NULL; +} + +/** + * \brief Disable old_device and then enable new_device. + * If from_device is not enabled just return. + * Check transition sequence firstly. + * \param uc_mgr Use case manager + * \param old the device to be closed + * \param new the device to be opened + * \return 0 = successful negative = error + */ +int snd_use_case_switch_device(snd_use_case_mgr_t *uc_mgr, + const char *old, const char *new) +{ + static struct sequence_element *trans_sequence; + struct use_case_device *old_device; + struct use_case_device *new_device; + int ret = 0, old_id, new_id; + + uc_dbg("old %s, new %s", old, new); + + pthread_mutex_lock(&uc_mgr->mutex); + + old_device = get_device(uc_mgr, old, &old_id); + if (!old_device) { + uc_error("error: device %s not found", old); + ret = -EINVAL; + goto out; + } + + if (!get_device_status(uc_mgr, old_id)) { + uc_error("error: device %s not enabled", old); + goto out; + } + + new_device = get_device(uc_mgr, new, &new_id); + if (!new_device) { + uc_error("error: device %s not found", new); + ret = -EINVAL; + goto out; + } + + trans_sequence = get_transition_sequence(old_device->transition_list, new); + if (trans_sequence != NULL) { + + uc_dbg("find transition sequece %s->%s", old, new); + + ret = exec_transition_sequence(uc_mgr, trans_sequence); + if (ret) + goto out; + + set_device_status(uc_mgr, old_id, 0); + set_device_status(uc_mgr, new_id, 1); + } else { + /* use lock in config_use_case_device */ + pthread_mutex_unlock(&uc_mgr->mutex); + + config_use_case_device(uc_mgr, old, 0); + config_use_case_device(uc_mgr, new, 1); + + return 0; + } +out: + pthread_mutex_unlock(&uc_mgr->mutex); + return ret; +} + +/* + * Check to make sure that the modifier actually supports any of the + * active devices. + */ +static int is_modifier_valid(snd_use_case_mgr_t *uc_mgr, + struct use_case_verb *verb, struct use_case_modifier *modifier) +{ + struct dev_list *dev_list; + int dev; + + /* check modifier list against each enabled device */ + for (dev = 0; dev < verb->num_devices; dev++) { + if (!get_device_status(uc_mgr, dev)) + continue; + + dev_list = modifier->dev_list; + uc_dbg("checking device %s for %s", verb->device[dev].name, + dev_list->name ? dev_list->name : ""); + + while (dev_list) { + uc_dbg("device supports %s", dev_list->name); + if (!strcmp(dev_list->name, verb->device[dev].name)) + return 1; + dev_list = dev_list->next; + } + } + return 0; +} + +static int config_use_case_mod(snd_use_case_mgr_t *uc_mgr, + const char *modifier_name, int enable) +{ + struct use_case_verb *verb; + int ret, i; + + pthread_mutex_lock(&uc_mgr->mutex); + + if (uc_mgr->card.current_verb == VERB_NOT_INITIALISED) { + ret = -EINVAL; + goto out; + } + + verb = &uc_mgr->verb[uc_mgr->card.current_verb]; + + uc_dbg("current verb %s", verb->name); + uc_dbg("uc_mgr %p modifier_name %s", uc_mgr, modifier_name); + + /* find modifier name */ + for (i = 0; i num_modifiers; i++) { + uc_dbg("verb->num_modifiers %d %s", i, verb->modifier[i].name); + if (!strcmp(verb->modifier[i].name, modifier_name) && + is_modifier_valid(uc_mgr, verb, &verb->modifier[i])) + goto found; + } + + uc_error("error: use case modifier %s not found or invalid", + modifier_name); + ret = -EINVAL; + goto out; + +found: + if (enable) { + /* Initialise the new use case device */ + ret = enable_use_case_modifier(uc_mgr, i, uc_mgr->list, + uc_mgr->handle); + if (ret < 0) + goto out; + } else { + /* disable the old device */ + ret = disable_use_case_modifier(uc_mgr, i, uc_mgr->list, + uc_mgr->handle); + if (ret < 0) + goto out; + } + +out: + pthread_mutex_unlock(&uc_mgr->mutex); + return ret; +} + +/** + * \brief Enable use case modifier + * \param uc_mgr Use case manager + * \param modifier the modifier to be enabled + * \return 0 = successful negative = error + */ +int snd_use_case_enable_modifier(snd_use_case_mgr_t *uc_mgr, + const char *modifier) +{ + return config_use_case_mod(uc_mgr, modifier, 1); +} + +/** + * \brief Disable use case modifier + * \param uc_mgr Use case manager + * \param modifier the modifier to be disabled + * \return 0 = successful negative = error + */ +int snd_use_case_disable_modifier(snd_use_case_mgr_t *uc_mgr, + const char *modifier) +{ + return config_use_case_mod(uc_mgr, modifier, 0); +} + +static struct use_case_modifier *get_modifier(snd_use_case_mgr_t *uc_mgr, + const char *name, int *mod_id) +{ + struct use_case_verb *verb; + int i; + + if (uc_mgr->card.current_verb == VERB_NOT_INITIALISED) + return NULL; + + verb = &uc_mgr->verb[uc_mgr->card.current_verb]; + + uc_dbg("current verb %s", verb->name); + + uc_dbg("uc_mgr %p modifier_name %s", uc_mgr, name); + + for (i = 0; i < verb->num_modifiers; i++) { + uc_dbg("verb->num_devices %s", verb->modifier[i].name); + + if (!strcmp(verb->modifier[i].name, name)) { + if (mod_id) + *mod_id = i; + return &verb->modifier[i]; + } + } + + return NULL; +} + +/** + * \brief Disable old_modifier and then enable new_modifier. + * If old_modifier is not enabled just return. + * Check transition sequence firstly. + * \param uc_mgr Use case manager + * \param old the modifier to be closed + * \param new the modifier to be opened + * \return 0 = successful negative = error + */ +int snd_use_case_switch_modifier(snd_use_case_mgr_t *uc_mgr, + const char *old, const char *new) +{ + struct use_case_modifier *old_modifier; + struct use_case_modifier *new_modifier; + static struct sequence_element *trans_sequence; + int ret = 0, old_id, new_id + + uc_dbg("old %s, new %s", old, new); + + pthread_mutex_lock(&uc_mgr->mutex); + + old_modifier = get_modifier(uc_mgr, old, &old_id); + if (!old_modifier) { + uc_error("error: modifier %s not found", old); + ret = -EINVAL; + goto out; + } + + if (!get_modifier_status(uc_mgr, old_id)) { + uc_error("error: modifier %s not enabled", old); + ret = -EINVAL; + goto out; + } + + new_modifier = get_modifier(uc_mgr, new, &new_id); + if (!new_modifier) { + uc_error("error: modifier %s not found", new); + ret = -EINVAL; + goto out; + } + + trans_sequence = get_transition_sequence( + old_modifier->transition_list, new); + if (trans_sequence != NULL) { + uc_dbg("find transition sequence %s->%s", old, new); + + ret = exec_transition_sequence(uc_mgr, trans_sequence); + if (ret) + goto out; + + set_device_status(uc_mgr, old_id, 0); + set_device_status(uc_mgr, new_id, 1); + } else { + /* use lock in config_use_case_mod*/ + pthread_mutex_unlock(&uc_mgr->mutex); + + config_use_case_mod(uc_mgr, old, 0); + config_use_case_mod(uc_mgr, new, 1); + + return 0; + } +out: + pthread_mutex_unlock(&uc_mgr->mutex); + return ret; +} + +/** + * \brief Get current use case verb from sound card + * \param uc_mgr use case manager + * \return Verb Name if success, otherwise NULL + */ +const char *snd_use_case_get_verb(snd_use_case_mgr_t *uc_mgr) +{ + const char *ret = NULL; + + pthread_mutex_lock(&uc_mgr->mutex); + + if (uc_mgr->card.current_verb != VERB_NOT_INITIALISED) + ret = uc_mgr->verb_list[uc_mgr->card.current_verb]; + + pthread_mutex_unlock(&uc_mgr->mutex); + + return ret; +} + +/** + * \brief Get device status for current use case verb + * \param uc_mgr Use case manager + * \param device_name The device we are interested in. + * \return - 1 = enabled, 0 = disabled, negative = error + */ +int snd_use_case_get_device_status(snd_use_case_mgr_t *uc_mgr, + const char *device_name) +{ + struct use_case_device *device; + int ret = -EINVAL, dev_id; + + pthread_mutex_lock(&uc_mgr->mutex); + + device = get_device(uc_mgr, device_name, &dev_id); + if (device == NULL) { + uc_error("error: use case device %s not found", device_name); + goto out; + } + + ret = get_device_status(uc_mgr, dev_id); +out: + pthread_mutex_unlock(&uc_mgr->mutex); + + return ret; +} + +/** + * \brief Get modifier status for current use case verb + * \param uc_mgr Use case manager + * \param device_name The device we are interested in. + * \return - 1 = enabled, 0 = disabled, negative = error + */ +int snd_use_case_get_modifier_status(snd_use_case_mgr_t *uc_mgr, + const char *modifier_name) +{ + struct use_case_modifier *modifier; + int ret = -EINVAL, mod_id; + + pthread_mutex_lock(&uc_mgr->mutex); + + modifier = get_modifier(uc_mgr, modifier_name, &mod_id); + if (modifier == NULL) { + uc_error("error: use case modifier %s not found", modifier_name); + goto out; + } + + ret = get_modifier_status(uc_mgr, mod_id); +out: + pthread_mutex_unlock(&uc_mgr->mutex); + + return ret; +} + +/** + * \brief Get current use case verb QoS + * \param uc_mgr use case manager + * \return QoS level + */ +enum snd_use_case_qos + snd_use_case_get_verb_qos(snd_use_case_mgr_t *uc_mgr) +{ + struct use_case_verb *verb; + enum snd_use_case_qos ret = SND_USE_CASE_QOS_UNKNOWN; + + pthread_mutex_lock(&uc_mgr->mutex); + + if (uc_mgr->card.current_verb != VERB_NOT_INITIALISED) { + verb = &uc_mgr->verb[uc_mgr->card.current_verb]; + ret = verb->qos; + } + + pthread_mutex_unlock(&uc_mgr->mutex); + + return ret; +} + +/** + * \brief Get current use case modifier QoS + * \param uc_mgr use case manager + * \return QoS level + */ +enum snd_use_case_qos + snd_use_case_get_mod_qos(snd_use_case_mgr_t *uc_mgr, + const char *modifier_name) +{ + struct use_case_modifier *modifier; + enum snd_use_case_qos ret = SND_USE_CASE_QOS_UNKNOWN; + + pthread_mutex_lock(&uc_mgr->mutex); + + modifier = get_modifier(uc_mgr, modifier_name, NULL); + if (modifier != NULL) + ret = modifier->qos; + else + uc_error("error: use case modifier %s not found", modifier_name); + pthread_mutex_unlock(&uc_mgr->mutex); + + return ret; +} + +/** + * \brief Get current use case verb playback PCM + * \param uc_mgr use case manager + * \return PCM number if success, otherwise negative + */ +int snd_use_case_get_verb_playback_pcm(snd_use_case_mgr_t *uc_mgr) +{ + struct use_case_verb *verb; + int ret = -EINVAL; + + pthread_mutex_lock(&uc_mgr->mutex); + + if (uc_mgr->card.current_verb != VERB_NOT_INITIALISED) { + verb = &uc_mgr->verb[uc_mgr->card.current_verb]; + ret = verb->playback_pcm; + } + + pthread_mutex_unlock(&uc_mgr->mutex); + + return ret; +} + +/** + * \brief Get current use case verb playback PCM + * \param uc_mgr use case manager + * \return PCM number if success, otherwise negative + */ +int snd_use_case_get_verb_capture_pcm(snd_use_case_mgr_t *uc_mgr) +{ + struct use_case_verb *verb; + int ret = -EINVAL; + + pthread_mutex_lock(&uc_mgr->mutex); + + if (uc_mgr->card.current_verb != VERB_NOT_INITIALISED) { + verb = &uc_mgr->verb[uc_mgr->card.current_verb]; + ret = verb->capture_pcm; + } + + pthread_mutex_unlock(&uc_mgr->mutex); + + return ret; +} + +/** + * \brief Get current use case modifier playback PCM + * \param uc_mgr use case manager + * \return PCM number if success, otherwise negative + */ +int snd_use_case_get_mod_playback_pcm(snd_use_case_mgr_t *uc_mgr, + const char *modifier_name) +{ + struct use_case_modifier *modifier; + int ret = -EINVAL; + + pthread_mutex_lock(&uc_mgr->mutex); + + modifier = get_modifier(uc_mgr, modifier_name, NULL); + if (modifier == NULL) + uc_error("error: use case modifier %s not found", + modifier_name); + else + ret = modifier->playback_pcm; + + pthread_mutex_unlock(&uc_mgr->mutex); + + return ret; +} + +/** + * \brief Get current use case modifier playback PCM + * \param uc_mgr use case manager + * \return PCM number if success, otherwise negative + */ +int snd_use_case_get_mod_capture_pcm(snd_use_case_mgr_t *uc_mgr, + const char *modifier_name) +{ + struct use_case_modifier *modifier; + int ret = -EINVAL; + + pthread_mutex_lock(&uc_mgr->mutex); + + modifier = get_modifier(uc_mgr, modifier_name, NULL); + if (modifier == NULL) + uc_error("error: use case modifier %s not found", + modifier_name); + else + ret = modifier->capture_pcm; + + pthread_mutex_unlock(&uc_mgr->mutex); + + return ret; +} + +/** + * \brief Get volume/mute control name depending on use case device. + * \param uc_mgr use case manager + * \param type the control type we are looking for + * \param device_name The use case device we are interested in. + * \return control name if success, otherwise NULL + * + * Get the control id for common volume and mute controls that are aliased + * in the named use case device. + */ +const char *snd_use_case_get_device_ctl_elem_name(snd_use_case_mgr_t *uc_mgr, + enum snd_use_case_control_alias type, const char *device_name) +{ + struct use_case_device *device; + const char *kcontrol_name = NULL; + + pthread_mutex_lock(&uc_mgr->mutex); + + device = get_device(uc_mgr, device_name, NULL); + if (!device) { + uc_error("error: device %s not found", device_name); + goto out; + } + + switch (type) { + case SND_USE_CASE_ALIAS_PLAYBACK_VOLUME: + kcontrol_name = device->playback_volume_id; + break; + case SND_USE_CASE_ALIAS_CAPTURE_VOLUME: + kcontrol_name = device->capture_volume_id; + break; + case SND_USE_CASE_ALIAS_PLAYBACK_SWITCH: + kcontrol_name = device->playback_switch_id; + break; + case SND_USE_CASE_ALIAS_CAPTURE_SWITCH: + kcontrol_name = device->capture_switch_id; + break; + default: + uc_error("error: invalid control alias %d", type); + break; + } + +out: + pthread_mutex_unlock(&uc_mgr->mutex); + + return kcontrol_name; +} + +/** + * \brief Get volume/mute control IDs depending on use case modifier. + * \param uc_mgr use case manager + * \param type the control type we are looking for + * \param modifier_name The use case modifier we are interested in. + * \return ID if success, otherwise a negative error code + * + * Get the control id for common volume and mute controls that are aliased + * in the named use case device. + */ +const char *snd_use_case_get_modifier_ctl_elem_name(snd_use_case_mgr_t *uc_mgr, + enum snd_use_case_control_alias type, const char *modifier_name) +{ + struct use_case_modifier *modifier; + const char *kcontrol_name = NULL; + + pthread_mutex_lock(&uc_mgr->mutex); + + modifier = get_modifier(uc_mgr, modifier_name, NULL); + if (!modifier) { + uc_error("error: modifier %s not found", modifier_name); + goto out; + } + + switch (type) { + case SND_USE_CASE_ALIAS_PLAYBACK_VOLUME: + kcontrol_name = modifier->playback_volume_id; + break; + case SND_USE_CASE_ALIAS_CAPTURE_VOLUME: + kcontrol_name = modifier->capture_volume_id; + break; + case SND_USE_CASE_ALIAS_PLAYBACK_SWITCH: + kcontrol_name = modifier->playback_switch_id; + break; + case SND_USE_CASE_ALIAS_CAPTURE_SWITCH: + kcontrol_name = modifier->capture_switch_id; + break; + default: + uc_error("error: invalid control alias %d", type); + break; + } + +out: + pthread_mutex_unlock(&uc_mgr->mutex); + + return kcontrol_name; +} From 1c5afdb0c1aa2ea6c67d32b92cb7f72a3078d0ba Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Mon, 23 Aug 2010 18:17:19 +0200 Subject: [PATCH 03/30] ucm: build - add build support for Use Case Manager Add doxygen and make support for UCM. CC: Mark Brown Signed-off-by: Justin Xu Signed-off-by: Stefan Schmidt Signed-off-by: Liam Girdwood --- doc/doxygen.cfg.in | 1 + include/Makefile.am | 2 +- src/Makefile.am | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/doc/doxygen.cfg.in b/doc/doxygen.cfg.in index 8606c485..39297722 100644 --- a/doc/doxygen.cfg.in +++ b/doc/doxygen.cfg.in @@ -28,6 +28,7 @@ INPUT = @top_srcdir@/doc/index.doxygen \ @top_srcdir@/include/pcm_ioplug.h \ @top_srcdir@/include/control_external.h \ @top_srcdir@/include/mixer.h \ + @top_srcdir@/include/use-case.h \ @top_srcdir@/src/error.c \ @top_srcdir@/src/dlmisc.c \ @top_srcdir@/src/async.c \ diff --git a/include/Makefile.am b/include/Makefile.am index a2915034..de37f2cc 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -5,7 +5,7 @@ alsaincludedir = ${includedir}/alsa alsainclude_HEADERS = asoundlib.h asoundef.h \ version.h global.h input.h output.h error.h \ - conf.h control.h iatomic.h + conf.h control.h iatomic.h use-case.h if BUILD_CTL_PLUGIN_EXT alsainclude_HEADERS += control_external.h diff --git a/src/Makefile.am b/src/Makefile.am index 3204fe46..7206dbdb 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -14,7 +14,7 @@ SYMFUNCS = endif lib_LTLIBRARIES = libasound.la -libasound_la_SOURCES = conf.c confmisc.c input.c output.c async.c error.c dlmisc.c socket.c shmarea.c userfile.c names.c +libasound_la_SOURCES = conf.c confmisc.c input.c output.c async.c error.c dlmisc.c socket.c shmarea.c userfile.c names.c use-case.c SUBDIRS=control libasound_la_LIBADD = control/libcontrol.la From fcc9adb26058667656277ba92032ba7e0b00261c Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Wed, 25 Aug 2010 09:03:36 +0200 Subject: [PATCH 04/30] ucm: Recode header to be more universal - merge all similar functions to one - make everything string based - add possibility to create virtual cards - add comment pairs for the end user interface Signed-off-by: Jaroslav Kysela --- include/use-case.h | 315 +++++++++++++++------------------------------ 1 file changed, 104 insertions(+), 211 deletions(-) diff --git a/include/use-case.h b/include/use-case.h index 18fe4d60..33d480f6 100644 --- a/include/use-case.h +++ b/include/use-case.h @@ -98,12 +98,12 @@ extern "C" { #define SND_USE_CASE_VERB_INACTIVE "Inactive" #define SND_USE_CASE_VERB_HIFI "HiFi" #define SND_USE_CASE_VERB_HIFI_LOW_POWER "HiFi Low Power" -#define SND_USE_CASE_VERB_VOICE "Voice" +#define SND_USE_CASE_VERB_VOICE "Voice" #define SND_USE_CASE_VERB_VOICE_LOW_POWER "Voice Low Power" #define SND_USE_CASE_VERB_VOICECALL "Voice Call" #define SND_USE_CASE_VERB_IP_VOICECALL "Voice Call IP" #define SND_USE_CASE_VERB_ANALOG_RADIO "FM Analog Radio" -#define SND_USE_CASE_VERB_DIGITAL_RADIO "FM Digital Radio" +#define SND_USE_CASE_VERB_DIGITAL_RADIO "FM Digital Radio" /* add new verbs to end of list */ @@ -135,13 +135,13 @@ extern "C" { * e.g. to record a voice call :- * 1. Set verb to SND_USE_CASE_VERB_VOICECALL (for voice call) * 2. Set modifier SND_USE_CASE_MOD_CAPTURE_VOICE when capture required. - * 3. Call snd_use_case_get_verb_capture_pcm() to get ALSA source PCM + * 3. Call snd_use_case_get("_pcm_/_cdevice") to get ALSA source PCM name * with captured voice pcm data. * * e.g. to play a ring tone when listenin to MP3 Music :- * 1. Set verb to SND_USE_CASE_VERB_HIFI (for MP3 playback) * 2. Set modifier to SND_USE_CASE_MOD_PLAY_TONE when incoming call happens. - * 3. Call snd_use_case_get_verb_playback_pcm() to get ALSA PCM sink for + * 3. Call snd_use_case_get("_pcm_/_pdevice") to get ALSA PCM sink name for * ringtone pcm data. */ #define SND_USE_CASE_MOD_CAPTURE_VOICE "Capture Voice" @@ -154,231 +154,124 @@ extern "C" { /** - * QoS - Quality of Service + * TQ - Tone Quality * - * The interface allows clients to determine the audio QoS required for each + * The interface allows clients to determine the audio TQ required for each * use case verb and modifier. It's intended as an optional hint to the * audio driver in order to lower power consumption. * */ -enum snd_use_case_qos { - SND_USE_CASE_QOS_UNKNOWN, - SND_USE_CASE_QOS_MUSIC, - SND_USE_CASE_QOS_VOICE, - SND_USE_CASE_QOS_TONES, -}; - -/* - * Use Case Control Aliases. - * - * Use cases often use different internal hardware paths to route digital and - * analog audio. This can mean different controls are used to change volumes - * depending on the particular use case. This interface allows clients to - * find out the hardware controls associated with each use case. - */ -enum snd_use_case_control_alias { - SND_USE_CASE_ALIAS_PLAYBACK_VOLUME = 0, - SND_USE_CASE_ALIAS_PLAYBACK_SWITCH, - SND_USE_CASE_ALIAS_CAPTURE_VOLUME, - SND_USE_CASE_ALIAS_CAPTURE_SWITCH, -}; +#define SND_USE_CASE_TQ_MUSIC "Music" +#define SND_USE_CASE_TQ_VOICE "Voice" +#define SND_USE_CASE_TQ_TONES "Tones" /** use case container */ typedef struct snd_use_case_mgr snd_use_case_mgr_t; /** - * \brief List supported use case verbs for given soundcard + * \brief Create an identifier + * \param fmt Format (sprintf like) + * \param ... Optional arguments for sprintf like format + * \return Allocated string identifier or NULL on error + */ +char *snd_use_case_identifier(const char *fmt, ...); + +/** + * \brief Obtain a list of entries * \param uc_mgr Use case manager - * \param verb Returned list of supported use case verbs - * \return Number of use case verbs if success, otherwise a negative error code - */ -int snd_use_case_get_verb_list(snd_use_case_mgr_t *uc_mgr, const char **verb[]); - -/** - * \brief List supported use case devices for given use case verb - * \param uc_mgr Use case manager - * \param verb Verb name. - * \param device Returned list of supported use case devices - * \return Number of use case devices if success, otherwise a negative error code - */ -int snd_use_case_get_device_list(snd_use_case_mgr_t *uc_mgr, - const char *verb, const char **device[]); - -/** - * \brief List supported use case verb modifiers for given verb - * \param uc_mgr use case manager - * \param verb verb id. - * \param mod returned list of supported use case modifier id and names - * \return number of use case modifiers if success, otherwise a negative error code - */ -int snd_use_case_get_mod_list(snd_use_case_mgr_t *uc_mgr, - const char *verb, const char **mod[]); - -/** - * \brief Get current use case verb for sound card - * \param uc_mgr Use case manager - * \return Verb if success, otherwise NULL - */ -const char *snd_use_case_get_verb(snd_use_case_mgr_t *uc_mgr); - -/** - * \brief Set new use case verb for sound card - * \param uc_mgr Use case manager - * \param verb Verb - * \return Zero if success, otherwise a negative error code - */ -int snd_use_case_set_verb(snd_use_case_mgr_t *uc_mgr, const char *verb); - -/** - * \brief Enable use case device for current use case verb - * \param uc_mgr Use case manager - * \param device Device - * \return Zero if success, otherwise a negative error code - */ -int snd_use_case_enable_device(snd_use_case_mgr_t *uc_mgr, const char *device); - -/** - * \brief Disable use case device for current use case verb - * \param uc_mgr Use case manager - * \param device Device - * \return Zero if success, otherwise a negative error code - */ -int snd_use_case_disable_device(snd_use_case_mgr_t *uc_mgr, const char *device); - -/** - * \brief Disable old_device and then enable new_device. - * If from_device is not enabled just return. - * Check transmit sequence firstly. - * \param uc_mgr Use case manager - * \param old the device to be closed - * \param new the device to be opened - * \return 0 = successful negative = error - */ -int snd_use_case_switch_device(snd_use_case_mgr_t *uc_mgr, - const char *old, const char *new); - - -/** - * \brief Enable use case modifier for current use case verb - * \param uc_mgr Use case manager - * \param modifier Modifier - * \return Zero if success, otherwise a negative error code - */ -int snd_use_case_enable_modifier(snd_use_case_mgr_t *uc_mgr, - const char *modifier); - -/** - * \brief Disable use case modifier for curent use case verb - * \param uc_mgr Use case manager - * \param modifier Modifier - * \return Zero if success, otherwise a negative error code - */ -int snd_use_case_disable_modifier(snd_use_case_mgr_t *uc_mgr, - const char *modifier); - -/** - * \brief Disable old_modifier and then enable new_modifier. - * If old_modifier is not enabled just return. - * Check transmit sequence firstly. - * \param uc_mgr Use case manager - * \param old the modifier to be closed - * \param new the modifier to be opened - * \return 0 = successful negative = error - */ -int snd_use_case_switch_modifier(snd_use_case_mgr_t *uc_mgr, - const char *old, const char *new); - -/** - * \brief Get device status for current use case verb - * \param uc_mgr Use case manager - * \param device_name The device we are interested in. - * \return - 1 = enabled, 0 = disabled, negative = error - */ -int snd_use_case_get_device_status(snd_use_case_mgr_t *uc_mgr, - const char *device_name); - -/** - * \brief Get modifier status for current use case verb - * \param uc_mgr Use case manager - * \param device_name The device we are interested in. - * \return - 1 = enabled, 0 = disabled, negative = error - */ -int snd_use_case_get_modifier_status(snd_use_case_mgr_t *uc_mgr, - const char *modifier_name); - -/** - * \brief Get the current use case verb QoS - * \param uc_mgr Use case manager - * \return QoS level - */ -enum snd_use_case_qos snd_use_case_get_verb_qos(snd_use_case_mgr_t *uc_mgr); - -/** - * \brief Get use case modifier QoS - * \param uc_mgr use case manager - * \param modifier Modifier - * \return QoS level - */ -enum snd_use_case_qos snd_use_case_get_mod_qos(snd_use_case_mgr_t *uc_mgr, - const char *modifier); - -/** - * \brief Get the current use case verb playback PCM sink ID. - * \param uc_mgr use case manager - * \return PCM number if success, otherwise negative - */ -int snd_use_case_get_verb_playback_pcm(snd_use_case_mgr_t *uc_mgr); - -/** - * \brief Get the current use case verb capture PCM source ID - * \param uc_mgr Use case manager - * \return PCM number if success, otherwise negative - */ -int snd_use_case_get_verb_capture_pcm(snd_use_case_mgr_t *uc_mgr); - -/** - * \brief Get use case modifier playback PCM sink ID - * \param uc_mgr Use case manager - * \param modifier Modifier - * \return PCM number if success, otherwise negative - */ -int snd_use_case_get_mod_playback_pcm(snd_use_case_mgr_t *uc_mgr, - const char *modifier); - -/** - * \brief Get use case modifier capture PCM source ID - * \param uc_mgr Use case manager - * \param modifier Modifier - * \return PCM number if success, otherwise negative - */ -int snd_use_case_get_mod_capture_pcm(snd_use_case_mgr_t *uc_mgr, - const char *modifier); - -/** - * \brief Get ALSA volume/mute control names depending on use case device. - * \param uc_mgr Use case manager - * \param type The control type we are looking for - * \param device The device we are interested in. - * \return name if success, otherwise NULL + * \param identifier (may be NULL) + * \param list Returned list + * \return Number of list entries if success, otherwise a negative error code + * + * Defined identifiers: + * NULL - get card list + * (in pair verb+comment) + * _verbs - get verb list + * (in pair verb+comment) + * _devices[/] - get list of supported devices + * (in pair device+comment) + * _modifiers[/]- get list of supported modifiers + * (in pair modifier+comment) + * _tqs[/] - get list of QoS identifiers + * _enadevs - get list of enabled devices + * _enamods - get list of enabled modifiers * - * Get the control name for common volume and mute controls that are aliased - * in the current use case verb. */ -const char *snd_use_case_get_device_ctl_elem_id(snd_use_case_mgr_t *uc_mgr, - enum snd_use_case_control_alias type, const char *device); +int snd_use_case_get_list(snd_use_case_mgr_t *uc_mgr, + const char *identifier, + const char **list[]); + /** - * \brief Get ALSA volume/mute control names depending on use case modifier. + * \brief Get current - string * \param uc_mgr Use case manager - * \param type The control type we are looking for - * \param modifier The modifier we are interested in. - * \return name if success, otherwise NULL + * \param identifier + * \param value Value pointer + * \return Zero if success, otherwise a negative error code * - * Get the control name for common volume and mute controls that are aliased - * in the current use case modifier. + * Note: String is dynamically allocated, use free() to + * deallocate this string. + * + * Known identifiers: + * NULL - return current card + * _verb - return current verb + * _pcm_/_pdevice[/] - full PCM playback device name + * _pcm_/_cdevice[/] - full PCM capture device name + * _ctl_/_pctl_[/] - playback control device name + * _ctl_/_pctlvol[/] - playback control volume ID string + * _ctl_/_pctlsw[/] - playback control switch ID string + * _ctl_/_cctl[/] - capture control device name + * _ctl_/_cctlvol[/] - capture control volume ID string + * _ctl_/_cctlsw[/] - capture control switch ID string + * _mixer_/_pname[/] - name of playback mixer + * _mixer_/_pid[/] - mixer playback ID + * _mixer_/_cname[/] - name of capture mixer + * _mixer_/_cid[/] - mixer capture ID */ -const char *snd_use_case_get_modifier_ctl_elem_id(snd_use_case_mgr_t *uc_mgr, - enum snd_use_case_control_alias type, const char *modifier); +int snd_use_case_get(snd_use_case_mgr_t *uc_mgr, + const char *identifier, + const char **value); + +/** + * \brief Get current - integer + * \param uc_mgr Use case manager + * \param identifier + * \return Value if success, otherwise a negative error code + * + * Known identifiers: + * _devstatus/ - return status for given device + * _modstatus/ - return status for given modifier + * _tq - return current Tone Quality + * _tq/ - return Tone Quality for given modifier + */ +int snd_use_case_geti(snd_use_case_mgr_t *uc_mgr, + const char *identifier); + +/** + * \brief Set new + * \param uc_mgr Use case manager + * \param identifier + * \param value Value + * \return Zero if success, otherwise a negative error code + * + * Known identifiers: + * _verb - set current verb = value + * _enadev - enable given device = value + * _disdev - disable given device = value + * _swdev/ - new_device = value + * - disable old_device and then enable new_device + * - if old_device is not enabled just return + * - check transmit sequence firstly + * _enamod - enable given modifier = value + * _dismod - disable given modifier = value + * _swmod/ - new_modifier = value + * - disable old_modifier and then enable new_modifier + * - if old_modifier is not enabled just return + * - check transmit sequence firstly + */ +int snd_use_case_set(snd_use_case_mgr_t *uc_mgr, + const char *identifier, + const char *value); /** * \brief Open and initialise use case core for sound card From 1c79fad9692fb04cb8cbc7be8e2ec88595e62e46 Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Tue, 7 Sep 2010 15:35:14 +0200 Subject: [PATCH 05/30] ucm: Moved ucm to src/ucm subdirectory - separate code to more files - use standard lists to represent structures - use alsa-lib configuration parser Signed-off-by: Jaroslav Kysela --- configure.in | 6 +- doc/doxygen.cfg.in | 6 +- include/list.h | 8 + include/use-case.h | 22 +- src/Makefile.am | 6 +- src/ucm/Makefile.am | 10 + src/ucm/main.c | 1448 ++++++++++++++++++ src/ucm/parser.c | 956 ++++++++++++ src/ucm/ucm_local.h | 217 +++ src/ucm/utils.c | 220 +++ src/use-case.c | 3515 ------------------------------------------- 11 files changed, 2888 insertions(+), 3526 deletions(-) create mode 100644 src/ucm/Makefile.am create mode 100644 src/ucm/main.c create mode 100644 src/ucm/parser.c create mode 100644 src/ucm/ucm_local.h create mode 100644 src/ucm/utils.c delete mode 100644 src/use-case.c diff --git a/configure.in b/configure.in index abc4687e..0a65cc15 100644 --- a/configure.in +++ b/configure.in @@ -373,6 +373,9 @@ AC_ARG_ENABLE(hwdep, AC_ARG_ENABLE(seq, AS_HELP_STRING([--disable-seq], [disable the sequencer component]), [build_seq="$enableval"], [build_seq="yes"]) +AC_ARG_ENABLE(ucm, + AS_HELP_STRING([--disable-ucm], [disable the use-case-manager component]), + [build_ucm="$enableval"], [build_ucm="yes"]) AC_ARG_ENABLE(alisp, AS_HELP_STRING([--disable-alisp], [disable the alisp component]), [build_alisp="$enableval"], [build_alisp="yes"]) @@ -414,6 +417,7 @@ AM_CONDITIONAL(BUILD_PCM, test x$build_pcm = xyes) AM_CONDITIONAL(BUILD_RAWMIDI, test x$build_rawmidi = xyes) AM_CONDITIONAL(BUILD_HWDEP, test x$build_hwdep = xyes) AM_CONDITIONAL(BUILD_SEQ, test x$build_seq = xyes) +AM_CONDITIONAL(BUILD_UCM, test x$build_ucm = xyes) AM_CONDITIONAL(BUILD_ALISP, test x$build_alisp = xyes) AM_CONDITIONAL(BUILD_PYTHON, test x$build_python = xyes) @@ -598,7 +602,7 @@ AC_OUTPUT(Makefile doc/Makefile doc/pictures/Makefile doc/doxygen.cfg \ src/control/Makefile src/mixer/Makefile \ src/pcm/Makefile src/pcm/scopes/Makefile \ src/rawmidi/Makefile src/timer/Makefile \ - src/hwdep/Makefile src/seq/Makefile \ + src/hwdep/Makefile src/seq/Makefile src/ucm/Makefile \ src/compat/Makefile src/alisp/Makefile src/conf/Makefile \ src/conf/cards/Makefile \ src/conf/pcm/Makefile \ diff --git a/doc/doxygen.cfg.in b/doc/doxygen.cfg.in index 39297722..f4499d61 100644 --- a/doc/doxygen.cfg.in +++ b/doc/doxygen.cfg.in @@ -77,7 +77,8 @@ INPUT = @top_srcdir@/doc/index.doxygen \ @top_srcdir@/src/rawmidi \ @top_srcdir@/src/timer \ @top_srcdir@/src/hwdep \ - @top_srcdir@/src/seq + @top_srcdir@/src/seq \ + @top_srcdir@/src/ucm EXCLUDE = @top_srcdir@/src/control/control_local.h \ @top_srcdir@/src/pcm/atomic.h \ @top_srcdir@/src/pcm/interval.h \ @@ -92,7 +93,8 @@ EXCLUDE = @top_srcdir@/src/control/control_local.h \ @top_srcdir@/src/hwdep/hwdep_local.h \ @top_srcdir@/src/mixer/mixer_local.h \ @top_srcdir@/src/rawmidi/rawmidi_local.h \ - @top_srcdir@/src/seq/seq_local.h + @top_srcdir@/src/seq/seq_local.h \ + @top_srcdir@/src/seq/ucm_local.h RECURSIVE = YES FILE_PATTERNS = *.c *.h EXAMPLE_PATH = @top_srcdir@/test diff --git a/include/list.h b/include/list.h index 5b3f1bf0..4d9895fe 100644 --- a/include/list.h +++ b/include/list.h @@ -162,5 +162,13 @@ static __inline__ void list_splice(struct list_head *list, struct list_head *hea #define list_entry(ptr, type, member) \ ((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member))) +/** + * list_entry - get the struct for this entry + * @ptr: the &struct list_head pointer. + * @type: the type of the struct this is embedded in. + * @offset: offset of entry inside a struct + */ +#define list_entry_offset(ptr, type, offset) \ + ((type *)((char *)(ptr)-(offset))) #endif /* _LIST_H */ diff --git a/include/use-case.h b/include/use-case.h index 33d480f6..6b9b6455 100644 --- a/include/use-case.h +++ b/include/use-case.h @@ -176,11 +176,18 @@ typedef struct snd_use_case_mgr snd_use_case_mgr_t; */ char *snd_use_case_identifier(const char *fmt, ...); +/** + * \brief Free a string list + * \param list The string list to free + * \return Zero if success, otherwise a negative error code + */ +int snd_use_case_free_list(const char *list[]); + /** * \brief Obtain a list of entries - * \param uc_mgr Use case manager - * \param identifier (may be NULL) - * \param list Returned list + * \param uc_mgr Use case manager (may be NULL - card list) + * \param identifier (may be NULL - card list) + * \param list Returned allocated list * \return Number of list entries if success, otherwise a negative error code * * Defined identifiers: @@ -215,6 +222,8 @@ int snd_use_case_get_list(snd_use_case_mgr_t *uc_mgr, * Known identifiers: * NULL - return current card * _verb - return current verb + * _tq - return current Tone Quality + * _tq/ - return Tone Quality for given modifier * _pcm_/_pdevice[/] - full PCM playback device name * _pcm_/_cdevice[/] - full PCM capture device name * _ctl_/_pctl_[/] - playback control device name @@ -241,8 +250,6 @@ int snd_use_case_get(snd_use_case_mgr_t *uc_mgr, * Known identifiers: * _devstatus/ - return status for given device * _modstatus/ - return status for given modifier - * _tq - return current Tone Quality - * _tq/ - return Tone Quality for given modifier */ int snd_use_case_geti(snd_use_case_mgr_t *uc_mgr, const char *identifier); @@ -275,10 +282,11 @@ int snd_use_case_set(snd_use_case_mgr_t *uc_mgr, /** * \brief Open and initialise use case core for sound card + * \param uc_mgr Returned use case manager pointer * \param card_name Sound card name. - * \return Use case handle if success, otherwise NULL + * \return zero if success, otherwise a negative error code */ -snd_use_case_mgr_t *snd_use_case_mgr_open(const char *card_name); +int snd_use_case_mgr_open(snd_use_case_mgr_t **uc_mgr, const char *card_name); /** diff --git a/src/Makefile.am b/src/Makefile.am index 7206dbdb..9a00dca3 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -14,7 +14,7 @@ SYMFUNCS = endif lib_LTLIBRARIES = libasound.la -libasound_la_SOURCES = conf.c confmisc.c input.c output.c async.c error.c dlmisc.c socket.c shmarea.c userfile.c names.c use-case.c +libasound_la_SOURCES = conf.c confmisc.c input.c output.c async.c error.c dlmisc.c socket.c shmarea.c userfile.c names.c SUBDIRS=control libasound_la_LIBADD = control/libcontrol.la @@ -38,6 +38,10 @@ if BUILD_SEQ SUBDIRS += seq libasound_la_LIBADD += seq/libseq.la endif +if BUILD_UCM +SUBDIRS += ucm +libasound_la_LIBADD += ucm/libucm.la +endif if BUILD_ALISP SUBDIRS += alisp libasound_la_LIBADD += alisp/libalisp.la diff --git a/src/ucm/Makefile.am b/src/ucm/Makefile.am new file mode 100644 index 00000000..7435d903 --- /dev/null +++ b/src/ucm/Makefile.am @@ -0,0 +1,10 @@ +EXTRA_LTLIBRARIES = libucm.la + +libucm_la_SOURCES = utils.c parser.c main.c + +noinst_HEADERS = ucm_local.h + +all: libucm.la + + +INCLUDES=-I$(top_srcdir)/include diff --git a/src/ucm/main.c b/src/ucm/main.c new file mode 100644 index 00000000..a8df44c2 --- /dev/null +++ b/src/ucm/main.c @@ -0,0 +1,1448 @@ +/* + * 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 + * Stefan Schmidt + * Justin Xu + * Jaroslav Kysela + */ + +#include "ucm_local.h" +#include + +/** + * \brief Execute the sequence + * \param uc_mgr Use case manager + * \param seq Sequence + * \return zero on success, otherwise a negative error code + */ +static int execute_sequence(snd_use_case_mgr_t *uc_mgr ATTRIBUTE_UNUSED, + struct list_head *seq) +{ + struct list_head *pos; + struct sequence_element *s; + + list_for_each(pos, seq) { + s = list_entry(pos, struct sequence_element, list); + switch (s->type) { + case SEQUENCE_ELEMENT_TYPE_CSET: + uc_error("cset not yet implemented: '%s'", s->data.cset); + break; + case SEQUENCE_ELEMENT_TYPE_SLEEP: + usleep(s->data.sleep); + break; + case SEQUENCE_ELEMENT_TYPE_EXEC: + uc_error("exec not yet implemented: '%s'", s->data.exec); + break; + default: + uc_error("unknown sequence command %i", s->type); + break; + } + } + return 0; +} + +/** + * \brief Import master config and execute the default sequence + * \param uc_mgr Use case manager + * \return zero on success, otherwise a negative error code + */ +static int import_master_config(snd_use_case_mgr_t *uc_mgr) +{ + int err; + + err = uc_mgr_import_master_config(uc_mgr); + if (err < 0) + return err; + err = execute_sequence(uc_mgr, &uc_mgr->default_list); + if (err < 0) + uc_error("Unable to execute default sequence"); + return err; +} + +/** + * \brief Universal find - string in a list + * \param uc_mgr Use case manager + * \param list List of structures + * \param offset Offset of list structure + * \param soffset Offset of string structure + * \param match String to match + * \return structure on success, otherwise a NULL (not found) + */ +static void *find0(snd_use_case_mgr_t *uc_mgr ATTRIBUTE_UNUSED, + struct list_head *list, + unsigned long offset, + unsigned long soffset, + const char *match) +{ + struct list_head *pos; + char *ptr, *str; + + list_for_each(pos, list) { + ptr = list_entry_offset(pos, char, offset); + str = *((char **)(ptr + soffset)); + if (strcmp(str, match) == 0) + return ptr; + } + return NULL; +} + +#define find(uc_mgr, list, type, member, value, match) \ + find0(uc_mgr, list, \ + (unsigned long)(&((type *)0)->member), \ + (unsigned long)(&((type *)0)->value), match) + +/** + * \brief Find verb + * \param uc_mgr Use case manager + * \param verb_name verb to find + * \return structure on success, otherwise a NULL (not found) + */ +static inline struct use_case_verb *find_verb(snd_use_case_mgr_t *uc_mgr, + const char *_name) +{ + return find(uc_mgr, &uc_mgr->verb_list, + struct use_case_verb, list, name, + _name); +} + +/** + * \brief Set verb + * \param uc_mgr Use case manager + * \param verb verb to set + * \return zero on success, otherwise a negative error code + */ +static int set_verb(snd_use_case_mgr_t *uc_mgr, + struct use_case_verb *verb, + int enable) +{ + struct list_head *seq; + int err; + + if (enable) { + seq = &verb->enable_list; + } else { + seq = &verb->disable_list; + } + err = execute_sequence(uc_mgr, seq); + if (enable && err >= 0) + uc_mgr->active_verb = verb; + return err; +} + +/** + * \brief Init sound card use case manager. + * \param uc_mgr Returned use case manager pointer + * \param card_name name of card to open + * \return zero on success, otherwise a negative error code + */ +int snd_use_case_mgr_open(snd_use_case_mgr_t **mgr, + const char *card_name) +{ + snd_use_case_mgr_t *uc_mgr; + int err; + + /* create a new UCM */ + uc_mgr = calloc(1, sizeof(snd_use_case_mgr_t)); + if (uc_mgr == NULL) + return -ENOMEM; + INIT_LIST_HEAD(&uc_mgr->verb_list); + INIT_LIST_HEAD(&uc_mgr->default_list); + pthread_mutex_init(&uc_mgr->mutex, NULL); + + uc_mgr->card_name = strdup(card_name); + if (uc_mgr->card_name == NULL) { + free(uc_mgr); + return -ENOMEM; + } + + /* get info on use_cases and verify against card */ + err = import_master_config(uc_mgr); + if (err < 0) { + uc_error("error: failed to import %s use case configuration %d", + card_name, err); + goto err; + } + + *mgr = uc_mgr; + return 0; + +err: + uc_mgr_free(uc_mgr); + return err; +} + +/** + * \brief Reload and reparse all use case files. + * \param uc_mgr Use case manager + * \return zero on success, otherwise a negative error code + */ +int snd_use_case_mgr_reload(snd_use_case_mgr_t *uc_mgr) +{ + int err; + + pthread_mutex_lock(&uc_mgr->mutex); + + uc_mgr_free_verb(uc_mgr); + + /* reload all use cases */ + err = import_master_config(uc_mgr); + if (err < 0) { + uc_error("error: failed to reload use cases\n"); + pthread_mutex_unlock(&uc_mgr->mutex); + return -EINVAL; + } + + pthread_mutex_unlock(&uc_mgr->mutex); + return err; +} + +/** + * \brief Close use case manager. + * \param uc_mgr Use case manager + * \return zero on success, otherwise a negative error code + */ +int snd_use_case_mgr_close(snd_use_case_mgr_t *uc_mgr) +{ + uc_mgr_free(uc_mgr); + + return 0; +} + +/** + * \brief Reset sound card controls to default values. + * \param uc_mgr Use case manager + * \return zero on success, otherwise a negative error code + */ +int snd_use_case_mgr_reset(snd_use_case_mgr_t *uc_mgr) +{ + struct list_head *pos, *npos; + struct use_case_modifier *modifier; + struct use_case_device *device; + int err; + + pthread_mutex_lock(&uc_mgr->mutex); + + list_for_each_safe(pos, npos, &uc_mgr->active_modifiers) { + modifier = list_entry(pos, struct use_case_modifier, + active_list); + err = disable_modifier(uc_mgr, modifier); + if (err < 0) + uc_error("Unable to disable modifier %s", modifier->name); + } + INIT_LIST_HEAD(&uc_mgr->active_modifiers); + + list_for_each_safe(pos, npos, &uc_mgr->active_devices) { + device = list_entry(pos, struct use_case_device, + active_list); + err = disable_device(uc_mgr, device); + if (err < 0) + uc_error("Unable to disable device %s", device->name); + } + INIT_LIST_HEAD(&uc_mgr->active_devices); + + err = disable_verb(uc_mgr, uc_mgr->active_verb); + if (err < 0) { + uc_error("Unable to disable verb %s", uc_mgr->active_verb->name); + return err; + } + uc_mgr->active_verb = NULL; + + err = execute_sequence(uc_mgr, &uc_mgr->default_list); + + pthread_mutex_unlock(&uc_mgr->mutex); + return err; +} + +#if 0 +static int enable_use_case_verb(snd_use_case_mgr_t *uc_mgr, int verb_id, + snd_ctl_elem_list_t *list, snd_ctl_t *handle) +{ + struct use_case_verb *verb; + int ret; + + if (verb_id >= uc_mgr->num_verbs) { + uc_error("error: invalid verb id %d", verb_id); + return -EINVAL; + } + verb = &uc_mgr->verb[verb_id]; + + uc_dbg("verb %s", verb->name); + ret = exec_sequence(verb->enable, uc_mgr, list, handle); + if (ret < 0) { + uc_error("error: could not enable verb %s", verb->name); + return ret; + } + uc_mgr->card.current_verb = verb_id; + + return 0; +} + +static int disable_use_case_verb(snd_use_case_mgr_t *uc_mgr, int verb_id, + snd_ctl_elem_list_t *list, snd_ctl_t *handle) +{ + struct use_case_verb *verb; + int ret; + + if (verb_id >= uc_mgr->num_verbs) { + uc_error("error: invalid verb id %d", verb_id); + return -EINVAL; + } + verb = &uc_mgr->verb[verb_id]; + + /* we set the invalid verb at open() but we should still + * check that this succeeded */ + if (verb == NULL) + return 0; + + uc_dbg("verb %s", verb->name); + ret = exec_sequence(verb->disable, uc_mgr, list, handle); + if (ret < 0) { + uc_error("error: could not disable verb %s", verb->name); + return ret; + } + + return 0; +} + +static int enable_use_case_device(snd_use_case_mgr_t *uc_mgr, + int device_id, snd_ctl_elem_list_t *list, snd_ctl_t *handle) +{ + struct use_case_verb *verb = &uc_mgr->verb[uc_mgr->card.current_verb]; + struct use_case_device *device = &verb->device[device_id]; + int ret; + + if (uc_mgr->card.current_verb == VERB_NOT_INITIALISED) + return -EINVAL; + + uc_dbg("device %s", device->name); + ret = exec_sequence(device->enable, uc_mgr, list, handle); + if (ret < 0) { + uc_error("error: could not enable device %s", device->name); + return ret; + } + + set_device_status(uc_mgr, device_id, 1); + return 0; +} + +static int disable_use_case_device(snd_use_case_mgr_t *uc_mgr, + int device_id, snd_ctl_elem_list_t *list, snd_ctl_t *handle) +{ + struct use_case_verb *verb = &uc_mgr->verb[uc_mgr->card.current_verb]; + struct use_case_device *device = &verb->device[device_id]; + int ret; + + if (uc_mgr->card.current_verb == VERB_NOT_INITIALISED) + return -EINVAL; + + uc_dbg("device %s", device->name); + ret = exec_sequence(device->disable, uc_mgr, list, handle); + if (ret < 0) { + uc_error("error: could not disable device %s", device->name); + return ret; + } + + set_device_status(uc_mgr, device_id, 0); + return 0; +} + +static int enable_use_case_modifier(snd_use_case_mgr_t *uc_mgr, + int modifier_id, snd_ctl_elem_list_t *list, snd_ctl_t *handle) +{ + struct use_case_verb *verb = &uc_mgr->verb[uc_mgr->card.current_verb]; + struct use_case_modifier *modifier = &verb->modifier[modifier_id]; + int ret; + + if (uc_mgr->card.current_verb == VERB_NOT_INITIALISED) + return -EINVAL; + + uc_dbg("modifier %s", modifier->name); + ret = exec_sequence(modifier->enable, uc_mgr, list, handle); + if (ret < 0) { + uc_error("error: could not enable modifier %s", modifier->name); + return ret; + } + + set_modifier_status(uc_mgr, modifier_id, 1); + return 0; +} + +static int disable_use_case_modifier(snd_use_case_mgr_t *uc_mgr, + int modifier_id, snd_ctl_elem_list_t *list, snd_ctl_t *handle) +{ + struct use_case_verb *verb = &uc_mgr->verb[uc_mgr->card.current_verb]; + struct use_case_modifier *modifier = &verb->modifier[modifier_id]; + int ret; + + if (uc_mgr->card.current_verb == VERB_NOT_INITIALISED) + return -EINVAL; + + uc_dbg("modifier %s", modifier->name); + ret = exec_sequence(modifier->disable, uc_mgr, list, handle); + if (ret < 0) { + uc_error("error: could not disable modifier %s", modifier->name); + return ret; + } + + set_modifier_status(uc_mgr, modifier_id, 0); + return 0; +} + +/* + * Tear down current use case verb, device and modifier. + */ +static int dismantle_use_case(snd_use_case_mgr_t *uc_mgr, + snd_ctl_elem_list_t *list, snd_ctl_t *handle) +{ + struct use_case_verb *verb = &uc_mgr->verb[uc_mgr->card.current_verb]; + int ret, i; + + /* No active verb */ + if (uc_mgr->card.current_verb == VERB_NOT_INITIALISED) + return 0; + + /* disable all modifiers that are active */ + for (i = 0; i < verb->num_modifiers; i++) { + if (get_modifier_status(uc_mgr,i)) { + ret = disable_use_case_modifier(uc_mgr, i, list, handle); + if (ret < 0) + return ret; + } + } + + /* disable all devices that are active */ + for (i = 0; i < verb->num_devices; i++) { + if (get_device_status(uc_mgr,i)) { + ret = disable_use_case_device(uc_mgr, i, list, handle); + if (ret < 0) + return ret; + } + } + + /* disable verb */ + ret = disable_use_case_verb(uc_mgr, uc_mgr->card.current_verb, list, handle); + if (ret < 0) + return ret; + + return 0; +} + + /** + * \brief Dump sound card controls in format required for sequencer. + * \param card_name The name of the sound card to be dumped + * \return zero on success, otherwise a negative error code + */ +int snd_use_case_dump(const char *card_name) +{ + snd_ctl_t *handle; + snd_ctl_card_info_t *info; + snd_ctl_elem_list_t *list; + int ret, i, count, idx; + char ctl_name[8]; + + snd_ctl_card_info_alloca(&info); + snd_ctl_elem_list_alloca(&list); + + idx = snd_card_get_index(card_name); + if (idx < 0) + return idx; + sprintf(ctl_name, "hw:%d", idx); + + /* open and load snd card */ + ret = snd_ctl_open(&handle, ctl_name, SND_CTL_READONLY); + if (ret < 0) { + uc_error("error: could not open controls for %s: %s", + card_name, snd_strerror(ret)); + return ret; + } + + ret = snd_ctl_card_info(handle, info); + if (ret < 0) { + uc_error("error: could not get control info for %s:%s", + card_name, snd_strerror(ret)); + goto close; + } + + ret = snd_ctl_elem_list(handle, list); + if (ret < 0) { + uc_error("error: cannot determine controls for %s: %s", + card_name, snd_strerror(ret)); + goto close; + } + + count = snd_ctl_elem_list_get_count(list); + if (count < 0) { + ret = 0; + goto close; + } + + snd_ctl_elem_list_set_offset(list, 0); + if (snd_ctl_elem_list_alloc_space(list, count) < 0) { + uc_error("error: not enough memory for control elements"); + ret = -ENOMEM; + goto close; + } + if ((ret = snd_ctl_elem_list(handle, list)) < 0) { + uc_error("error: cannot determine controls: %s", + snd_strerror(ret)); + goto free; + } + + /* iterate through each kcontrol and add to use + * case manager control list */ + for (i = 0; i < count; ++i) { + snd_ctl_elem_id_t *id; + snd_ctl_elem_id_alloca(&id); + snd_ctl_elem_list_get_id(list, i, id); + + /* dump to stdout in friendly format */ + ret = dump_control(handle, id); + if (ret < 0) { + uc_error("error: control dump failed: %s", + snd_strerror(ret)); + goto free; + } + } +free: + snd_ctl_elem_list_free_space(list); +close: + snd_ctl_close(handle); + return ret; +} + +/** + * \brief List supported use case verbs for given soundcard + * \param uc_mgr use case manager + * \param verb returned list of supported use case verb id and names + * \return number of use case verbs if success, otherwise a negative error code + */ +int snd_use_case_get_verb_list(snd_use_case_mgr_t *uc_mgr, + const char **verb[]) +{ + int ret; + + pthread_mutex_lock(&uc_mgr->mutex); + + *verb = uc_mgr->verb_list; + ret = uc_mgr->num_verbs; + + pthread_mutex_unlock(&uc_mgr->mutex); + return ret; +} + +/** + * \brief List supported use case devices for given verb + * \param uc_mgr use case manager + * \param verb verb id. + * \param device returned list of supported use case device id and names + * \return number of use case devices if success, otherwise a negative error code + */ +int snd_use_case_get_device_list(snd_use_case_mgr_t *uc_mgr, + const char *verb_name, const char **device[]) +{ + struct use_case_verb *verb = NULL; + int i, ret = -EINVAL; + + pthread_mutex_lock(&uc_mgr->mutex); + + /* find verb name */ + for (i = 0; i < uc_mgr->num_verbs; i++) { + verb = &uc_mgr->verb[i]; + if (!strcmp(uc_mgr->verb[i].name, verb_name)) + goto found; + } + + uc_error("error: use case verb %s not found", verb_name); + goto out; + +found: + *device = verb->device_list; + ret = verb->num_devices; +out: + pthread_mutex_unlock(&uc_mgr->mutex); + return ret; +} + +/** + * \brief List supported use case verb modifiers for given verb + * \param uc_mgr use case manager + * \param verb verb id. + * \param mod returned list of supported use case modifier id and names + * \return number of use case modifiers if success, otherwise a negative error code + */ +int snd_use_case_get_mod_list(snd_use_case_mgr_t *uc_mgr, + const char *verb_name, const char **mod[]) +{ + struct use_case_verb *verb = NULL; + int i, ret = -EINVAL; + + pthread_mutex_lock(&uc_mgr->mutex); + + /* find verb name */ + for (i = 0; i num_verbs; i++) { + verb = &uc_mgr->verb[i]; + if (!strcmp(uc_mgr->verb[i].name, verb_name)) + goto found; + } + + uc_error("error: use case verb %s not found", verb_name); + goto out; + +found: + *mod = verb->modifier_list; + ret = verb->num_modifiers; +out: + pthread_mutex_unlock(&uc_mgr->mutex); + return ret; +} + +static struct sequence_element *get_transition_sequence( + struct transition_sequence *trans_list, const char *name) +{ + struct transition_sequence *trans = trans_list; + + while (trans) { + if (trans->name && !strcmp(trans->name, name)) + return trans->transition; + + trans = trans->next; + } + + return NULL; +} + +static int exec_transition_sequence(snd_use_case_mgr_t *uc_mgr, + struct sequence_element *trans_sequence) +{ + int ret; + + ret = exec_sequence(trans_sequence, uc_mgr, uc_mgr->list, + uc_mgr->handle); + if (ret < 0) + uc_error("error: could not exec transition sequence"); + + return ret; +} + +static int handle_transition_verb(snd_use_case_mgr_t *uc_mgr, + int new_verb_id) +{ + struct use_case_verb *old_verb = &uc_mgr->verb[uc_mgr->card.current_verb]; + struct use_case_verb *new_verb; + static struct sequence_element *trans_sequence; + + if (uc_mgr->card.current_verb == VERB_NOT_INITIALISED) + return -EINVAL; + + if (new_verb_id >= uc_mgr->num_verbs) { + uc_error("error: invalid new_verb id %d", new_verb_id); + return -EINVAL; + } + + new_verb = &uc_mgr->verb[new_verb_id]; + + uc_dbg("new verb %s", new_verb->name); + + trans_sequence = get_transition_sequence(old_verb->transition_list, + new_verb->name); + if (trans_sequence != NULL) { + int ret, i; + + uc_dbg("find transition sequence %s->%s", + old_verb->name, new_verb->name); + + /* disable all modifiers that are active */ + for (i = 0; i < old_verb->num_modifiers; i++) { + if (get_modifier_status(uc_mgr,i)) { + ret = disable_use_case_modifier(uc_mgr, i, + uc_mgr->list, uc_mgr->handle); + if (ret < 0) + return ret; + } + } + + /* disable all devices that are active */ + for (i = 0; i < old_verb->num_devices; i++) { + if (get_device_status(uc_mgr,i)) { + ret = disable_use_case_device(uc_mgr, i, + uc_mgr->list, uc_mgr->handle); + if (ret < 0) + return ret; + } + } + + ret = exec_transition_sequence(uc_mgr, trans_sequence); + if (ret) + return ret; + + uc_mgr->card.current_verb = new_verb_id; + + return 0; + } + + return-EINVAL; +} + +/** + * \brief Set new use case verb for sound card + * \param uc_mgr use case manager + * \param verb verb id + * \return zero if success, otherwise a negative error code + */ +int snd_use_case_set_verb(snd_use_case_mgr_t *uc_mgr, + const char *verb_name) +{ + int i = 0, ret = -EINVAL, inactive = 0; + + pthread_mutex_lock(&uc_mgr->mutex); + + uc_dbg("uc_mgr %p, verb_name %s", uc_mgr, verb_name); + + /* check for "Inactive" */ + if (!strcmp(verb_name, SND_USE_CASE_VERB_INACTIVE)) { + inactive = 1; + goto found; + } + + /* find verb name */ + for (i = 0; i num_verbs; i++) { + if (!strcmp(uc_mgr->verb[i].name, verb_name)) + goto found; + } + + uc_error("error: use case verb %s not found", verb_name); + goto out; +found: + /* use case verb found - check that we actually changing the verb */ + if (i == uc_mgr->card.current_verb) { + uc_dbg("current verb ID %d", i); + ret = 0; + goto out; + } + + if (handle_transition_verb(uc_mgr, i) == 0) + goto out; + + /* + * Dismantle the old use cases by running it's verb, device and modifier + * disable sequences + */ + ret = dismantle_use_case(uc_mgr, uc_mgr->list, uc_mgr->handle); + if (ret < 0) { + uc_error("error: failed to dismantle current use case: %s", + uc_mgr->verb[i].name); + goto out; + } + + /* we don't need to initialise new verb if inactive */ + if (inactive) + goto out; + + /* Initialise the new use case verb */ + ret = enable_use_case_verb(uc_mgr, i, uc_mgr->list, uc_mgr->handle); + if (ret < 0) + uc_error("error: failed to initialise new use case: %s", + verb_name); +out: + pthread_mutex_unlock(&uc_mgr->mutex); + + return ret; +} + +static int config_use_case_device(snd_use_case_mgr_t *uc_mgr, + const char *device_name, int enable) +{ + struct use_case_verb *verb; + int ret, i; + + pthread_mutex_lock(&uc_mgr->mutex); + + if (uc_mgr->card.current_verb == VERB_NOT_INITIALISED) { + uc_error("error: no valid use case verb set\n"); + ret = -EINVAL; + goto out; + } + + verb = &uc_mgr->verb[uc_mgr->card.current_verb]; + + uc_dbg("current verb %s", verb->name); + uc_dbg("uc_mgr %p device_name %s", uc_mgr, device_name); + + /* find device name and index */ + for (i = 0; i num_devices; i++) { + uc_dbg("verb->num_devices %s", verb->device[i].name); + if (!strcmp(verb->device[i].name, device_name)) + goto found; + } + + uc_error("error: use case device %s not found", device_name); + ret = -EINVAL; + goto out; + +found: + if (enable) { + /* Initialise the new use case device */ + ret = enable_use_case_device(uc_mgr, i, uc_mgr->list, + uc_mgr->handle); + if (ret < 0) + goto out; + } else { + /* disable the old device */ + ret = disable_use_case_device(uc_mgr, i, uc_mgr->list, + uc_mgr->handle); + if (ret < 0) + goto out; + } + +out: + pthread_mutex_unlock(&uc_mgr->mutex); + return ret; +} + +/** + * \brief Enable use case device + * \param uc_mgr Use case manager + * \param device the device to be enabled + * \return 0 = successful negative = error + */ +int snd_use_case_enable_device(snd_use_case_mgr_t *uc_mgr, + const char *device) +{ + return config_use_case_device(uc_mgr, device, 1); +} + +/** + * \brief Disable use case device + * \param uc_mgr Use case manager + * \param device the device to be disabled + * \return 0 = successful negative = error + */ +int snd_use_case_disable_device(snd_use_case_mgr_t *uc_mgr, + const char *device) +{ + return config_use_case_device(uc_mgr, device, 0); +} + +static struct use_case_device *get_device(snd_use_case_mgr_t *uc_mgr, + const char *name, int *id) +{ + struct use_case_verb *verb; + int i; + + if (uc_mgr->card.current_verb == VERB_NOT_INITIALISED) + return NULL; + + verb = &uc_mgr->verb[uc_mgr->card.current_verb]; + + uc_dbg("current verb %s", verb->name); + + for (i = 0; i < verb->num_devices; i++) { + uc_dbg("device %s", verb->device[i].name); + + if (!strcmp(verb->device[i].name, name)) { + if (id) + *id = i; + return &verb->device[i]; + } + } + + return NULL; +} + +/** + * \brief Disable old_device and then enable new_device. + * If from_device is not enabled just return. + * Check transition sequence firstly. + * \param uc_mgr Use case manager + * \param old the device to be closed + * \param new the device to be opened + * \return 0 = successful negative = error + */ +int snd_use_case_switch_device(snd_use_case_mgr_t *uc_mgr, + const char *old, const char *new) +{ + static struct sequence_element *trans_sequence; + struct use_case_device *old_device; + struct use_case_device *new_device; + int ret = 0, old_id, new_id; + + uc_dbg("old %s, new %s", old, new); + + pthread_mutex_lock(&uc_mgr->mutex); + + old_device = get_device(uc_mgr, old, &old_id); + if (!old_device) { + uc_error("error: device %s not found", old); + ret = -EINVAL; + goto out; + } + + if (!get_device_status(uc_mgr, old_id)) { + uc_error("error: device %s not enabled", old); + goto out; + } + + new_device = get_device(uc_mgr, new, &new_id); + if (!new_device) { + uc_error("error: device %s not found", new); + ret = -EINVAL; + goto out; + } + + trans_sequence = get_transition_sequence(old_device->transition_list, new); + if (trans_sequence != NULL) { + + uc_dbg("find transition sequece %s->%s", old, new); + + ret = exec_transition_sequence(uc_mgr, trans_sequence); + if (ret) + goto out; + + set_device_status(uc_mgr, old_id, 0); + set_device_status(uc_mgr, new_id, 1); + } else { + /* use lock in config_use_case_device */ + pthread_mutex_unlock(&uc_mgr->mutex); + + config_use_case_device(uc_mgr, old, 0); + config_use_case_device(uc_mgr, new, 1); + + return 0; + } +out: + pthread_mutex_unlock(&uc_mgr->mutex); + return ret; +} + +/* + * Check to make sure that the modifier actually supports any of the + * active devices. + */ +static int is_modifier_valid(snd_use_case_mgr_t *uc_mgr, + struct use_case_verb *verb, struct use_case_modifier *modifier) +{ + struct dev_list *dev_list; + int dev; + + /* check modifier list against each enabled device */ + for (dev = 0; dev < verb->num_devices; dev++) { + if (!get_device_status(uc_mgr, dev)) + continue; + + dev_list = modifier->dev_list; + uc_dbg("checking device %s for %s", verb->device[dev].name, + dev_list->name ? dev_list->name : ""); + + while (dev_list) { + uc_dbg("device supports %s", dev_list->name); + if (!strcmp(dev_list->name, verb->device[dev].name)) + return 1; + dev_list = dev_list->next; + } + } + return 0; +} + +static int config_use_case_mod(snd_use_case_mgr_t *uc_mgr, + const char *modifier_name, int enable) +{ + struct use_case_verb *verb; + int ret, i; + + pthread_mutex_lock(&uc_mgr->mutex); + + if (uc_mgr->card.current_verb == VERB_NOT_INITIALISED) { + ret = -EINVAL; + goto out; + } + + verb = &uc_mgr->verb[uc_mgr->card.current_verb]; + + uc_dbg("current verb %s", verb->name); + uc_dbg("uc_mgr %p modifier_name %s", uc_mgr, modifier_name); + + /* find modifier name */ + for (i = 0; i num_modifiers; i++) { + uc_dbg("verb->num_modifiers %d %s", i, verb->modifier[i].name); + if (!strcmp(verb->modifier[i].name, modifier_name) && + is_modifier_valid(uc_mgr, verb, &verb->modifier[i])) + goto found; + } + + uc_error("error: use case modifier %s not found or invalid", + modifier_name); + ret = -EINVAL; + goto out; + +found: + if (enable) { + /* Initialise the new use case device */ + ret = enable_use_case_modifier(uc_mgr, i, uc_mgr->list, + uc_mgr->handle); + if (ret < 0) + goto out; + } else { + /* disable the old device */ + ret = disable_use_case_modifier(uc_mgr, i, uc_mgr->list, + uc_mgr->handle); + if (ret < 0) + goto out; + } + +out: + pthread_mutex_unlock(&uc_mgr->mutex); + return ret; +} + +/** + * \brief Enable use case modifier + * \param uc_mgr Use case manager + * \param modifier the modifier to be enabled + * \return 0 = successful negative = error + */ +int snd_use_case_enable_modifier(snd_use_case_mgr_t *uc_mgr, + const char *modifier) +{ + return config_use_case_mod(uc_mgr, modifier, 1); +} + +/** + * \brief Disable use case modifier + * \param uc_mgr Use case manager + * \param modifier the modifier to be disabled + * \return 0 = successful negative = error + */ +int snd_use_case_disable_modifier(snd_use_case_mgr_t *uc_mgr, + const char *modifier) +{ + return config_use_case_mod(uc_mgr, modifier, 0); +} + +static struct use_case_modifier *get_modifier(snd_use_case_mgr_t *uc_mgr, + const char *name, int *mod_id) +{ + struct use_case_verb *verb; + int i; + + if (uc_mgr->card.current_verb == VERB_NOT_INITIALISED) + return NULL; + + verb = &uc_mgr->verb[uc_mgr->card.current_verb]; + + uc_dbg("current verb %s", verb->name); + + uc_dbg("uc_mgr %p modifier_name %s", uc_mgr, name); + + for (i = 0; i < verb->num_modifiers; i++) { + uc_dbg("verb->num_devices %s", verb->modifier[i].name); + + if (!strcmp(verb->modifier[i].name, name)) { + if (mod_id) + *mod_id = i; + return &verb->modifier[i]; + } + } + + return NULL; +} + +/** + * \brief Disable old_modifier and then enable new_modifier. + * If old_modifier is not enabled just return. + * Check transition sequence firstly. + * \param uc_mgr Use case manager + * \param old the modifier to be closed + * \param new the modifier to be opened + * \return 0 = successful negative = error + */ +int snd_use_case_switch_modifier(snd_use_case_mgr_t *uc_mgr, + const char *old, const char *new) +{ + struct use_case_modifier *old_modifier; + struct use_case_modifier *new_modifier; + static struct sequence_element *trans_sequence; + int ret = 0, old_id, new_id + + uc_dbg("old %s, new %s", old, new); + + pthread_mutex_lock(&uc_mgr->mutex); + + old_modifier = get_modifier(uc_mgr, old, &old_id); + if (!old_modifier) { + uc_error("error: modifier %s not found", old); + ret = -EINVAL; + goto out; + } + + if (!get_modifier_status(uc_mgr, old_id)) { + uc_error("error: modifier %s not enabled", old); + ret = -EINVAL; + goto out; + } + + new_modifier = get_modifier(uc_mgr, new, &new_id); + if (!new_modifier) { + uc_error("error: modifier %s not found", new); + ret = -EINVAL; + goto out; + } + + trans_sequence = get_transition_sequence( + old_modifier->transition_list, new); + if (trans_sequence != NULL) { + uc_dbg("find transition sequence %s->%s", old, new); + + ret = exec_transition_sequence(uc_mgr, trans_sequence); + if (ret) + goto out; + + set_device_status(uc_mgr, old_id, 0); + set_device_status(uc_mgr, new_id, 1); + } else { + /* use lock in config_use_case_mod*/ + pthread_mutex_unlock(&uc_mgr->mutex); + + config_use_case_mod(uc_mgr, old, 0); + config_use_case_mod(uc_mgr, new, 1); + + return 0; + } +out: + pthread_mutex_unlock(&uc_mgr->mutex); + return ret; +} + +/** + * \brief Get current use case verb from sound card + * \param uc_mgr use case manager + * \return Verb Name if success, otherwise NULL + */ +const char *snd_use_case_get_verb(snd_use_case_mgr_t *uc_mgr) +{ + const char *ret = NULL; + + pthread_mutex_lock(&uc_mgr->mutex); + + if (uc_mgr->card.current_verb != VERB_NOT_INITIALISED) + ret = uc_mgr->verb_list[uc_mgr->card.current_verb]; + + pthread_mutex_unlock(&uc_mgr->mutex); + + return ret; +} + +/** + * \brief Get device status for current use case verb + * \param uc_mgr Use case manager + * \param device_name The device we are interested in. + * \return - 1 = enabled, 0 = disabled, negative = error + */ +int snd_use_case_get_device_status(snd_use_case_mgr_t *uc_mgr, + const char *device_name) +{ + struct use_case_device *device; + int ret = -EINVAL, dev_id; + + pthread_mutex_lock(&uc_mgr->mutex); + + device = get_device(uc_mgr, device_name, &dev_id); + if (device == NULL) { + uc_error("error: use case device %s not found", device_name); + goto out; + } + + ret = get_device_status(uc_mgr, dev_id); +out: + pthread_mutex_unlock(&uc_mgr->mutex); + + return ret; +} + +/** + * \brief Get modifier status for current use case verb + * \param uc_mgr Use case manager + * \param device_name The device we are interested in. + * \return - 1 = enabled, 0 = disabled, negative = error + */ +int snd_use_case_get_modifier_status(snd_use_case_mgr_t *uc_mgr, + const char *modifier_name) +{ + struct use_case_modifier *modifier; + int ret = -EINVAL, mod_id; + + pthread_mutex_lock(&uc_mgr->mutex); + + modifier = get_modifier(uc_mgr, modifier_name, &mod_id); + if (modifier == NULL) { + uc_error("error: use case modifier %s not found", modifier_name); + goto out; + } + + ret = get_modifier_status(uc_mgr, mod_id); +out: + pthread_mutex_unlock(&uc_mgr->mutex); + + return ret; +} + +/** + * \brief Get current use case verb QoS + * \param uc_mgr use case manager + * \return QoS level + */ +enum snd_use_case_qos + snd_use_case_get_verb_qos(snd_use_case_mgr_t *uc_mgr) +{ + struct use_case_verb *verb; + enum snd_use_case_qos ret = SND_USE_CASE_QOS_UNKNOWN; + + pthread_mutex_lock(&uc_mgr->mutex); + + if (uc_mgr->card.current_verb != VERB_NOT_INITIALISED) { + verb = &uc_mgr->verb[uc_mgr->card.current_verb]; + ret = verb->qos; + } + + pthread_mutex_unlock(&uc_mgr->mutex); + + return ret; +} + +/** + * \brief Get current use case modifier QoS + * \param uc_mgr use case manager + * \return QoS level + */ +enum snd_use_case_qos + snd_use_case_get_mod_qos(snd_use_case_mgr_t *uc_mgr, + const char *modifier_name) +{ + struct use_case_modifier *modifier; + enum snd_use_case_qos ret = SND_USE_CASE_QOS_UNKNOWN; + + pthread_mutex_lock(&uc_mgr->mutex); + + modifier = get_modifier(uc_mgr, modifier_name, NULL); + if (modifier != NULL) + ret = modifier->qos; + else + uc_error("error: use case modifier %s not found", modifier_name); + pthread_mutex_unlock(&uc_mgr->mutex); + + return ret; +} + +/** + * \brief Get current use case verb playback PCM + * \param uc_mgr use case manager + * \return PCM number if success, otherwise negative + */ +int snd_use_case_get_verb_playback_pcm(snd_use_case_mgr_t *uc_mgr) +{ + struct use_case_verb *verb; + int ret = -EINVAL; + + pthread_mutex_lock(&uc_mgr->mutex); + + if (uc_mgr->card.current_verb != VERB_NOT_INITIALISED) { + verb = &uc_mgr->verb[uc_mgr->card.current_verb]; + ret = verb->playback_pcm; + } + + pthread_mutex_unlock(&uc_mgr->mutex); + + return ret; +} + +/** + * \brief Get current use case verb playback PCM + * \param uc_mgr use case manager + * \return PCM number if success, otherwise negative + */ +int snd_use_case_get_verb_capture_pcm(snd_use_case_mgr_t *uc_mgr) +{ + struct use_case_verb *verb; + int ret = -EINVAL; + + pthread_mutex_lock(&uc_mgr->mutex); + + if (uc_mgr->card.current_verb != VERB_NOT_INITIALISED) { + verb = &uc_mgr->verb[uc_mgr->card.current_verb]; + ret = verb->capture_pcm; + } + + pthread_mutex_unlock(&uc_mgr->mutex); + + return ret; +} + +/** + * \brief Get current use case modifier playback PCM + * \param uc_mgr use case manager + * \return PCM number if success, otherwise negative + */ +int snd_use_case_get_mod_playback_pcm(snd_use_case_mgr_t *uc_mgr, + const char *modifier_name) +{ + struct use_case_modifier *modifier; + int ret = -EINVAL; + + pthread_mutex_lock(&uc_mgr->mutex); + + modifier = get_modifier(uc_mgr, modifier_name, NULL); + if (modifier == NULL) + uc_error("error: use case modifier %s not found", + modifier_name); + else + ret = modifier->playback_pcm; + + pthread_mutex_unlock(&uc_mgr->mutex); + + return ret; +} + +/** + * \brief Get current use case modifier playback PCM + * \param uc_mgr use case manager + * \return PCM number if success, otherwise negative + */ +int snd_use_case_get_mod_capture_pcm(snd_use_case_mgr_t *uc_mgr, + const char *modifier_name) +{ + struct use_case_modifier *modifier; + int ret = -EINVAL; + + pthread_mutex_lock(&uc_mgr->mutex); + + modifier = get_modifier(uc_mgr, modifier_name, NULL); + if (modifier == NULL) + uc_error("error: use case modifier %s not found", + modifier_name); + else + ret = modifier->capture_pcm; + + pthread_mutex_unlock(&uc_mgr->mutex); + + return ret; +} + +/** + * \brief Get volume/mute control name depending on use case device. + * \param uc_mgr use case manager + * \param type the control type we are looking for + * \param device_name The use case device we are interested in. + * \return control name if success, otherwise NULL + * + * Get the control id for common volume and mute controls that are aliased + * in the named use case device. + */ +const char *snd_use_case_get_device_ctl_elem_name(snd_use_case_mgr_t *uc_mgr, + enum snd_use_case_control_alias type, const char *device_name) +{ + struct use_case_device *device; + const char *kcontrol_name = NULL; + + pthread_mutex_lock(&uc_mgr->mutex); + + device = get_device(uc_mgr, device_name, NULL); + if (!device) { + uc_error("error: device %s not found", device_name); + goto out; + } + + switch (type) { + case SND_USE_CASE_ALIAS_PLAYBACK_VOLUME: + kcontrol_name = device->playback_volume_id; + break; + case SND_USE_CASE_ALIAS_CAPTURE_VOLUME: + kcontrol_name = device->capture_volume_id; + break; + case SND_USE_CASE_ALIAS_PLAYBACK_SWITCH: + kcontrol_name = device->playback_switch_id; + break; + case SND_USE_CASE_ALIAS_CAPTURE_SWITCH: + kcontrol_name = device->capture_switch_id; + break; + default: + uc_error("error: invalid control alias %d", type); + break; + } + +out: + pthread_mutex_unlock(&uc_mgr->mutex); + + return kcontrol_name; +} + +/** + * \brief Get volume/mute control IDs depending on use case modifier. + * \param uc_mgr use case manager + * \param type the control type we are looking for + * \param modifier_name The use case modifier we are interested in. + * \return ID if success, otherwise a negative error code + * + * Get the control id for common volume and mute controls that are aliased + * in the named use case device. + */ +const char *snd_use_case_get_modifier_ctl_elem_name(snd_use_case_mgr_t *uc_mgr, + enum snd_use_case_control_alias type, const char *modifier_name) +{ + struct use_case_modifier *modifier; + const char *kcontrol_name = NULL; + + pthread_mutex_lock(&uc_mgr->mutex); + + modifier = get_modifier(uc_mgr, modifier_name, NULL); + if (!modifier) { + uc_error("error: modifier %s not found", modifier_name); + goto out; + } + + switch (type) { + case SND_USE_CASE_ALIAS_PLAYBACK_VOLUME: + kcontrol_name = modifier->playback_volume_id; + break; + case SND_USE_CASE_ALIAS_CAPTURE_VOLUME: + kcontrol_name = modifier->capture_volume_id; + break; + case SND_USE_CASE_ALIAS_PLAYBACK_SWITCH: + kcontrol_name = modifier->playback_switch_id; + break; + case SND_USE_CASE_ALIAS_CAPTURE_SWITCH: + kcontrol_name = modifier->capture_switch_id; + break; + default: + uc_error("error: invalid control alias %d", type); + break; + } + +out: + pthread_mutex_unlock(&uc_mgr->mutex); + + return kcontrol_name; +} +#endif 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 + * Stefan Schmidt + * Justin Xu + * Jaroslav Kysela + */ + +#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 + * + * 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 + * + * # 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; +} diff --git a/src/ucm/ucm_local.h b/src/ucm/ucm_local.h new file mode 100644 index 00000000..ffef8f00 --- /dev/null +++ b/src/ucm/ucm_local.h @@ -0,0 +1,217 @@ +/* + * 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 + * Stefan Schmidt + * Justin Xu + * Jaroslav Kysela + */ + + + +#if 0 +#define UC_MGR_DEBUG +#endif + +#include "local.h" +#include "use-case.h" + +#define PRE_SEQ 0 +#define POST_SEQ 1 +#define MAX_FILE 256 +#define ALSA_USE_CASE_DIR ALSA_CONFIG_DIR "/ucm" +#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0])) +#define VERB_NOT_INITIALISED -1 + +#define SEQUENCE_ELEMENT_TYPE_CSET 1 +#define SEQUENCE_ELEMENT_TYPE_SLEEP 2 +#define SEQUENCE_ELEMENT_TYPE_EXEC 3 + +struct sequence_element { + struct list_head list; + unsigned int type; + union { + long sleep; /* Sleep time in msecs if sleep element, else 0 */ + char *cset; + char *exec; + } data; +}; + +/* + * Transition sequences. i.e. transition between one verb, device, mod to another + */ +struct transition_sequence { + struct list_head list; + char *name; + struct list_head transition_list; +}; + +/* + * Modifier Supported Devices. + */ +struct dev_list { + struct list_head list; + char *name; +}; + + +/* + * Describes a Use Case Modifier and it's enable and disable sequences. + * A use case verb can have N modifiers. + */ +struct use_case_modifier { + struct list_head list; + struct list_head active_list; + + char *name; + char *comment; + + /* modifier enable and disable sequences */ + struct list_head enable_list; + struct list_head disable_list; + + /* modifier transition list */ + struct list_head transition_list; + + /* list of supported devices per modifier */ + struct list_head dev_list; + + /* ALSA PCM devices associated with any modifier PCM streams */ + char *capture_pcm; + char *playback_pcm; + + /* Any modifier stream TQ */ + char *tq; + + /* aliased controls */ + char *playback_ctl; + char *playback_volume_id; + char *playback_switch_id; + char *capture_ctl; + char *capture_volume_id; + char *capture_switch_id; +}; + +/* + * Describes a Use Case Device and it's enable and disable sequences. + * A use case verb can have N devices. + */ +struct use_case_device { + struct list_head list; + struct list_head active_list; + + unsigned int active: 1; + + char *name; + char *comment; + long idx; /* index for similar devices i.e. 2 headphone jacks */ + + /* device enable and disable sequences */ + struct list_head enable_list; + struct list_head disable_list; + + /* device transition list */ + struct list_head transition_list; + + /* aliased controls */ + char *playback_ctl; + char *playback_volume_id; + char *playback_switch_id; + char *capture_ctl; + char *capture_volume_id; + char *capture_switch_id; +}; + +/* + * Describes a Use Case Verb and it's enable and disable sequences. + * A use case verb can have N devices and N modifiers. + */ +struct use_case_verb { + struct list_head list; + + unsigned int active: 1; + + char *name; + char *comment; + + /* verb enable and disable sequences */ + struct list_head enable_list; + struct list_head disable_list; + + /* verb transition list */ + struct list_head transition_list; + + /* verb PCMs and TQ */ + char *tq; + char *capture_pcm; + char *playback_pcm; + + /* hardware devices that can be used with this use case */ + struct list_head device_list; + + /* modifiers that can be used with this use case */ + struct list_head modifier_list; +}; + +/* + * Manages a sound card and all its use cases. + */ +struct snd_use_case_mgr { + char *card_name; + char *comment; + + /* use case verb, devices and modifier configs parsed from files */ + struct list_head verb_list; + + /* default settings - sequence */ + struct list_head default_list; + + /* current status */ + struct use_case_verb *active_verb; + struct list_head active_devices; + struct list_head active_modifiers; + + /* locking */ + pthread_mutex_t mutex; +}; + +#define uc_error SNDERR + +#ifdef UC_MGR_DEBUG +#define uc_dbg SNDERR +#else +#define uc_dbg(fmt, arg...) +#endif + +void uc_mgr_error(const char *fmt, ...); +void uc_mgr_stdout(const char *fmt, ...); + +int uc_mgr_config_load(const char *file, snd_config_t **cfg); +int uc_mgr_import_master_config(snd_use_case_mgr_t *uc_mgr); + +void uc_mgr_free_sequence_element(struct sequence_element *seq); +void uc_mgr_free_verb(snd_use_case_mgr_t *uc_mgr); +void uc_mgr_free(snd_use_case_mgr_t *uc_mgr); diff --git a/src/ucm/utils.c b/src/ucm/utils.c new file mode 100644 index 00000000..98e3cf65 --- /dev/null +++ b/src/ucm/utils.c @@ -0,0 +1,220 @@ +/* + * 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 + * Stefan Schmidt + * Justin Xu + * Jaroslav Kysela + */ + +#include "ucm_local.h" + +void uc_mgr_error(const char *fmt,...) +{ + va_list va; + va_start(va, fmt); + fprintf(stderr, "ucm: "); + vfprintf(stderr, fmt, va); + va_end(va); +} + +void uc_mgr_stdout(const char *fmt,...) +{ + va_list va; + va_start(va, fmt); + vfprintf(stdout, fmt, va); + va_end(va); +} + +int uc_mgr_config_load(const char *file, snd_config_t **cfg) +{ + FILE *fp; + snd_input_t *in; + snd_config_t *top; + int err; + + fp = fopen(file, "r"); + err = snd_input_stdio_attach(&in, fp, 1); + if (err < 0) { + uc_error("could not open configuration file %s", file); + return err; + } + err = snd_config_top(&top); + if (err < 0) + return err; + err = snd_config_load(top, in); + if (err < 0) { + uc_error("could not load configuration file %s", file); + snd_config_delete(top); + return err; + } + err = snd_input_close(in); + if (err < 0) { + snd_config_delete(top); + return err; + } + *cfg = top; + return 0; +} + +void uc_mgr_free_dev_list(struct list_head *base) +{ + struct list_head *pos, *npos; + struct dev_list *dlist; + + list_for_each_safe(pos, npos, base) { + dlist = list_entry(pos, struct dev_list, list); + free(dlist->name); + free(dlist); + list_del(pos); + } +} + +void uc_mgr_free_sequence_element(struct sequence_element *seq) +{ + if (seq == NULL) + return; + switch (seq->type) { + case SEQUENCE_ELEMENT_TYPE_CSET: + case SEQUENCE_ELEMENT_TYPE_EXEC: + free(seq->data.exec); + break; + default: + break; + } + free(seq); +} + +void uc_mgr_free_sequence(struct list_head *base) +{ + struct list_head *pos, *npos; + struct sequence_element *seq; + + list_for_each_safe(pos, npos, base) { + seq = list_entry(pos, struct sequence_element, list); + uc_mgr_free_sequence_element(seq); + list_del(pos); + } +} + +void uc_mgr_free_transition(struct list_head *base) +{ + struct list_head *pos, *npos; + struct transition_sequence *tseq; + + list_for_each_safe(pos, npos, base) { + tseq = list_entry(pos, struct transition_sequence, list); + free(tseq->name); + uc_mgr_free_sequence(&tseq->transition_list); + free(tseq); + list_del(pos); + } +} + +void uc_mgr_free_modifier(struct list_head *base) +{ + struct list_head *pos, *npos; + struct use_case_modifier *mod; + + list_for_each_safe(pos, npos, base) { + mod = list_entry(pos, struct use_case_modifier, list); + free(mod->name); + free(mod->comment); + uc_mgr_free_sequence(&mod->enable_list); + uc_mgr_free_sequence(&mod->disable_list); + uc_mgr_free_transition(&mod->transition_list); + uc_mgr_free_dev_list(&mod->dev_list); + free(mod->capture_pcm); + free(mod->playback_pcm); + free(mod->tq); + free(mod->playback_ctl); + free(mod->playback_volume_id); + free(mod->playback_switch_id); + free(mod->capture_ctl); + free(mod->capture_volume_id); + free(mod->capture_switch_id); + free(mod); + list_del(pos); + } +} + +void uc_mgr_free_device(struct list_head *base) +{ + struct list_head *pos, *npos; + struct use_case_device *dev; + + list_for_each_safe(pos, npos, base) { + dev = list_entry(pos, struct use_case_device, list); + free(dev->name); + free(dev->comment); + uc_mgr_free_sequence(&dev->enable_list); + uc_mgr_free_sequence(&dev->disable_list); + uc_mgr_free_transition(&dev->transition_list); + free(dev->playback_ctl); + free(dev->playback_volume_id); + free(dev->playback_switch_id); + free(dev->capture_ctl); + free(dev->capture_volume_id); + free(dev->capture_switch_id); + free(dev); + list_del(pos); + } +} + +void uc_mgr_free_verb(snd_use_case_mgr_t *uc_mgr) +{ + struct list_head *pos, *npos; + struct use_case_verb *verb; + + list_for_each_safe(pos, npos, &uc_mgr->verb_list) { + verb = list_entry(pos, struct use_case_verb, list); + free(verb->name); + free(verb->comment); + uc_mgr_free_sequence(&verb->enable_list); + uc_mgr_free_sequence(&verb->disable_list); + uc_mgr_free_transition(&verb->transition_list); + free(verb->tq); + free(verb->capture_pcm); + free(verb->playback_pcm); + uc_mgr_free_device(&verb->device_list); + uc_mgr_free_modifier(&verb->modifier_list); + free(verb); + list_del(pos); + } + uc_mgr_free_sequence(&uc_mgr->default_list); + free(uc_mgr->comment); + uc_mgr->comment = NULL; + uc_mgr->active_verb = NULL; + INIT_LIST_HEAD(&uc_mgr->active_devices); + INIT_LIST_HEAD(&uc_mgr->active_modifiers); +} + +void uc_mgr_free(snd_use_case_mgr_t *uc_mgr) +{ + uc_mgr_free_verb(uc_mgr); + free(uc_mgr->card_name); + free(uc_mgr); +} diff --git a/src/use-case.c b/src/use-case.c deleted file mode 100644 index b45fe1f1..00000000 --- a/src/use-case.c +++ /dev/null @@ -1,3515 +0,0 @@ -/* - * 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. - * Authors: Liam Girdwood - * Stefan Schmidt - * Justin Xu - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "../include/use-case.h" -#include "../include/iatomic.h" - -#define PRE_SEQ 0 -#define POST_SEQ 1 -#define MAX_VERB 32 -#define MAX_DEVICE 64 -#define MAX_MODIFIER 64 -#define MAX_NAME 64 -#define MAX_FILE 256 -#define MAX_BUF 256 -#define ALSA_USE_CASE_DIR "/usr/share/alsa/ucm" -#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0])) -#define VERB_NOT_INITIALISED -1 - -/* - * Stores all use case settings for 1 kcontrol. Hence we have a - * control_settings for each kcontrol in card. - */ -struct control_settings { - char name[MAX_NAME]; - unsigned int id; - snd_ctl_elem_type_t type; - short count; /* 1 = mono, 2 = stereo, etc */ - unsigned short *value; -}; - -/* - * If sleep is 0 the element contains the settings in control. Else sleep - * contains the sleep time in micro seconds. - */ -struct sequence_element { - unsigned int sleep; /* Sleep time in msecs if sleep element, else 0 */ - struct control_settings *control; - struct sequence_element *next; /* Pointer to next list element */ -}; - -/* - * Transition sequences. i.e. transition between one verb, device, mod to another - */ -struct transition_sequence { - char *name; - struct sequence_element *transition; - struct transition_sequence *next; -}; - -/* - * Modifier Supported Devicees. - */ -struct dev_list { - char *name; - struct dev_list *next; -}; - - -/* - * Describes a Use Case Modifier and it's enable and disable sequences. - * A use case verb can have N modifiers. - */ -struct use_case_modifier { - char *name; - char *comment; - - /* modifier enable and disable sequences */ - struct sequence_element *enable; - struct sequence_element *disable; - - /* modifier transition list */ - struct transition_sequence *transition_list; - - /* list of supported devices per modifier */ - struct dev_list *dev_list; - - /* ALSA PCM devices associated with any modifier PCM streams */ - int capture_pcm; - int playback_pcm; - - /* Any modifier stream QoS */ - enum snd_use_case_qos qos; - - /* aliased controls */ - char *playback_volume_id; - char *playback_switch_id; - char *capture_volume_id; - char *capture_switch_id; -}; - -/* - * Describes a Use Case Device and it's enable and disable sequences. - * A use case verb can have N devices. - */ -struct use_case_device { - char *name; - char *comment; - int idx; /* index for similar devices i.e. 2 headphone jacks */ - - /* device enable and disable sequences */ - struct sequence_element *enable; - struct sequence_element *disable; - - /* device transition list */ - struct transition_sequence *transition_list; - - /* aliased controls */ - char *playback_volume_id; - char *playback_switch_id; - char *capture_volume_id; - char *capture_switch_id; -}; - -/* - * Describes a Use Case Verb and it's enable and disable sequences. - * A use case verb can have N devices and N modifiers. - */ -struct use_case_verb { - char *name; - char *comment; - - /* verb enable and disable sequences */ - struct sequence_element *enable; - struct sequence_element *disable; - - /* verb transition list */ - struct transition_sequence *transition_list; - - /* verb PCMs and QoS */ - enum snd_use_case_qos qos; - int capture_pcm; - int playback_pcm; - - /* hardware devices that can be used with this use case */ - int num_devices; - struct use_case_device *device; - /* - * device_list[i] shares string with device[i].name, - * so device_list don't need not be freed - */ - const char *device_list[MAX_DEVICE]; - - /* modifiers that can be used with this use case */ - int num_modifiers; - struct use_case_modifier *modifier; - /* - * modifier_list[i] shares string with modifier[i].name, - * so modifier_list don't need not be freed - */ - const char *modifier_list[MAX_MODIFIER]; -}; - -struct ucm_card { - int current_verb; - int current_device[MAX_DEVICE]; - int current_modifier[MAX_MODIFIER]; -}; - -/* - * Manages a sound card and all its use cases. - */ -struct snd_use_case_mgr { - pthread_mutex_t mutex; - char *card_name; - char *ctl_name; - struct ucm_card card; - - /* use case verb, devices and modifier configs parsed from files */ - int num_verbs; /* number of supported use case verbs */ - struct use_case_verb *verb; /* var len array of use case info */ - /* - * verb_list[i] shares string with verb[i].name, - * so verb_list don't need not be freed - */ - const char *verb_list[MAX_VERB]; - - /* sound card ALSA kcontrol read from sound card device */ - struct control_settings *control; /* var len array of controls */ - - /* snd ctl */ - int count; - snd_ctl_t *handle; - snd_ctl_card_info_t *info; - snd_ctl_elem_list_t *list; - snd_ctl_elem_id_t *id; -}; - -static void uc_mgr_error(const char *fmt,...) -{ - va_list va; - va_start(va, fmt); - fprintf(stderr, "ucm: "); - vfprintf(stderr, fmt, va); - va_end(va); -} - -#define uc_error(fmt, arg...) do { \ - uc_mgr_error("%s() - " fmt "\n", __FUNCTION__ , ## arg); \ -} while (0) - -static void uc_mgr_stdout(const char *fmt,...) -{ - va_list va; - va_start(va, fmt); - vfprintf(stdout, fmt, va); - va_end(va); -} - -#undef UC_MGR_DEBUG - -#ifdef UC_MGR_DEBUG -#define uc_dbg(fmt, arg...) do { \ - uc_mgr_stdout("%s() - " fmt "\n", __FUNCTION__ , ## arg); \ -} while (0) -#else -#define uc_dbg(fmt, arg...) -#endif - -static int set_control(snd_ctl_t *handle, snd_ctl_elem_id_t *id, - snd_use_case_mgr_t *uc_mgr, unsigned short value[]); - -static inline void set_value(struct control_settings *control, - int count, unsigned short val) -{ - uc_dbg("value %d, count %d", val, count); - control->value[count] = val; -} - -static inline unsigned short get_value(struct control_settings *control, - int count) -{ - return control->value[count]; -} - -static inline void set_device_status(struct snd_use_case_mgr *uc_mgr, - int device_id, int status) -{ - struct use_case_verb *verb; - - verb = &uc_mgr->verb[uc_mgr->card.current_verb]; - uc_mgr->card.current_device[device_id] = status; -} - -static inline void set_modifier_status(struct snd_use_case_mgr *uc_mgr, - int modifier_id, int status) -{ - struct use_case_verb *verb; - - verb = &uc_mgr->verb[uc_mgr->card.current_verb]; - uc_mgr->card.current_modifier[modifier_id] = status; -} - -static inline int get_device_status(struct snd_use_case_mgr *uc_mgr, - int device_id) -{ - struct use_case_verb *verb; - - verb = &uc_mgr->verb[uc_mgr->card.current_verb]; - return uc_mgr->card.current_device[device_id]; -} - -static inline int get_modifier_status(struct snd_use_case_mgr *uc_mgr, - int modifier_id) -{ - struct use_case_verb *verb; - - verb = &uc_mgr->verb[uc_mgr->card.current_verb]; - return uc_mgr->card.current_modifier[modifier_id]; -} - -static int dump_control(snd_ctl_t *handle, snd_ctl_elem_id_t *id) -{ - int err, count, i; - snd_ctl_elem_info_t *info; - snd_ctl_elem_type_t type; - snd_ctl_elem_value_t *control; - - snd_ctl_elem_info_alloca(&info); - snd_ctl_elem_value_alloca(&control); - - snd_ctl_elem_info_set_id(info, id); - err = snd_ctl_elem_info(handle, info); - if (err < 0) { - uc_error("error: failed to get ctl info: %s\n", - snd_strerror(err)); - return err; - } - - snd_ctl_elem_value_set_id(control, id); - snd_ctl_elem_read(handle, control); - - type = snd_ctl_elem_info_get_type(info); - count = snd_ctl_elem_info_get_count(info); - if (count == 0) - return 0; - - uc_mgr_stdout("'%s':%d:", - snd_ctl_elem_id_get_name(id), count); - - switch (type) { - case SND_CTL_ELEM_TYPE_BOOLEAN: - for (i = 0; i < count - 1; i++) - uc_mgr_stdout("%d,", - snd_ctl_elem_value_get_boolean(control, i)); - uc_mgr_stdout("%d", - snd_ctl_elem_value_get_boolean(control, i)); - break; - case SND_CTL_ELEM_TYPE_INTEGER: - for (i = 0; i < count - 1; i++) - uc_mgr_stdout("%d,", - snd_ctl_elem_value_get_integer(control, i)); - uc_mgr_stdout("%d", - snd_ctl_elem_value_get_integer(control, i)); - break; - case SND_CTL_ELEM_TYPE_INTEGER64: - for (i = 0; i < count - 1; i++) - uc_mgr_stdout("%ld,", - snd_ctl_elem_value_get_integer64(control, i)); - uc_mgr_stdout("%ld", - snd_ctl_elem_value_get_integer64(control, i)); - break; - case SND_CTL_ELEM_TYPE_ENUMERATED: - for (i = 0; i < count - 1; i++) - uc_mgr_stdout("%d,", - snd_ctl_elem_value_get_enumerated(control, i)); - uc_mgr_stdout("%d", - snd_ctl_elem_value_get_enumerated(control, i)); - break; - case SND_CTL_ELEM_TYPE_BYTES: - for (i = 0; i < count - 1; i++) - uc_mgr_stdout("%2.2x,", - snd_ctl_elem_value_get_byte(control, i)); - uc_mgr_stdout("%2.2x", - snd_ctl_elem_value_get_byte(control, i)); - break; - default: - break; - } - uc_mgr_stdout("\n"); - return 0; -} - -/* - * Add new kcontrol from sound card into memory database. - */ -static int add_control(snd_ctl_t *handle, snd_ctl_elem_id_t *id, - struct control_settings *control_settings) -{ - int err; - snd_ctl_elem_info_t *info; - snd_ctl_elem_value_t *control; - - snd_ctl_elem_info_alloca(&info); - snd_ctl_elem_value_alloca(&control); - - snd_ctl_elem_info_set_id(info, id); - err = snd_ctl_elem_info(handle, info); - if (err < 0) { - uc_error("error: failed to get ctl info: %s\n", - snd_strerror(err)); - return err; - } - - snd_ctl_elem_value_set_id(control, id); - snd_ctl_elem_read(handle, control); - - strncpy(control_settings->name, snd_ctl_elem_id_get_name(id), - MAX_NAME); - - control_settings->name[MAX_NAME - 1] = 0; - control_settings->count = snd_ctl_elem_info_get_count(info); - control_settings->type = snd_ctl_elem_info_get_type(info); - control_settings->id = snd_ctl_elem_id_get_numid(id); - uc_dbg("control name %s", control_settings->name); - uc_dbg("control count %d", control_settings->count); - uc_dbg("control type %d", control_settings->type); - uc_dbg("control id %d", control_settings->id); - return 0; -} - -static int set_control_default(snd_use_case_mgr_t *uc_mgr, - struct control_settings *control) -{ - snd_ctl_elem_id_t *id; - int i, ret = -ENODEV; - unsigned int numid; - - snd_ctl_elem_id_alloca(&id); - - /* Where is id lookup from numid if you need it? */ - for (i = 0; i < uc_mgr->count; ++i) { - snd_ctl_elem_list_get_id(uc_mgr->list, i, id); - numid = snd_ctl_elem_id_get_numid(id); - if (numid == control->id) { - ret = set_control(uc_mgr->handle, id, uc_mgr, - control->value); - goto out; - } - } - uc_error("error: could not find control ID %s : %d", - control->name, control->id); -out: - return ret; -} - -static int get_control_id(snd_use_case_mgr_t *uc_mgr, - struct control_settings *control) -{ - int i = 0; - struct control_settings *card_control; - - uc_dbg("name %s count %d", - control->name, control->count); - - for (i = 0; i < uc_mgr->count; i++) { - card_control = &uc_mgr->control[i]; - if (!strcmp(card_control->name, control->name)) { - control->id = uc_mgr->control[i].id; - uc_dbg("Get id %d", control->id); - return 0; - } - } - - uc_error("error: control name %s is not available", control->name); - - return -EINVAL; -} - -static char *get_control_name(char *buf, int line, char *file) -{ - char name[MAX_NAME]; - char *name_start, *name_end, *tbuf = buf; - - /* get name start */ - while (*tbuf != 0 && *tbuf != '\'') - tbuf++; - if (*tbuf == 0) - return NULL; - name_start = ++tbuf; - - /* get name end */ - while (*tbuf != 0 && *tbuf != '\'') - tbuf++; - if (*tbuf == 0) - return NULL; - name_end = tbuf++; - - /* copy name */ - if ((name_end - name_start) > MAX_NAME) { - uc_error("error: %s:%d name too big at %d chars", - file, line, name_end - name_start); - return NULL; - } - strncpy(name, name_start, name_end - name_start); - name[name_end - name_start] = 0; - return strdup(name); -} - -/* - * Parse a single control from file. - * - * Controls are in the following form:- - * - * 'name':channels:value0,value1,...,valueN - * - * e.g. - * 'Master Playback Switch':2:0,0 - */ -static int parse_control(snd_use_case_mgr_t *uc_mgr, - struct control_settings *control, char *buf, - int line, char *file) -{ - char name[MAX_NAME]; - int count, i; - char *name_start, *name_end, *tbuf; - - uc_dbg("%s", buf); - - tbuf = buf; - - /* get name start */ - while (*tbuf != 0 && *tbuf != '\'') - tbuf++; - if (*tbuf == 0) - return -EINVAL; - name_start = ++tbuf; - - /* get name end */ - while (*tbuf != 0 && *tbuf != '\'') - tbuf++; - if (*tbuf == 0) - return -EINVAL; - name_end = tbuf++; - - /* copy name */ - if ((name_end - name_start) > MAX_NAME) { - uc_error("error: %s:%d name too big at %d chars", - file, line, name_end - name_start); - return -EINVAL; - } - strncpy(name, name_start, name_end - name_start); - name[name_end - name_start] = 0; - strncpy(control->name, name, name_end - name_start +1); - - /* get count */ - uc_dbg("%s", tbuf); - tbuf++; - count = atoi(tbuf); - if (count == 0) { - uc_error("error: %s:%d count == 0 on line %d", file, line); - return -EINVAL; - } - control->count = count; - - /* get vals */ - control->value = calloc(count, sizeof(unsigned short)); - if (control->value == NULL) - return -ENOMEM; - - while (*tbuf != 0 && *tbuf != ':') - tbuf++; - if (*tbuf == 0) - return -EINVAL; - tbuf++; - - for (i = 0; i < count; i++) { - set_value(control, i, atoi(tbuf)); - while (*tbuf != 0 && *tbuf != ',') - tbuf++; - - if (*tbuf++ == 0 && i < (count - 1)) - return -EINVAL; - } - - return get_control_id(uc_mgr, control); -} - -static int parse_controls(snd_use_case_mgr_t *uc_mgr, FILE *f, int *line_, - char *file) -{ - struct control_settings *control = NULL; - char buf[MAX_BUF], name[MAX_NAME]; - int count, i, ret = 0, line = *line_; - char *name_start, *name_end, *tbuf; - - while (fgets(buf, MAX_BUF, f) != NULL) { - - uc_dbg("%s: get line %d\n%s", file, line, buf); - - tbuf = buf; - while (isblank(*tbuf)) - tbuf++; - line ++; - - /* end of section ?*/ - if (strncmp(tbuf, "EndSectionDefaults", 18) == 0) - return 0; - - /* get name start */ - while (*tbuf != 0 && *tbuf != '\'') - tbuf++; - if (*tbuf == 0) - return -EINVAL; - name_start = ++tbuf; - - /* get name end */ - while (*tbuf != 0 && *tbuf != '\'') - tbuf++; - if (*tbuf == 0) - return -EINVAL; - name_end = tbuf++; - - /* copy name */ - if ((name_end - name_start) > MAX_NAME) { - uc_error("error: %s:%d name too big at %d chars", - file, line, name_end - name_start); - return -EINVAL; - } - strncpy(name, name_start, name_end - name_start); - name[name_end - name_start] = 0; - - for (i = 0; i < uc_mgr->count; i++) { - struct control_settings *card_control = - &uc_mgr->control[i]; - if (!strcmp(card_control->name, name)) - control = &uc_mgr->control[i]; - } - - uc_dbg("control id %d", control->id); - - /* get count */ - tbuf++; - count = atoi(tbuf); - if (count == 0) { - uc_error("error: %s:%d count == 0", file, line); - return -EINVAL; - } - if (count != control->count) { - uc_error("error: %s:%d count %d does not match card count" - " %d", file, line, count, control->count); - return -EINVAL; - } - - /* get vals */ - control->value = malloc(control->count *uc_mgr->num_verbs * - sizeof(unsigned short)); - if (control->value == NULL) - return -ENOMEM; - - while (*tbuf != 0 && *tbuf != ':') - tbuf++; - if (*tbuf == 0) - return -EINVAL; - tbuf++; - - for (i = 0; i < count; i++) { - set_value(control, i, atoi(tbuf)); - while (*tbuf != 0 && *tbuf != ',') - tbuf++; - - if (*tbuf++ == 0 && i < (count - 1)) - return -EINVAL; - } - } - - *line_ = line; - return ret; -} - -static char *get_string (char *buf) -{ - char *str, *end; - - uc_dbg("%s", buf); - - while (isblank(*buf)) - buf++; - - /* find leading '"' */ - if (*buf == 0 || *buf != '"') { - uc_error("error: missing start '\"'"); - return NULL; - } - str = ++buf; - - /* get value */ - while (*buf != 0 && *buf != '"') - buf++; - end = buf; - - /* find '"' terminator */ - if (*buf == 0 || *buf != '"') { - uc_error("error: missing terminator '\"' %s", buf); - return NULL; - } - - *end = 0; - return strdup(str); -} - -static enum snd_use_case_qos get_enum (char *buf) -{ - while (isblank(*buf)) - buf++; - - if (!strncmp(buf, "Music", 5)) - return SND_USE_CASE_QOS_MUSIC; - if (!strncmp(buf, "Voice", 5)) - return SND_USE_CASE_QOS_VOICE; - if (!strncmp(buf, "Tones", 5)) - return SND_USE_CASE_QOS_TONES; - - return SND_USE_CASE_QOS_MUSIC; -} - -static void seq_list_append(struct sequence_element **base, - struct sequence_element *curr) -{ - struct sequence_element *last, *tmp; - - if (!*base) - *base = curr; - else { - tmp = *base; - while (tmp) { - last = tmp; - tmp = tmp->next; - } - last->next = curr; - curr->next = NULL; - } -} -/* - * Parse sequences. - * - * Sequence controls elements are in the following form:- - * - * 'name':value0,value1,...,valueN - * - * e.g. - * 'Master Playback Switch':0,0 - */ -static int parse_sequence(snd_use_case_mgr_t *uc_mgr, - struct sequence_element **base, FILE *f, - int *line_, char *file) -{ - char buf[MAX_BUF], *tbuf; - int ret, line = *line_; - struct sequence_element *curr; - - /* read line by line */ - while (fgets(buf, MAX_BUF, f) != NULL) { - - uc_dbg("%s", buf); - line++; - - /* Check for lines with comments and ignore */ - if (buf[0] == '#') - continue; - - /* Parse current line and skip blanks */ - tbuf = buf; - while (isblank(*tbuf)) - tbuf++; - - /* end of sequence ? */ - if (strncmp(tbuf, "EndSequence", 11) == 0) - goto out; - - if (strncmp(tbuf, "EndTransition", 13) == 0) - goto out; - - /* alloc new sequence element */ - curr = calloc(1, sizeof(struct sequence_element)); - if (curr == NULL) - return -ENOMEM; - - /* is element a sleep ? */ - if (strncmp(tbuf, "msleep", 6) == 0) { - curr->sleep = atoi(tbuf + 6); - uc_dbg("msleep %d", curr->sleep); - seq_list_append(base, curr); - continue; - } - - /* alloc new sequence control */ - curr->control = calloc(1, sizeof(struct control_settings)); - if (curr->control == NULL) - return -ENOMEM; - - ret = parse_control(uc_mgr, curr->control, tbuf, line, file); - if (ret < 0) { - uc_error("error: %s:%d failed to get parse sequence" - " controls", file, line); - goto err; - } - - uc_dbg("name %s, id %d, count %d", curr->control->name, - curr->control->id, curr->control->count); - seq_list_append(base, curr); - } - -out: - *line_ = line; - return 0; - -err: - free(curr); - return ret; -} - -static void prepend_transition( - struct transition_sequence **transition_list, - struct transition_sequence *trans_seq) -{ - if (*transition_list == NULL) - *transition_list = trans_seq; - else { - trans_seq->next = *transition_list; - *transition_list = trans_seq; - } - -} - -static void prepend_dev(struct dev_list **dev_list, - struct dev_list *sdev) -{ - if (*dev_list == NULL) - *dev_list = sdev; - else { - sdev->next = *dev_list; - *dev_list = sdev; - } - -} - -/* - * Parse Modifier Use cases - * - * # Each modifier is described in new section. N modifier are allowed - * SectionModifier - * - * Name "Capture Voice" - * Comment "Record voice call" - * SupportedDevice "x" - * SupportedDevice "y" - * - * EnableSequence - * .... - * EndSequence - * - * DisableSequence - * ... - * EndSequence - * - * # Optional QoS and ALSA PCMs - * QoS Voice - * CapturePCM 1 - * MasterPlaybackVolume 'Device Master Playback Volume' - * MasterPlaybackSwitch 'Device Master Playback Switch' - * - * EndSection - */ -static int parse_modifier(snd_use_case_mgr_t *uc_mgr, - struct use_case_verb *verb, FILE *f, int *line_, char *file) -{ - struct use_case_modifier *modifier; - int line = *line_, end = 0, en_seq = 0, dis_seq = 0; - int id = 0, dev = 0, ret; - char buf[MAX_BUF], *tbuf; - char *name = NULL, *comment = NULL; - - /* allocate modifier */ - verb->modifier = realloc(verb->modifier, - (verb->num_modifiers + 1) * sizeof(struct use_case_modifier)); - if (verb->modifier == NULL) - return -ENOMEM; - modifier = verb->modifier + verb->num_modifiers; - bzero(modifier, sizeof(struct use_case_modifier)); - - /* read line by line */ - while(fgets(buf, MAX_BUF, f) != NULL) { - - line++; - - /* skip comments */ - if (buf[0] == '#') - continue; - - tbuf = buf; - /* skip spaces */ - while (isblank(*tbuf)) - tbuf++; - - /* get use case modifier name */ - if (strncmp(tbuf, "Name", 4) == 0) { - name = get_string(tbuf + 4); - if (name == NULL) { - uc_error("error: %s:%d failed to get modifier name", - file, line); - goto err; - } - modifier->name = name; - id = 1; - continue; - } - - if (strncmp(tbuf, "Comment", 8) == 0) { - name = get_string(tbuf + 8); - if (name == NULL) { - uc_error("error: %s:%d failed to get modifier comment", - file, line); - goto err; - } - modifier->comment = comment; - continue; - } - - if (strncmp(tbuf, "SupportedDevice", 15) == 0) { - struct dev_list *sdev; - name = get_string(tbuf + 15); - if (name == NULL) { - uc_error("error: %s:%d failed to get modifier" - " supported device", file, line); - goto err; - } - - sdev = calloc(1, sizeof(struct dev_list)); - if (sdev == NULL) - goto err; - - dev = 1; - sdev->name = name; - prepend_dev(&modifier->dev_list, sdev); - continue; - } - - if (strncmp(tbuf, "EnableSequence", 14) == 0) { - ret = parse_sequence(uc_mgr, &modifier->enable, f, - &line, file); - if (ret < 0) { - uc_error("error: %s:%d failed to parse modifier" - " enable sequence", file, line); - goto err; - } - en_seq = 1; - continue; - } - - if (strncmp(tbuf, "DisableSequence", 15) == 0) { - ret = parse_sequence(uc_mgr, &modifier->disable, f, - &line, file); - if (ret < 0) { - uc_error("error: %s:%d failed to parse modifier" - " disable sequence", file, line); - goto err; - } - dis_seq = 1; - continue; - } - - if (strncmp(tbuf, "TransitionModifier", 16) == 0) { - struct transition_sequence *trans_seq; - - name = get_string(tbuf + 16); - if (name == NULL) - continue; - - trans_seq = calloc(1, sizeof(struct transition_sequence)); - if (trans_seq == NULL) - return -ENOMEM; - - trans_seq->name = name; - - ret = parse_sequence(uc_mgr, &trans_seq->transition, f, - &line, file); - if (ret < 0) { - uc_error("error: %s:%d failed to parse transition" - " modifier", file, line); - goto err; - } - - prepend_transition(&modifier->transition_list, trans_seq); - continue; - } - - if (strncmp(tbuf, "QoS", 3) == 0) { - modifier->qos = get_enum(tbuf + 3); - continue; - } - - if (strncmp(tbuf, "CapturePCM", 3) == 0) { - modifier->capture_pcm = atoi(tbuf + 3); - if (modifier->capture_pcm < 0) { - uc_error("error: %s:%d failed to get Capture PCM ID", - file, line); - goto err; - } - continue; - } - - if (strncmp(tbuf, "PlaybackPCM", 3) == 0) { - modifier->playback_pcm = atoi(tbuf + 3); - if (modifier->playback_pcm < 0) { - uc_error("error: %s:%d failed to get Playback PCM ID", - file, line); - goto err; - } - continue; - } - - if (strncmp(tbuf, "MasterPlaybackVolume", 20) == 0) { - modifier->playback_volume_id = - get_control_name(tbuf + 20, line, file); - if (modifier->playback_volume_id == NULL) { - uc_error("error: %s:%d failed to get MasterPlaybackVolume", - file, line); - goto err; - } - continue; - } - - if (strncmp(tbuf, "MasterPlaybackSwitch", 20) == 0) { - modifier->playback_switch_id = - get_control_name(tbuf + 20, line, file); - if (modifier->playback_switch_id == NULL) { - uc_error("error: %s:%d failed to get MasterPlaybackSwitch", - file, line); - goto err; - } - continue; - } - - if (strncmp(tbuf, "MasterCaptureVolume", 19) == 0) { - modifier->capture_volume_id = - get_control_name(tbuf + 19, line, file); - if (modifier->capture_volume_id == NULL) { - uc_error("error: %s:%d failed to get MasterCaptureVolume", - file, line); - goto err; - } - continue; - } - - if (strncmp(tbuf, "MasterCaptureSwitch", 19) == 0) { - modifier->capture_switch_id = - get_control_name(tbuf + 19, line, file); - if (modifier->capture_switch_id == NULL) { - uc_error("error: %s:%d failed to get MasterCaptureSwitch", - file, line); - goto err; - } - continue; - } - - /* end of section ?*/ - if (strncmp(tbuf, "EndSection", 10) == 0) { - end = 1; - break; - } - } - - /* do we have the modifier basics ? */ - if (!en_seq || !dis_seq || !end || !dev) { - uc_error("error: invalid modifier"); - if (!en_seq) - uc_error("error: %s: modifier missing enable sequence", - file); - if (!dis_seq) - uc_error("error: %s: modifier missing disable sequence", - file); - if (!dev) - uc_error("error: %s: modifier missing supported device" - " sequence", file); - if (!end) - uc_error("error: %s: modifier missing end", file); - return -EINVAL; - } - - verb->modifier_list[verb->num_modifiers++] = modifier->name; - *line_ = line; - return 0; - -err: - if (ferror(f)) { - uc_error("error: %s: failed to read modifier master section", - file); - return ferror(f); - } - return -ENOMEM; -} - -/* - * Parse Device Use Cases - * - *# Each device is described in new section. N devices are allowed - *SectionDevice - * - * Name "Headphones" - * Comment "Headphones connected to 3.5mm jack" - * Index 0 - * - * EnableSequence - * .... - * EndSequence - * - * DisableSequence - * ... - * EndSequence - * - * #Optional control aliases - * MasterPlaybackVolume 'Device Master Playback Volume' - * MasterPlaybackSwitch 'Device Master Playback Switch' - * - * EndSection - */ -static int parse_device(snd_use_case_mgr_t *uc_mgr, - struct use_case_verb *verb, FILE *f, int *line_, char *file) -{ - struct use_case_device *device; - int line = *line_, end = 0, en_seq = 0, dis_seq = 0, id = 0, ret; - char buf[MAX_BUF], *tbuf; - char *name = NULL, *comment; - - /* allocate device */ - verb->device = realloc(verb->device, - (verb->num_devices + 1) * sizeof(struct use_case_device)); - if (verb->device == NULL) - return -ENOMEM; - device = verb->device + verb->num_devices; - bzero(device, sizeof(struct use_case_device)); - - /* read line by line */ - while(fgets(buf, MAX_BUF, f) != NULL) { - - line++; - - /* skip comments */ - if (buf[0] == '#') - continue; - - tbuf = buf; - /* skip spaces */ - while (isblank(*tbuf)) - tbuf++; - - /* get use case device name */ - if (strncmp(tbuf, "Name", 4) == 0) { - name = get_string(tbuf + 4); - if (name == NULL) { - uc_error("error: %s:%d failed to get device name", - file, line); - goto err; - } - device->name = name; - id = 1; - continue; - } - - if (strncmp(tbuf, "Comment", 8) == 0) { - comment = get_string(tbuf + 8); - if (name == NULL) { - uc_error("error: %s: %d failed to get device comment", - file, line); - goto err; - } - - device->comment = comment; - continue; - } - - if (strncmp(tbuf, "Index", 5) == 0) { - device->idx = atoi(tbuf + 5); - if (device->idx < 0) { - uc_error("error: %s:%d failed to get device index", - file, line); - goto err; - } - continue; - } - - if (strncmp(tbuf, "EnableSequence", 14) == 0) { - ret = parse_sequence(uc_mgr, &device->enable, f, - &line, file); - if (ret < 0) { - uc_error("error: %s:%d failed to parse device enable" - " sequence", file, line); - goto err; - } - en_seq = 1; - continue; - } - - if (strncmp(tbuf, "DisableSequence", 15) == 0) { - uc_dbg("DisableSequence"); - ret = parse_sequence(uc_mgr, &device->disable, f, - &line, file); - if (ret < 0) { - uc_error("error: %s:%d failed to parse device disable sequence", - file, line); - goto err; - } - dis_seq = 1; - continue; - } - - if (strncmp(tbuf, "TransitionDevice", 14) == 0) { - struct transition_sequence *trans_seq; - - name = get_string(tbuf + 14); - if (name == NULL) - continue; - - uc_dbg("TransitionDevice %s", name); - - trans_seq = calloc(1, sizeof(struct transition_sequence)); - if (trans_seq == NULL) - return -ENOMEM; - - trans_seq->name = name; - - ret = parse_sequence(uc_mgr, &trans_seq->transition, f, - &line, file); - if (ret < 0) { - uc_error("error: %s:%d failed to parse transition" - " device", file, line); - goto err; - } - - prepend_transition(&device->transition_list, trans_seq); - continue; - } - - if (strncmp(tbuf, "MasterPlaybackVolume", 20) == 0) { - device->playback_volume_id = - get_control_name(tbuf + 20, line, file); - if (device->playback_volume_id == NULL) { - uc_error("error: %s:%d failed to get MasterPlaybackVolume", - file, line); - goto err; - } - continue; - } - - if (strncmp(tbuf, "MasterPlaybackSwitch", 20) == 0) { - device->playback_switch_id = - get_control_name(tbuf + 20, line, file); - if (device->playback_switch_id == NULL) { - uc_error("error: %s:%d failed to get MasterPlaybackSwitch", - file, line); - goto err; - } - continue; - } - - if (strncmp(tbuf, "MasterCaptureVolume", 19) == 0) { - device->capture_volume_id = - get_control_name(tbuf + 19, line, file); - if (device->capture_volume_id == NULL) { - uc_error("error: %s:%d failed to get MasterCaptureVolume%d", - file, line); - goto err; - } - continue; - } - - if (strncmp(tbuf, "MasterCaptureSwitch", 19) == 0) { - device->capture_switch_id = - get_control_name(tbuf + 19, line, file); - if (device->capture_switch_id == NULL) { - uc_error("error: %s:%d failed to get MasterCaptureSwitch", - file, line); - goto err; - } - continue; - } - - /* end of section ?*/ - if (strncmp(tbuf, "EndSection", 10) == 0) { - end = 1; - break; - } - } - - /* do we have the basics for this device ? */ - if (!en_seq || !dis_seq || !end || !id) { - uc_error("error: invalid device"); - if (!en_seq) - uc_error("error: %s: device missing enable sequence", file); - if (!dis_seq) - uc_error("error: %s: device missing disable sequence", file); - if (!end) - uc_error("error: %s: device missing end", file); - return -EINVAL; - } - - verb->device_list[verb->num_devices++] = device->name; - *line_ = line; - return 0; - -err: - if (ferror(f)) { - uc_error("%s: failed to read device section", file); - return ferror(f); - } - return -ENOMEM; -} - -/* - * Parse Verb Section - * - * # Example Use case verb section for Voice call blah - * # By Joe Blogs - * - * SectionVerb - * # enable and disable sequences are compulsory - * EnableSequence - * 'Master Playback Switch':2:0,0 - * 'Master Playback Volume':2:25,25 - * msleep 50 - * 'Master Playback Switch':2:1,1 - * 'Master Playback Volume':2:50,50 - * EndSequence - * - * DisableSequence - * 'Master Playback Switch':2:0,0 - * 'Master Playback Volume':2:25,25 - * msleep 50 - * 'Master Playback Switch':2:1,1 - * 'Master Playback Volume':2:50,50 - * EndSequence - * - * # Optional QoS and ALSA PCMs - * QoS HiFi - * CapturePCM 0 - * PlaybackPCM 0 - * - * EndSection - */ -static int parse_verb(snd_use_case_mgr_t *uc_mgr, - struct use_case_verb *verb, FILE *f, int *line_, char *file) -{ - int line = *line_, end = 0, en_seq = 0, dis_seq = 0, ret; - char buf[MAX_BUF], *tbuf; - - /* read line by line */ - while(fgets(buf, MAX_BUF, f) != NULL) { - - line++; - - /* skip comments */ - if (buf[0] == '#') - continue; - - tbuf = buf; - /* skip spaces */ - while (isblank(*tbuf)) - tbuf++; - - if (strncmp(tbuf, "EnableSequence", 14) == 0) { - uc_dbg("Parse EnableSequence"); - ret = parse_sequence(uc_mgr, &verb->enable, f, &line, file); - if (ret < 0) { - uc_error("error: %s:%d failed to parse verb enable sequence", - file, line); - goto err; - } - en_seq = 1; - continue; - } - - if (strncmp(tbuf, "DisableSequence", 15) == 0) { - uc_dbg("Parse DisableSequence"); - ret = parse_sequence(uc_mgr, &verb->disable, f, &line, file); - if (ret < 0) { - uc_error("error: %s:%d failed to parse verb disable sequence", - file, line); - goto err; - } - dis_seq = 1; - continue; - } - - if (strncmp(tbuf, "TransitionVerb", 12) == 0) { - struct transition_sequence *trans_seq; - char *name; - - name = get_string(tbuf + 12); - if (name == NULL) - continue; - - uc_dbg("TransitionVerb %s", name); - - trans_seq = calloc(1, sizeof(struct transition_sequence)); - if (trans_seq == NULL) - return -ENOMEM; - - trans_seq->name = name; - - ret = parse_sequence(uc_mgr, &trans_seq->transition, f, - &line, file); - if (ret < 0) { - uc_error("error: %s:%d failed to parse transition verb", - file, line); - goto err; - } - - prepend_transition(&verb->transition_list, trans_seq); - continue; - } - - if (strncmp(tbuf, "QoS", 3) == 0) { - uc_dbg("Parse Qos"); - verb->qos = get_enum(tbuf + 3); - continue; - } - - if (strncmp(tbuf, "CapturePCM", 3) == 0) { - uc_dbg("Parse CapTurePCM"); - verb->capture_pcm = atoi(tbuf + 3); - if (verb->capture_pcm < 0) { - uc_error("error: %s:%d failed to get Capture PCM ID", - file, line); - goto err; - } - continue; - } - - if (strncmp(tbuf, "PlaybackPCM", 3) == 0) { - uc_dbg("Parse PlaybackPCM"); - verb->playback_pcm = atoi(tbuf + 3); - if (verb->playback_pcm < 0) { - uc_error("error: %s: %d failed to get Playback PCM ID", - file, line); - goto err; - } - continue; - } - - /* end of section ?*/ - if (strncmp(tbuf, "EndSection", 10) == 0) { - end = 1; - break; - } - } - - /* do we have both use case name and file ? */ - if (!en_seq || !dis_seq || !end) { - uc_error("error: invalid verb"); - if (!en_seq) - uc_error("error: %s: verb missing enable sequence", file); - if (!dis_seq) - uc_error("error: %s: verb missing disable sequence", file); - if (!end) - uc_error("error: %s: verb missing end", file); - return -EINVAL; - } - - *line_ = line; - return 0; - -err: - if (ferror(f)) { - uc_error("error: failed to read verb master section"); - return ferror(f); - } - return -ENOMEM; -} - -/* - * 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, - char *use_case_name, char *file) -{ - struct use_case_verb *verb; - FILE *f; - int line = 0, ret = 0, dev_idx = 0, verb_idx = 0, mod_idx = 0; - char buf[MAX_BUF], *tbuf; - char filename[MAX_FILE]; - - /* allocate verb */ - uc_mgr->verb = realloc(uc_mgr->verb, - (uc_mgr->num_verbs + 1) * sizeof(struct use_case_verb)); - if (uc_mgr->verb == NULL) - return -ENOMEM; - verb = uc_mgr->verb +uc_mgr->num_verbs; - bzero(verb, sizeof(struct use_case_verb)); - - /* open Verb file for reading */ - sprintf(filename, "%s/%s/%s", ALSA_USE_CASE_DIR, - uc_mgr->card_name, file); - - f = fopen(filename, "r"); - if (f == NULL) { - uc_error("error: failed to open verb file %s : %d", - filename, -errno); - return -errno; - } - - /* read line by line */ - while(fgets(buf, MAX_BUF, f) != NULL) { - - line++; - - /* skip comments */ - if (buf[0] == '#') - continue; - - /* skip leading spaces */ - tbuf = buf; - while (isblank(*tbuf)) - tbuf++; - - /* find verb section and parse it */ - if (strncmp(tbuf, "SectionVerb", 11) == 0) { - ret = parse_verb(uc_mgr, verb, f, &line, file); - if (ret < 0) { - uc_error("error: %s:%d failed to parse verb %s", - file, line, use_case_name); - goto err; - } - verb_idx++; - continue; - } - - /* find device sections and parse them */ - if (strncmp(tbuf, "SectionDevice", 13) == 0) { - - if (verb->num_devices >= MAX_DEVICE) { - uc_error("error: %s:%d verb number of devices %d" - "exceeds max %d", file, line, - verb->num_devices, MAX_DEVICE); - goto err; - } - ret = parse_device(uc_mgr, verb, f, &line, file); - if (ret < 0) { - uc_error("error: %s:%d failed to parse device", - file, line); - goto err; - } - dev_idx++; - continue; - } - - /* find modifier sections and parse them */ - if (strncmp(tbuf, "SectionModifier", 15) == 0) { - if (verb->num_modifiers >= MAX_MODIFIER) { - uc_error("error: %s:%d verb number of modifiers %d" - " exceeds max %d", file, line, - verb->num_modifiers, MAX_MODIFIER); - goto err; - } - - ret = parse_modifier(uc_mgr, verb, f, &line, file); - if (ret < 0) { - uc_error("error: %s:%d failed to parse modifier", - file, line); - goto err; - } - mod_idx++; - continue; - } - } - - /* use case verb must have at least verb and 1 device */ - if (verb_idx && dev_idx) { - uc_mgr->verb_list[uc_mgr->num_verbs++] = use_case_name; - verb->name = use_case_name; - } - else { - uc_error("error: failed to parse use case %s", file); - if (verb_idx == 0) - uc_error("error: no use case verb defined", file); - if (dev_idx == 0) - uc_error("error: no use case device defined", file); - ret = -EINVAL; - } - -err: - fclose(f); - return ret; -} - -/* - * Parse master section for "Use Case" and "File" tags. - */ -static int parse_master_section(snd_use_case_mgr_t *uc_mgr, FILE *f, - int *line_) -{ - int line = *line_ - 1, end = 0; - char buf[MAX_BUF], *tbuf; - char *file = NULL, *use_case_name = NULL, *comment; - - /* read line by line */ - while(fgets(buf, MAX_BUF, f) != NULL) { - - line++; - - /* skip comments */ - if (buf[0] == '#') - continue; - - uc_dbg("%s", buf); - - /* skip leading spaces */ - tbuf = buf; - while (isblank(*tbuf)) - tbuf++; - - /* get use case name */ - if (strncmp(tbuf, "Use Case", 8) == 0) { - use_case_name = get_string(tbuf + 8); - if (use_case_name == NULL) { - uc_error("error: failed to get Use Case at line %d", - line); - goto err; - } - - if (uc_mgr->num_verbs >= MAX_VERB) { - uc_error("error: verb number exceed max %d", - uc_mgr->num_verbs, MAX_VERB); - goto err; - } - continue; - } - - /* get use case verb file name */ - if (strncmp(tbuf, "File", 4) == 0) { - file = get_string(tbuf + 4); - if (file == NULL) { - uc_error("error: failed to get File at line %d", line); - goto err; - } - continue; - } - - /* get optional use case comment */ - if (strncmp(tbuf, "Comment", 7) == 0) { - comment = get_string(tbuf + 7); - if (comment == NULL) { - uc_error("error: failed to get Comment at line %d", - line); - goto err; - } - continue; - } - - /* end of section ?*/ - if (strncmp(tbuf, "EndSectionUseCase", 10) == 0) { - end = 1; - break; - } - } - - 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 (!use_case_name || !file || !end) { - uc_error("error: failed to find use case\n"); - if (!use_case_name) - uc_error("error: use case missing name"); - if (!file) - uc_error("error: use case missing file"); - if (!end) - uc_error("error: use case missing end"); - return -EINVAL; - } - - *line_ = line; - - /* parse verb file */ - return parse_verb_file(uc_mgr, use_case_name, file); - -err: - if (ferror(f)) { - uc_error("error: failed to read use case master section"); - return ferror(f); - } - return -ENOMEM; -} - -/* - * 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 - * - * # The file is divided into Use case sections. One section per use case verb. - * - * SectionUseCase - * Use Case "Voice Call" - * File "voice_call_blah" - * Comment "Make a voice phone call." - * EndSectionUseCase - * - * SectionUseCase - * Use Case "HiFi" - * File "hifi_blah" - * Comment "Play and record HiFi quality Music." - * EndSectionUseCase - * - * # This file also stores the default sound card state. - * - * SectionDefaults - * 'Master Playback Switch':2:1,1 - * 'Master Playback Volume':2:25,25 - * 'Master Mono Playback Switch':1:0 - * 'Master Mono Playback Volume':1:0 - * 'PCM Switch':2:1,1 - * ........ - * EndSectionDefaults - * - * # End of example file. - */ -static int parse_master_file(snd_use_case_mgr_t *uc_mgr, FILE *f, - char *file) -{ - int line = 0, ret = 0; - char buf[MAX_BUF], *tbuf; - - /* parse master config sections */ - while(fgets(buf, MAX_BUF, f) != NULL) { - - line++; - - /* ignore comments */ - if (buf[0] == '#') { - line++; - continue; - } - - /* find use case section and parse it */ - if (strncmp(buf, "SectionUseCase", 14) == 0) { - tbuf = buf + 14; - while (isblank(*tbuf)) - tbuf++; - ret = parse_master_section(uc_mgr, f, &line); - if (ret < 0) - goto err; - } - - /* find default control values section and parse it */ - if (strncmp(buf, "SectionDefaults", 15) == 0) { - tbuf = buf + 15; - while (isblank(*tbuf)) - tbuf++; - ret = parse_controls(uc_mgr, f, &line, file); - if (ret < 0) - goto err; - } - } - -err: - if (ferror(f)) { - uc_error("error: %s: failed to read master file", file); - return ferror(f); - } - return ret; -} - -/* load master use case file for sound card */ -static int import_master_config(snd_use_case_mgr_t *uc_mgr) -{ - int ret; - FILE *f; - char filename[MAX_FILE]; - - sprintf(filename, "%s/%s/%s.conf", ALSA_USE_CASE_DIR, - uc_mgr->card_name, uc_mgr->card_name); - - uc_dbg("master config file %s", filename); - - f = fopen(filename, "r"); - if (f == NULL) { - uc_error("error: couldn't open %s configuration file %s", - uc_mgr->card_name, filename); - return -errno; - } - - ret = parse_master_file(uc_mgr, f, uc_mgr->card_name); - fclose(f); - return ret; -} - -static int parse_card_controls(snd_use_case_mgr_t *uc_mgr) -{ - struct control_settings *control; - int i, ret = 0; - snd_ctl_elem_id_t *id; - - /* allocate memory for controls */ - uc_mgr->control = calloc(uc_mgr->count, - sizeof(struct control_settings)); - if (uc_mgr->control == NULL) { - uc_error("error: not enough memory to store controls.\n"); - return -ENOMEM; - } - control = uc_mgr->control; - snd_ctl_elem_id_alloca(&id); - - /* iterate through each kcontrol and add to manager */ - for (i = 0; i < uc_mgr->count; ++i) { - - snd_ctl_elem_list_get_id(uc_mgr->list, i, id); - ret = add_control(uc_mgr->handle, id, control++); - if (ret < 0) { - uc_error("error: failed to add control error %s\n", - __func__, snd_strerror(ret)); - break; - } - } - - return ret; -} - -static int import_use_case_files(snd_use_case_mgr_t *uc_mgr) -{ - int ret; - - /* import the master file and default kcontrol settings */ - ret = import_master_config(uc_mgr); - if (ret < 0) { - uc_error("error: failed to parse master use case config %s\n", - uc_mgr->card_name); - return ret; - } - - return 0; -} - - -static void free_sequence(struct sequence_element *sequence) -{ - struct sequence_element *element = sequence; - - while (element) { - struct sequence_element *pre_element; - - if (element->control) { - free(element->control->value); - free(element->control); - } - pre_element = element; - element = element->next; - free(pre_element); - } -} - -static void free_transition_sequence_element(struct transition_sequence *trans) -{ - free(trans->name); - free_sequence(trans->transition); - free(trans); -} - -static void free_transition_sequence(struct transition_sequence *transition_list) -{ - struct transition_sequence *last, *trans_sequence = transition_list; - - while (trans_sequence) { - last = trans_sequence; - trans_sequence = trans_sequence->next; - free_transition_sequence_element(last); - } -} - -static void free_modifier(struct use_case_modifier *modifier) -{ - if (!modifier) - return; - - free(modifier->name); - free(modifier->comment); - free(modifier->playback_volume_id); - free(modifier->playback_switch_id); - free(modifier->capture_volume_id); - free(modifier->capture_switch_id); - - free_sequence(modifier->enable); - free_sequence(modifier->disable); - free_transition_sequence(modifier->transition_list); -} - -static void free_device(struct use_case_device *device) -{ - if (!device) - return; - - free(device->name); - free(device->comment); - free(device->playback_volume_id); - free(device->playback_switch_id); - free(device->capture_volume_id); - free(device->capture_switch_id); - - free_sequence(device->enable); - free_sequence(device->disable); - free_transition_sequence(device->transition_list); -} - -static void free_devices(struct use_case_device *devices, int num_devices) -{ - int i; - - if (!devices) - return; - - for (i = 0; i< num_devices; i++) - free_device(devices + i); - - free(devices); -} - -static void free_modifiers(struct use_case_modifier *modifiers, - int num_modifiers) -{ - int i; - - if (!modifiers) - return; - - for (i = 0; i < num_modifiers; i++) - free_modifier(modifiers + i); - - free(modifiers); -} - -static void free_verb(struct use_case_verb *verb) -{ - if (!verb) - return; - - free(verb->name); - free(verb->comment); - - free_sequence(verb->enable); - free_sequence(verb->disable); - free_transition_sequence(verb->transition_list); - - free_devices(verb->device, verb->num_devices); - free_modifiers(verb->modifier, verb->num_modifiers); -} - -static void free_verbs(struct use_case_verb *verbs, int num_verbs) -{ - int i; - - if (!verbs) - return; - - for (i = 0; i < num_verbs; i++) - free_verb(verbs + i); - - free(verbs); -} - -static void free_controls(struct control_settings *controls, int num_controls) -{ - int i = 0; - - if (!controls) - return; - - for(i = 0; i < num_controls; i++){ - struct control_settings *control = controls + i; - free(control->value); - } - free(controls); -} - -/* - * Free all use case manager resources. - * Callers holds locks. - */ -static void free_uc_mgr(snd_use_case_mgr_t *uc_mgr) -{ - if (uc_mgr == NULL) - return; - - if (uc_mgr->info) - snd_ctl_card_info_free(uc_mgr->info); - if (uc_mgr->list) - snd_ctl_elem_list_free(uc_mgr->list); - if (uc_mgr->id) - snd_ctl_elem_id_free(uc_mgr->id); - - if (uc_mgr->handle) - snd_ctl_close(uc_mgr->handle); - - free(uc_mgr->card_name); - free(uc_mgr->ctl_name); - - free_verbs(uc_mgr->verb, uc_mgr->num_verbs); - - free_controls(uc_mgr->control, uc_mgr->count); - - pthread_mutex_destroy(&uc_mgr->mutex); - - free(uc_mgr); -} - - /** - * \brief Init sound card use case manager. - * \param uc_mgr Use case manager - * \return zero on success, otherwise a negative error code - */ -snd_use_case_mgr_t *snd_use_case_mgr_open(const char *card_name) -{ - snd_use_case_mgr_t *uc_mgr; - char ctl_name[8]; - int err, idx; - - idx = snd_card_get_index(card_name); - if (idx < 0) { - uc_error("error: can't get sound card %s: %s", - card_name, snd_strerror(idx)); - return NULL; - } - - /* create a new UCM */ - uc_mgr = calloc(1, sizeof(snd_use_case_mgr_t)); - if (uc_mgr == NULL) - return NULL; - - uc_mgr->card_name = strdup(card_name); - if (uc_mgr->card_name == NULL) { - free(uc_mgr); - return NULL; - } - sprintf(ctl_name, "hw:%d", idx); - uc_mgr->ctl_name = strdup(ctl_name); - if (uc_mgr->ctl_name == NULL) { - free_uc_mgr(uc_mgr); - return NULL; - } - if (snd_ctl_card_info_malloc(&uc_mgr->info) < 0) { - free_uc_mgr(uc_mgr); - return NULL; - } - if (snd_ctl_elem_list_malloc(&uc_mgr->list) < 0) { - free_uc_mgr(uc_mgr); - return NULL; - } - if (snd_ctl_elem_id_malloc(&uc_mgr->id) < 0) { - free_uc_mgr(uc_mgr); - return NULL; - } - - /* open and init CTLs */ - err = snd_ctl_open(&uc_mgr->handle, uc_mgr->ctl_name, 0); - if (err) { - uc_error("error: can't open sound card %s: %s", - uc_mgr->card_name, snd_strerror(err)); - goto err; - } - - err = snd_ctl_card_info(uc_mgr->handle, uc_mgr->info); - if (err < 0) { - uc_error("error: can't get sound card %s control info: %s", - uc_mgr->card_name, snd_strerror(err)); - goto err; - } - - err = snd_ctl_elem_list(uc_mgr->handle, uc_mgr->list); - if (err < 0) { - uc_error("error: can't get sound card %s elements: %s", - uc_mgr->card_name, snd_strerror(err)); - goto err; - } - - uc_mgr->count = snd_ctl_elem_list_get_count(uc_mgr->list); - if (uc_mgr->count < 0) { - uc_error("error: can't get sound card %s controls %s:", - uc_mgr->card_name, snd_strerror(err)); - goto err; - } - - snd_ctl_elem_list_set_offset(uc_mgr->list, 0); - if (snd_ctl_elem_list_alloc_space(uc_mgr->list, uc_mgr->count) < 0) { - uc_error("error: could not allocate elements for %s", - uc_mgr->card_name); - goto err; - } - - err = snd_ctl_elem_list(uc_mgr->handle, uc_mgr->list); - if (err < 0) { - uc_error("error: could not get elements for %s : %s", - uc_mgr->card_name, snd_strerror(err)); - goto err; - } - - /* get info about sound card */ - err = parse_card_controls(uc_mgr); - if (err < 0) { - uc_error("error: failed to parse sound device %s controls %d", - card_name, err); - goto err; - } - - /* get info on use_cases and verify against card */ - err = import_use_case_files(uc_mgr); - if (err < 0) { - uc_error("error: failed to import %s use case configuration %d", - card_name, err); - goto err; - } - - pthread_mutex_init(&uc_mgr->mutex, NULL); - uc_mgr->card.current_verb = VERB_NOT_INITIALISED; - - return uc_mgr; - -err: - free_uc_mgr(uc_mgr); - return NULL; -} - - /** - * \brief Reload and reparse all use case files. - * \param uc_mgr Use case manager - * \return zero on success, otherwise a negative error code - */ -int snd_use_case_mgr_reload(snd_use_case_mgr_t *uc_mgr) -{ - int ret; - - pthread_mutex_lock(&uc_mgr->mutex); - - free_verbs(uc_mgr->verb, uc_mgr->num_verbs); - free_controls(uc_mgr->control, uc_mgr->count); - - /* reload all sound card controls */ - ret = parse_card_controls(uc_mgr); - if (ret <= 0) { - uc_error("error: failed to reload sound card controls %d\n", - ret); - free_uc_mgr(uc_mgr); - return -EINVAL; - } - - /* reload all use cases */ - uc_mgr->num_verbs = import_use_case_files(uc_mgr); - if (uc_mgr->num_verbs <= 0) { - uc_error("error: failed to reload use cases\n"); - return -EINVAL; - } - - pthread_mutex_unlock(&uc_mgr->mutex); - return ret; -} - - /** - * \brief Close use case manager. - * \param uc_mgr Use case manager - * \return zero on success, otherwise a negative error code - */ -int snd_use_case_mgr_close(snd_use_case_mgr_t *uc_mgr) -{ - free_uc_mgr(uc_mgr); - - return 0; -} - - /** - * \brief Reset sound card controls to default values. - * \param uc_mgr Use case manager - * \return zero on success, otherwise a negative error code - */ -int snd_use_case_mgr_reset(snd_use_case_mgr_t *uc_mgr) -{ - int ret = 0, i; - struct control_settings *control; - - pthread_mutex_lock(&uc_mgr->mutex); - - for (i = 0; i < uc_mgr->count; i++) { - control = &uc_mgr->control[i]; - - /* Only set default value specified in master config file */ - if (control->value == NULL) - continue; - - ret = set_control_default(uc_mgr, control); - if (ret < 0) - goto out; - } - uc_mgr->card.current_verb = VERB_NOT_INITIALISED; -out: - pthread_mutex_unlock(&uc_mgr->mutex); - return ret; -} - - -/* - * Change a sound card control to a new value. - */ -static int set_control(snd_ctl_t *handle, snd_ctl_elem_id_t *id, - snd_use_case_mgr_t *uc_mgr, unsigned short value[]) -{ - struct control_settings *setting; - int ret, count, i; - unsigned int idnum; - snd_ctl_elem_info_t *info; - snd_ctl_elem_type_t type; - snd_ctl_elem_value_t *control; - - snd_ctl_elem_info_alloca(&info); - snd_ctl_elem_value_alloca(&control); - - snd_ctl_elem_info_set_id(info, id); - ret = snd_ctl_elem_info(handle, info); - if (ret < 0) { - uc_error("error: failed to get ctl elem info %d: ", - snd_strerror(ret)); - return ret; - } - - snd_ctl_elem_value_set_id(control, id); - snd_ctl_elem_read(handle, control); - - idnum = snd_ctl_elem_id_get_numid(id); - for (i = 0; i < uc_mgr->count; i++) { - setting = &uc_mgr->control[i]; - if (setting->id == idnum) - goto set_val; - } - uc_error("error: failed to find control at id %d", idnum); - return 0; - -set_val: - uc_dbg("set control %s id %d count %d", setting->name, setting->id, - setting->count); - - type = snd_ctl_elem_info_get_type(info); - count = snd_ctl_elem_info_get_count(info); - if (count == 0) - return 0; - - uc_dbg("type %d count %d", type, count); - - switch (type) { - case SND_CTL_ELEM_TYPE_BOOLEAN: - for (i = 0; i < count; i++) { - uc_dbg("count %d value %u", i, *(value + i)); - snd_ctl_elem_value_set_boolean(control, i, value[i]); - } - break; - case SND_CTL_ELEM_TYPE_INTEGER: - uc_dbg("int"); - for (i = 0; i < count; i++) { - uc_dbg("count %d value %u", i, value[i]); - snd_ctl_elem_value_set_integer(control, i, value[i]); - } - break; - case SND_CTL_ELEM_TYPE_INTEGER64: - uc_dbg("int64"); - for (i = 0; i < count; i++) { - uc_dbg("count %d value %u", i, value[i]); - snd_ctl_elem_value_set_integer64(control, i, value[i]); - } - - break; - case SND_CTL_ELEM_TYPE_ENUMERATED: - uc_dbg("enumerated"); - for (i = 0; i < count; i++) { - uc_dbg("count %d value %u", i, value[i]); - snd_ctl_elem_value_set_enumerated(control, i, value[i]); - } - break; - case SND_CTL_ELEM_TYPE_BYTES: - uc_dbg("bytes"); - for (i = 0; i < count; i++) { - uc_dbg("count %d value %u", i, value[i]); - snd_ctl_elem_value_set_byte(control, i, value[i]); - } - break; - default: - break; - } - - ret = snd_ctl_elem_write(handle, control); - if (ret < 0) { - uc_error("error: failed to set control %s: %s", - setting->name, snd_strerror(ret)); - uc_error("error: count %d type: %d", - count, type); - for (i = 0; i < count; i++) - fprintf(stderr, "%d ", get_value(setting, i)); - return ret; - } - return 0; -} - -/* - * Execute a sequence of control writes. - */ -static int exec_sequence(struct sequence_element *seq, snd_use_case_mgr_t - *uc_mgr, snd_ctl_elem_list_t *list, snd_ctl_t *handle) -{ - int count = snd_ctl_elem_list_get_count(list); - int ret, i; - - uc_dbg(""); - - /* keep going until end of sequence */ - while (seq) { - /* do we need to sleep */ - if (seq->sleep) { - uc_dbg("msleep %d", seq->sleep); - usleep(seq->sleep); - } else { - uc_dbg("control name %s, id %d, count %d, vale[1] %u", - seq->control->name, seq->control->id, - seq->control->count, seq->control->value[0]); - - /* control write */ - snd_ctl_elem_id_t *id; - snd_ctl_elem_id_alloca(&id); - unsigned int numid; - - /* Where is id lookup from numid if you need it? */ - for (i = 0; i < count; ++i) { - - snd_ctl_elem_list_get_id(list, i, id); - numid = snd_ctl_elem_id_get_numid(id); - - if (numid == seq->control->id) { - ret = set_control(handle, id, uc_mgr, seq->control->value); - if (ret < 0) { - uc_error("error: failed to set control %s", - __func__, uc_mgr->card_name); - return ret; - } - break; - } - } - } - seq = seq->next; - } - return 0; -} - -static int enable_use_case_verb(snd_use_case_mgr_t *uc_mgr, int verb_id, - snd_ctl_elem_list_t *list, snd_ctl_t *handle) -{ - struct use_case_verb *verb; - int ret; - - if (verb_id >= uc_mgr->num_verbs) { - uc_error("error: invalid verb id %d", verb_id); - return -EINVAL; - } - verb = &uc_mgr->verb[verb_id]; - - uc_dbg("verb %s", verb->name); - ret = exec_sequence(verb->enable, uc_mgr, list, handle); - if (ret < 0) { - uc_error("error: could not enable verb %s", verb->name); - return ret; - } - uc_mgr->card.current_verb = verb_id; - - return 0; -} - -static int disable_use_case_verb(snd_use_case_mgr_t *uc_mgr, int verb_id, - snd_ctl_elem_list_t *list, snd_ctl_t *handle) -{ - struct use_case_verb *verb; - int ret; - - if (verb_id >= uc_mgr->num_verbs) { - uc_error("error: invalid verb id %d", verb_id); - return -EINVAL; - } - verb = &uc_mgr->verb[verb_id]; - - /* we set the invalid verb at open() but we should still - * check that this succeeded */ - if (verb == NULL) - return 0; - - uc_dbg("verb %s", verb->name); - ret = exec_sequence(verb->disable, uc_mgr, list, handle); - if (ret < 0) { - uc_error("error: could not disable verb %s", verb->name); - return ret; - } - - return 0; -} - -static int enable_use_case_device(snd_use_case_mgr_t *uc_mgr, - int device_id, snd_ctl_elem_list_t *list, snd_ctl_t *handle) -{ - struct use_case_verb *verb = &uc_mgr->verb[uc_mgr->card.current_verb]; - struct use_case_device *device = &verb->device[device_id]; - int ret; - - if (uc_mgr->card.current_verb == VERB_NOT_INITIALISED) - return -EINVAL; - - uc_dbg("device %s", device->name); - ret = exec_sequence(device->enable, uc_mgr, list, handle); - if (ret < 0) { - uc_error("error: could not enable device %s", device->name); - return ret; - } - - set_device_status(uc_mgr, device_id, 1); - return 0; -} - -static int disable_use_case_device(snd_use_case_mgr_t *uc_mgr, - int device_id, snd_ctl_elem_list_t *list, snd_ctl_t *handle) -{ - struct use_case_verb *verb = &uc_mgr->verb[uc_mgr->card.current_verb]; - struct use_case_device *device = &verb->device[device_id]; - int ret; - - if (uc_mgr->card.current_verb == VERB_NOT_INITIALISED) - return -EINVAL; - - uc_dbg("device %s", device->name); - ret = exec_sequence(device->disable, uc_mgr, list, handle); - if (ret < 0) { - uc_error("error: could not disable device %s", device->name); - return ret; - } - - set_device_status(uc_mgr, device_id, 0); - return 0; -} - -static int enable_use_case_modifier(snd_use_case_mgr_t *uc_mgr, - int modifier_id, snd_ctl_elem_list_t *list, snd_ctl_t *handle) -{ - struct use_case_verb *verb = &uc_mgr->verb[uc_mgr->card.current_verb]; - struct use_case_modifier *modifier = &verb->modifier[modifier_id]; - int ret; - - if (uc_mgr->card.current_verb == VERB_NOT_INITIALISED) - return -EINVAL; - - uc_dbg("modifier %s", modifier->name); - ret = exec_sequence(modifier->enable, uc_mgr, list, handle); - if (ret < 0) { - uc_error("error: could not enable modifier %s", modifier->name); - return ret; - } - - set_modifier_status(uc_mgr, modifier_id, 1); - return 0; -} - -static int disable_use_case_modifier(snd_use_case_mgr_t *uc_mgr, - int modifier_id, snd_ctl_elem_list_t *list, snd_ctl_t *handle) -{ - struct use_case_verb *verb = &uc_mgr->verb[uc_mgr->card.current_verb]; - struct use_case_modifier *modifier = &verb->modifier[modifier_id]; - int ret; - - if (uc_mgr->card.current_verb == VERB_NOT_INITIALISED) - return -EINVAL; - - uc_dbg("modifier %s", modifier->name); - ret = exec_sequence(modifier->disable, uc_mgr, list, handle); - if (ret < 0) { - uc_error("error: could not disable modifier %s", modifier->name); - return ret; - } - - set_modifier_status(uc_mgr, modifier_id, 0); - return 0; -} - -/* - * Tear down current use case verb, device and modifier. - */ -static int dismantle_use_case(snd_use_case_mgr_t *uc_mgr, - snd_ctl_elem_list_t *list, snd_ctl_t *handle) -{ - struct use_case_verb *verb = &uc_mgr->verb[uc_mgr->card.current_verb]; - int ret, i; - - /* No active verb */ - if (uc_mgr->card.current_verb == VERB_NOT_INITIALISED) - return 0; - - /* disable all modifiers that are active */ - for (i = 0; i < verb->num_modifiers; i++) { - if (get_modifier_status(uc_mgr,i)) { - ret = disable_use_case_modifier(uc_mgr, i, list, handle); - if (ret < 0) - return ret; - } - } - - /* disable all devices that are active */ - for (i = 0; i < verb->num_devices; i++) { - if (get_device_status(uc_mgr,i)) { - ret = disable_use_case_device(uc_mgr, i, list, handle); - if (ret < 0) - return ret; - } - } - - /* disable verb */ - ret = disable_use_case_verb(uc_mgr, uc_mgr->card.current_verb, list, handle); - if (ret < 0) - return ret; - - return 0; -} - - /** - * \brief Dump sound card controls in format required for sequencer. - * \param card_name The name of the sound card to be dumped - * \return zero on success, otherwise a negative error code - */ -int snd_use_case_dump(const char *card_name) -{ - snd_ctl_t *handle; - snd_ctl_card_info_t *info; - snd_ctl_elem_list_t *list; - int ret, i, count, idx; - char ctl_name[8]; - - snd_ctl_card_info_alloca(&info); - snd_ctl_elem_list_alloca(&list); - - idx = snd_card_get_index(card_name); - if (idx < 0) - return idx; - sprintf(ctl_name, "hw:%d", idx); - - /* open and load snd card */ - ret = snd_ctl_open(&handle, ctl_name, SND_CTL_READONLY); - if (ret < 0) { - uc_error("error: could not open controls for %s: %s", - card_name, snd_strerror(ret)); - return ret; - } - - ret = snd_ctl_card_info(handle, info); - if (ret < 0) { - uc_error("error: could not get control info for %s:%s", - card_name, snd_strerror(ret)); - goto close; - } - - ret = snd_ctl_elem_list(handle, list); - if (ret < 0) { - uc_error("error: cannot determine controls for %s: %s", - card_name, snd_strerror(ret)); - goto close; - } - - count = snd_ctl_elem_list_get_count(list); - if (count < 0) { - ret = 0; - goto close; - } - - snd_ctl_elem_list_set_offset(list, 0); - if (snd_ctl_elem_list_alloc_space(list, count) < 0) { - uc_error("error: not enough memory for control elements"); - ret = -ENOMEM; - goto close; - } - if ((ret = snd_ctl_elem_list(handle, list)) < 0) { - uc_error("error: cannot determine controls: %s", - snd_strerror(ret)); - goto free; - } - - /* iterate through each kcontrol and add to use - * case manager control list */ - for (i = 0; i < count; ++i) { - snd_ctl_elem_id_t *id; - snd_ctl_elem_id_alloca(&id); - snd_ctl_elem_list_get_id(list, i, id); - - /* dump to stdout in friendly format */ - ret = dump_control(handle, id); - if (ret < 0) { - uc_error("error: control dump failed: %s", - snd_strerror(ret)); - goto free; - } - } -free: - snd_ctl_elem_list_free_space(list); -close: - snd_ctl_close(handle); - return ret; -} - -/** - * \brief List supported use case verbs for given soundcard - * \param uc_mgr use case manager - * \param verb returned list of supported use case verb id and names - * \return number of use case verbs if success, otherwise a negative error code - */ -int snd_use_case_get_verb_list(snd_use_case_mgr_t *uc_mgr, - const char **verb[]) -{ - int ret; - - pthread_mutex_lock(&uc_mgr->mutex); - - *verb = uc_mgr->verb_list; - ret = uc_mgr->num_verbs; - - pthread_mutex_unlock(&uc_mgr->mutex); - return ret; -} - -/** - * \brief List supported use case devices for given verb - * \param uc_mgr use case manager - * \param verb verb id. - * \param device returned list of supported use case device id and names - * \return number of use case devices if success, otherwise a negative error code - */ -int snd_use_case_get_device_list(snd_use_case_mgr_t *uc_mgr, - const char *verb_name, const char **device[]) -{ - struct use_case_verb *verb = NULL; - int i, ret = -EINVAL; - - pthread_mutex_lock(&uc_mgr->mutex); - - /* find verb name */ - for (i = 0; i < uc_mgr->num_verbs; i++) { - verb = &uc_mgr->verb[i]; - if (!strcmp(uc_mgr->verb[i].name, verb_name)) - goto found; - } - - uc_error("error: use case verb %s not found", verb_name); - goto out; - -found: - *device = verb->device_list; - ret = verb->num_devices; -out: - pthread_mutex_unlock(&uc_mgr->mutex); - return ret; -} - -/** - * \brief List supported use case verb modifiers for given verb - * \param uc_mgr use case manager - * \param verb verb id. - * \param mod returned list of supported use case modifier id and names - * \return number of use case modifiers if success, otherwise a negative error code - */ -int snd_use_case_get_mod_list(snd_use_case_mgr_t *uc_mgr, - const char *verb_name, const char **mod[]) -{ - struct use_case_verb *verb = NULL; - int i, ret = -EINVAL; - - pthread_mutex_lock(&uc_mgr->mutex); - - /* find verb name */ - for (i = 0; i num_verbs; i++) { - verb = &uc_mgr->verb[i]; - if (!strcmp(uc_mgr->verb[i].name, verb_name)) - goto found; - } - - uc_error("error: use case verb %s not found", verb_name); - goto out; - -found: - *mod = verb->modifier_list; - ret = verb->num_modifiers; -out: - pthread_mutex_unlock(&uc_mgr->mutex); - return ret; -} - -static struct sequence_element *get_transition_sequence( - struct transition_sequence *trans_list, const char *name) -{ - struct transition_sequence *trans = trans_list; - - while (trans) { - if (trans->name && !strcmp(trans->name, name)) - return trans->transition; - - trans = trans->next; - } - - return NULL; -} - -static int exec_transition_sequence(snd_use_case_mgr_t *uc_mgr, - struct sequence_element *trans_sequence) -{ - int ret; - - ret = exec_sequence(trans_sequence, uc_mgr, uc_mgr->list, - uc_mgr->handle); - if (ret < 0) - uc_error("error: could not exec transition sequence"); - - return ret; -} - -static int handle_transition_verb(snd_use_case_mgr_t *uc_mgr, - int new_verb_id) -{ - struct use_case_verb *old_verb = &uc_mgr->verb[uc_mgr->card.current_verb]; - struct use_case_verb *new_verb; - static struct sequence_element *trans_sequence; - - if (uc_mgr->card.current_verb == VERB_NOT_INITIALISED) - return -EINVAL; - - if (new_verb_id >= uc_mgr->num_verbs) { - uc_error("error: invalid new_verb id %d", new_verb_id); - return -EINVAL; - } - - new_verb = &uc_mgr->verb[new_verb_id]; - - uc_dbg("new verb %s", new_verb->name); - - trans_sequence = get_transition_sequence(old_verb->transition_list, - new_verb->name); - if (trans_sequence != NULL) { - int ret, i; - - uc_dbg("find transition sequence %s->%s", - old_verb->name, new_verb->name); - - /* disable all modifiers that are active */ - for (i = 0; i < old_verb->num_modifiers; i++) { - if (get_modifier_status(uc_mgr,i)) { - ret = disable_use_case_modifier(uc_mgr, i, - uc_mgr->list, uc_mgr->handle); - if (ret < 0) - return ret; - } - } - - /* disable all devices that are active */ - for (i = 0; i < old_verb->num_devices; i++) { - if (get_device_status(uc_mgr,i)) { - ret = disable_use_case_device(uc_mgr, i, - uc_mgr->list, uc_mgr->handle); - if (ret < 0) - return ret; - } - } - - ret = exec_transition_sequence(uc_mgr, trans_sequence); - if (ret) - return ret; - - uc_mgr->card.current_verb = new_verb_id; - - return 0; - } - - return-EINVAL; -} - -/** - * \brief Set new use case verb for sound card - * \param uc_mgr use case manager - * \param verb verb id - * \return zero if success, otherwise a negative error code - */ -int snd_use_case_set_verb(snd_use_case_mgr_t *uc_mgr, - const char *verb_name) -{ - int i = 0, ret = -EINVAL, inactive = 0; - - pthread_mutex_lock(&uc_mgr->mutex); - - uc_dbg("uc_mgr %p, verb_name %s", uc_mgr, verb_name); - - /* check for "Inactive" */ - if (!strcmp(verb_name, SND_USE_CASE_VERB_INACTIVE)) { - inactive = 1; - goto found; - } - - /* find verb name */ - for (i = 0; i num_verbs; i++) { - if (!strcmp(uc_mgr->verb[i].name, verb_name)) - goto found; - } - - uc_error("error: use case verb %s not found", verb_name); - goto out; -found: - /* use case verb found - check that we actually changing the verb */ - if (i == uc_mgr->card.current_verb) { - uc_dbg("current verb ID %d", i); - ret = 0; - goto out; - } - - if (handle_transition_verb(uc_mgr, i) == 0) - goto out; - - /* - * Dismantle the old use cases by running it's verb, device and modifier - * disable sequences - */ - ret = dismantle_use_case(uc_mgr, uc_mgr->list, uc_mgr->handle); - if (ret < 0) { - uc_error("error: failed to dismantle current use case: %s", - uc_mgr->verb[i].name); - goto out; - } - - /* we don't need to initialise new verb if inactive */ - if (inactive) - goto out; - - /* Initialise the new use case verb */ - ret = enable_use_case_verb(uc_mgr, i, uc_mgr->list, uc_mgr->handle); - if (ret < 0) - uc_error("error: failed to initialise new use case: %s", - verb_name); -out: - pthread_mutex_unlock(&uc_mgr->mutex); - - return ret; -} - -static int config_use_case_device(snd_use_case_mgr_t *uc_mgr, - const char *device_name, int enable) -{ - struct use_case_verb *verb; - int ret, i; - - pthread_mutex_lock(&uc_mgr->mutex); - - if (uc_mgr->card.current_verb == VERB_NOT_INITIALISED) { - uc_error("error: no valid use case verb set\n"); - ret = -EINVAL; - goto out; - } - - verb = &uc_mgr->verb[uc_mgr->card.current_verb]; - - uc_dbg("current verb %s", verb->name); - uc_dbg("uc_mgr %p device_name %s", uc_mgr, device_name); - - /* find device name and index */ - for (i = 0; i num_devices; i++) { - uc_dbg("verb->num_devices %s", verb->device[i].name); - if (!strcmp(verb->device[i].name, device_name)) - goto found; - } - - uc_error("error: use case device %s not found", device_name); - ret = -EINVAL; - goto out; - -found: - if (enable) { - /* Initialise the new use case device */ - ret = enable_use_case_device(uc_mgr, i, uc_mgr->list, - uc_mgr->handle); - if (ret < 0) - goto out; - } else { - /* disable the old device */ - ret = disable_use_case_device(uc_mgr, i, uc_mgr->list, - uc_mgr->handle); - if (ret < 0) - goto out; - } - -out: - pthread_mutex_unlock(&uc_mgr->mutex); - return ret; -} - -/** - * \brief Enable use case device - * \param uc_mgr Use case manager - * \param device the device to be enabled - * \return 0 = successful negative = error - */ -int snd_use_case_enable_device(snd_use_case_mgr_t *uc_mgr, - const char *device) -{ - return config_use_case_device(uc_mgr, device, 1); -} - -/** - * \brief Disable use case device - * \param uc_mgr Use case manager - * \param device the device to be disabled - * \return 0 = successful negative = error - */ -int snd_use_case_disable_device(snd_use_case_mgr_t *uc_mgr, - const char *device) -{ - return config_use_case_device(uc_mgr, device, 0); -} - -static struct use_case_device *get_device(snd_use_case_mgr_t *uc_mgr, - const char *name, int *id) -{ - struct use_case_verb *verb; - int i; - - if (uc_mgr->card.current_verb == VERB_NOT_INITIALISED) - return NULL; - - verb = &uc_mgr->verb[uc_mgr->card.current_verb]; - - uc_dbg("current verb %s", verb->name); - - for (i = 0; i < verb->num_devices; i++) { - uc_dbg("device %s", verb->device[i].name); - - if (!strcmp(verb->device[i].name, name)) { - if (id) - *id = i; - return &verb->device[i]; - } - } - - return NULL; -} - -/** - * \brief Disable old_device and then enable new_device. - * If from_device is not enabled just return. - * Check transition sequence firstly. - * \param uc_mgr Use case manager - * \param old the device to be closed - * \param new the device to be opened - * \return 0 = successful negative = error - */ -int snd_use_case_switch_device(snd_use_case_mgr_t *uc_mgr, - const char *old, const char *new) -{ - static struct sequence_element *trans_sequence; - struct use_case_device *old_device; - struct use_case_device *new_device; - int ret = 0, old_id, new_id; - - uc_dbg("old %s, new %s", old, new); - - pthread_mutex_lock(&uc_mgr->mutex); - - old_device = get_device(uc_mgr, old, &old_id); - if (!old_device) { - uc_error("error: device %s not found", old); - ret = -EINVAL; - goto out; - } - - if (!get_device_status(uc_mgr, old_id)) { - uc_error("error: device %s not enabled", old); - goto out; - } - - new_device = get_device(uc_mgr, new, &new_id); - if (!new_device) { - uc_error("error: device %s not found", new); - ret = -EINVAL; - goto out; - } - - trans_sequence = get_transition_sequence(old_device->transition_list, new); - if (trans_sequence != NULL) { - - uc_dbg("find transition sequece %s->%s", old, new); - - ret = exec_transition_sequence(uc_mgr, trans_sequence); - if (ret) - goto out; - - set_device_status(uc_mgr, old_id, 0); - set_device_status(uc_mgr, new_id, 1); - } else { - /* use lock in config_use_case_device */ - pthread_mutex_unlock(&uc_mgr->mutex); - - config_use_case_device(uc_mgr, old, 0); - config_use_case_device(uc_mgr, new, 1); - - return 0; - } -out: - pthread_mutex_unlock(&uc_mgr->mutex); - return ret; -} - -/* - * Check to make sure that the modifier actually supports any of the - * active devices. - */ -static int is_modifier_valid(snd_use_case_mgr_t *uc_mgr, - struct use_case_verb *verb, struct use_case_modifier *modifier) -{ - struct dev_list *dev_list; - int dev; - - /* check modifier list against each enabled device */ - for (dev = 0; dev < verb->num_devices; dev++) { - if (!get_device_status(uc_mgr, dev)) - continue; - - dev_list = modifier->dev_list; - uc_dbg("checking device %s for %s", verb->device[dev].name, - dev_list->name ? dev_list->name : ""); - - while (dev_list) { - uc_dbg("device supports %s", dev_list->name); - if (!strcmp(dev_list->name, verb->device[dev].name)) - return 1; - dev_list = dev_list->next; - } - } - return 0; -} - -static int config_use_case_mod(snd_use_case_mgr_t *uc_mgr, - const char *modifier_name, int enable) -{ - struct use_case_verb *verb; - int ret, i; - - pthread_mutex_lock(&uc_mgr->mutex); - - if (uc_mgr->card.current_verb == VERB_NOT_INITIALISED) { - ret = -EINVAL; - goto out; - } - - verb = &uc_mgr->verb[uc_mgr->card.current_verb]; - - uc_dbg("current verb %s", verb->name); - uc_dbg("uc_mgr %p modifier_name %s", uc_mgr, modifier_name); - - /* find modifier name */ - for (i = 0; i num_modifiers; i++) { - uc_dbg("verb->num_modifiers %d %s", i, verb->modifier[i].name); - if (!strcmp(verb->modifier[i].name, modifier_name) && - is_modifier_valid(uc_mgr, verb, &verb->modifier[i])) - goto found; - } - - uc_error("error: use case modifier %s not found or invalid", - modifier_name); - ret = -EINVAL; - goto out; - -found: - if (enable) { - /* Initialise the new use case device */ - ret = enable_use_case_modifier(uc_mgr, i, uc_mgr->list, - uc_mgr->handle); - if (ret < 0) - goto out; - } else { - /* disable the old device */ - ret = disable_use_case_modifier(uc_mgr, i, uc_mgr->list, - uc_mgr->handle); - if (ret < 0) - goto out; - } - -out: - pthread_mutex_unlock(&uc_mgr->mutex); - return ret; -} - -/** - * \brief Enable use case modifier - * \param uc_mgr Use case manager - * \param modifier the modifier to be enabled - * \return 0 = successful negative = error - */ -int snd_use_case_enable_modifier(snd_use_case_mgr_t *uc_mgr, - const char *modifier) -{ - return config_use_case_mod(uc_mgr, modifier, 1); -} - -/** - * \brief Disable use case modifier - * \param uc_mgr Use case manager - * \param modifier the modifier to be disabled - * \return 0 = successful negative = error - */ -int snd_use_case_disable_modifier(snd_use_case_mgr_t *uc_mgr, - const char *modifier) -{ - return config_use_case_mod(uc_mgr, modifier, 0); -} - -static struct use_case_modifier *get_modifier(snd_use_case_mgr_t *uc_mgr, - const char *name, int *mod_id) -{ - struct use_case_verb *verb; - int i; - - if (uc_mgr->card.current_verb == VERB_NOT_INITIALISED) - return NULL; - - verb = &uc_mgr->verb[uc_mgr->card.current_verb]; - - uc_dbg("current verb %s", verb->name); - - uc_dbg("uc_mgr %p modifier_name %s", uc_mgr, name); - - for (i = 0; i < verb->num_modifiers; i++) { - uc_dbg("verb->num_devices %s", verb->modifier[i].name); - - if (!strcmp(verb->modifier[i].name, name)) { - if (mod_id) - *mod_id = i; - return &verb->modifier[i]; - } - } - - return NULL; -} - -/** - * \brief Disable old_modifier and then enable new_modifier. - * If old_modifier is not enabled just return. - * Check transition sequence firstly. - * \param uc_mgr Use case manager - * \param old the modifier to be closed - * \param new the modifier to be opened - * \return 0 = successful negative = error - */ -int snd_use_case_switch_modifier(snd_use_case_mgr_t *uc_mgr, - const char *old, const char *new) -{ - struct use_case_modifier *old_modifier; - struct use_case_modifier *new_modifier; - static struct sequence_element *trans_sequence; - int ret = 0, old_id, new_id - - uc_dbg("old %s, new %s", old, new); - - pthread_mutex_lock(&uc_mgr->mutex); - - old_modifier = get_modifier(uc_mgr, old, &old_id); - if (!old_modifier) { - uc_error("error: modifier %s not found", old); - ret = -EINVAL; - goto out; - } - - if (!get_modifier_status(uc_mgr, old_id)) { - uc_error("error: modifier %s not enabled", old); - ret = -EINVAL; - goto out; - } - - new_modifier = get_modifier(uc_mgr, new, &new_id); - if (!new_modifier) { - uc_error("error: modifier %s not found", new); - ret = -EINVAL; - goto out; - } - - trans_sequence = get_transition_sequence( - old_modifier->transition_list, new); - if (trans_sequence != NULL) { - uc_dbg("find transition sequence %s->%s", old, new); - - ret = exec_transition_sequence(uc_mgr, trans_sequence); - if (ret) - goto out; - - set_device_status(uc_mgr, old_id, 0); - set_device_status(uc_mgr, new_id, 1); - } else { - /* use lock in config_use_case_mod*/ - pthread_mutex_unlock(&uc_mgr->mutex); - - config_use_case_mod(uc_mgr, old, 0); - config_use_case_mod(uc_mgr, new, 1); - - return 0; - } -out: - pthread_mutex_unlock(&uc_mgr->mutex); - return ret; -} - -/** - * \brief Get current use case verb from sound card - * \param uc_mgr use case manager - * \return Verb Name if success, otherwise NULL - */ -const char *snd_use_case_get_verb(snd_use_case_mgr_t *uc_mgr) -{ - const char *ret = NULL; - - pthread_mutex_lock(&uc_mgr->mutex); - - if (uc_mgr->card.current_verb != VERB_NOT_INITIALISED) - ret = uc_mgr->verb_list[uc_mgr->card.current_verb]; - - pthread_mutex_unlock(&uc_mgr->mutex); - - return ret; -} - -/** - * \brief Get device status for current use case verb - * \param uc_mgr Use case manager - * \param device_name The device we are interested in. - * \return - 1 = enabled, 0 = disabled, negative = error - */ -int snd_use_case_get_device_status(snd_use_case_mgr_t *uc_mgr, - const char *device_name) -{ - struct use_case_device *device; - int ret = -EINVAL, dev_id; - - pthread_mutex_lock(&uc_mgr->mutex); - - device = get_device(uc_mgr, device_name, &dev_id); - if (device == NULL) { - uc_error("error: use case device %s not found", device_name); - goto out; - } - - ret = get_device_status(uc_mgr, dev_id); -out: - pthread_mutex_unlock(&uc_mgr->mutex); - - return ret; -} - -/** - * \brief Get modifier status for current use case verb - * \param uc_mgr Use case manager - * \param device_name The device we are interested in. - * \return - 1 = enabled, 0 = disabled, negative = error - */ -int snd_use_case_get_modifier_status(snd_use_case_mgr_t *uc_mgr, - const char *modifier_name) -{ - struct use_case_modifier *modifier; - int ret = -EINVAL, mod_id; - - pthread_mutex_lock(&uc_mgr->mutex); - - modifier = get_modifier(uc_mgr, modifier_name, &mod_id); - if (modifier == NULL) { - uc_error("error: use case modifier %s not found", modifier_name); - goto out; - } - - ret = get_modifier_status(uc_mgr, mod_id); -out: - pthread_mutex_unlock(&uc_mgr->mutex); - - return ret; -} - -/** - * \brief Get current use case verb QoS - * \param uc_mgr use case manager - * \return QoS level - */ -enum snd_use_case_qos - snd_use_case_get_verb_qos(snd_use_case_mgr_t *uc_mgr) -{ - struct use_case_verb *verb; - enum snd_use_case_qos ret = SND_USE_CASE_QOS_UNKNOWN; - - pthread_mutex_lock(&uc_mgr->mutex); - - if (uc_mgr->card.current_verb != VERB_NOT_INITIALISED) { - verb = &uc_mgr->verb[uc_mgr->card.current_verb]; - ret = verb->qos; - } - - pthread_mutex_unlock(&uc_mgr->mutex); - - return ret; -} - -/** - * \brief Get current use case modifier QoS - * \param uc_mgr use case manager - * \return QoS level - */ -enum snd_use_case_qos - snd_use_case_get_mod_qos(snd_use_case_mgr_t *uc_mgr, - const char *modifier_name) -{ - struct use_case_modifier *modifier; - enum snd_use_case_qos ret = SND_USE_CASE_QOS_UNKNOWN; - - pthread_mutex_lock(&uc_mgr->mutex); - - modifier = get_modifier(uc_mgr, modifier_name, NULL); - if (modifier != NULL) - ret = modifier->qos; - else - uc_error("error: use case modifier %s not found", modifier_name); - pthread_mutex_unlock(&uc_mgr->mutex); - - return ret; -} - -/** - * \brief Get current use case verb playback PCM - * \param uc_mgr use case manager - * \return PCM number if success, otherwise negative - */ -int snd_use_case_get_verb_playback_pcm(snd_use_case_mgr_t *uc_mgr) -{ - struct use_case_verb *verb; - int ret = -EINVAL; - - pthread_mutex_lock(&uc_mgr->mutex); - - if (uc_mgr->card.current_verb != VERB_NOT_INITIALISED) { - verb = &uc_mgr->verb[uc_mgr->card.current_verb]; - ret = verb->playback_pcm; - } - - pthread_mutex_unlock(&uc_mgr->mutex); - - return ret; -} - -/** - * \brief Get current use case verb playback PCM - * \param uc_mgr use case manager - * \return PCM number if success, otherwise negative - */ -int snd_use_case_get_verb_capture_pcm(snd_use_case_mgr_t *uc_mgr) -{ - struct use_case_verb *verb; - int ret = -EINVAL; - - pthread_mutex_lock(&uc_mgr->mutex); - - if (uc_mgr->card.current_verb != VERB_NOT_INITIALISED) { - verb = &uc_mgr->verb[uc_mgr->card.current_verb]; - ret = verb->capture_pcm; - } - - pthread_mutex_unlock(&uc_mgr->mutex); - - return ret; -} - -/** - * \brief Get current use case modifier playback PCM - * \param uc_mgr use case manager - * \return PCM number if success, otherwise negative - */ -int snd_use_case_get_mod_playback_pcm(snd_use_case_mgr_t *uc_mgr, - const char *modifier_name) -{ - struct use_case_modifier *modifier; - int ret = -EINVAL; - - pthread_mutex_lock(&uc_mgr->mutex); - - modifier = get_modifier(uc_mgr, modifier_name, NULL); - if (modifier == NULL) - uc_error("error: use case modifier %s not found", - modifier_name); - else - ret = modifier->playback_pcm; - - pthread_mutex_unlock(&uc_mgr->mutex); - - return ret; -} - -/** - * \brief Get current use case modifier playback PCM - * \param uc_mgr use case manager - * \return PCM number if success, otherwise negative - */ -int snd_use_case_get_mod_capture_pcm(snd_use_case_mgr_t *uc_mgr, - const char *modifier_name) -{ - struct use_case_modifier *modifier; - int ret = -EINVAL; - - pthread_mutex_lock(&uc_mgr->mutex); - - modifier = get_modifier(uc_mgr, modifier_name, NULL); - if (modifier == NULL) - uc_error("error: use case modifier %s not found", - modifier_name); - else - ret = modifier->capture_pcm; - - pthread_mutex_unlock(&uc_mgr->mutex); - - return ret; -} - -/** - * \brief Get volume/mute control name depending on use case device. - * \param uc_mgr use case manager - * \param type the control type we are looking for - * \param device_name The use case device we are interested in. - * \return control name if success, otherwise NULL - * - * Get the control id for common volume and mute controls that are aliased - * in the named use case device. - */ -const char *snd_use_case_get_device_ctl_elem_name(snd_use_case_mgr_t *uc_mgr, - enum snd_use_case_control_alias type, const char *device_name) -{ - struct use_case_device *device; - const char *kcontrol_name = NULL; - - pthread_mutex_lock(&uc_mgr->mutex); - - device = get_device(uc_mgr, device_name, NULL); - if (!device) { - uc_error("error: device %s not found", device_name); - goto out; - } - - switch (type) { - case SND_USE_CASE_ALIAS_PLAYBACK_VOLUME: - kcontrol_name = device->playback_volume_id; - break; - case SND_USE_CASE_ALIAS_CAPTURE_VOLUME: - kcontrol_name = device->capture_volume_id; - break; - case SND_USE_CASE_ALIAS_PLAYBACK_SWITCH: - kcontrol_name = device->playback_switch_id; - break; - case SND_USE_CASE_ALIAS_CAPTURE_SWITCH: - kcontrol_name = device->capture_switch_id; - break; - default: - uc_error("error: invalid control alias %d", type); - break; - } - -out: - pthread_mutex_unlock(&uc_mgr->mutex); - - return kcontrol_name; -} - -/** - * \brief Get volume/mute control IDs depending on use case modifier. - * \param uc_mgr use case manager - * \param type the control type we are looking for - * \param modifier_name The use case modifier we are interested in. - * \return ID if success, otherwise a negative error code - * - * Get the control id for common volume and mute controls that are aliased - * in the named use case device. - */ -const char *snd_use_case_get_modifier_ctl_elem_name(snd_use_case_mgr_t *uc_mgr, - enum snd_use_case_control_alias type, const char *modifier_name) -{ - struct use_case_modifier *modifier; - const char *kcontrol_name = NULL; - - pthread_mutex_lock(&uc_mgr->mutex); - - modifier = get_modifier(uc_mgr, modifier_name, NULL); - if (!modifier) { - uc_error("error: modifier %s not found", modifier_name); - goto out; - } - - switch (type) { - case SND_USE_CASE_ALIAS_PLAYBACK_VOLUME: - kcontrol_name = modifier->playback_volume_id; - break; - case SND_USE_CASE_ALIAS_CAPTURE_VOLUME: - kcontrol_name = modifier->capture_volume_id; - break; - case SND_USE_CASE_ALIAS_PLAYBACK_SWITCH: - kcontrol_name = modifier->playback_switch_id; - break; - case SND_USE_CASE_ALIAS_CAPTURE_SWITCH: - kcontrol_name = modifier->capture_switch_id; - break; - default: - uc_error("error: invalid control alias %d", type); - break; - } - -out: - pthread_mutex_unlock(&uc_mgr->mutex); - - return kcontrol_name; -} From 404cd090b279b329c86514984dc74439dedf2e90 Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Wed, 22 Sep 2010 14:31:15 +0200 Subject: [PATCH 06/30] ucm: Introduce "Value {}" section, more implementation work - new "Value {}" section is introduced for read-only values describing the PCM and control/mixer IDs (or any other things) - more complete implementation for API functions Signed-off-by: Jaroslav Kysela --- include/use-case.h | 45 +- src/ucm/main.c | 1829 ++++++++++++++++--------------------------- src/ucm/parser.c | 242 +++--- src/ucm/ucm_local.h | 40 +- src/ucm/utils.c | 34 +- 5 files changed, 855 insertions(+), 1335 deletions(-) diff --git a/include/use-case.h b/include/use-case.h index 6b9b6455..fdbcaca0 100644 --- a/include/use-case.h +++ b/include/use-case.h @@ -179,9 +179,10 @@ char *snd_use_case_identifier(const char *fmt, ...); /** * \brief Free a string list * \param list The string list to free + * \param items Count of strings * \return Zero if success, otherwise a negative error code */ -int snd_use_case_free_list(const char *list[]); +int snd_use_case_free_list(const char *list[], int items); /** * \brief Obtain a list of entries @@ -192,14 +193,14 @@ int snd_use_case_free_list(const char *list[]); * * Defined identifiers: * NULL - get card list - * (in pair verb+comment) + * (in pair cardname+comment) * _verbs - get verb list * (in pair verb+comment) * _devices[/] - get list of supported devices * (in pair device+comment) * _modifiers[/]- get list of supported modifiers * (in pair modifier+comment) - * _tqs[/] - get list of QoS identifiers + * TQ[/] - get list of TQ identifiers * _enadevs - get list of enabled devices * _enamods - get list of enabled modifiers * @@ -222,20 +223,19 @@ int snd_use_case_get_list(snd_use_case_mgr_t *uc_mgr, * Known identifiers: * NULL - return current card * _verb - return current verb - * _tq - return current Tone Quality - * _tq/ - return Tone Quality for given modifier - * _pcm_/_pdevice[/] - full PCM playback device name - * _pcm_/_cdevice[/] - full PCM capture device name - * _ctl_/_pctl_[/] - playback control device name - * _ctl_/_pctlvol[/] - playback control volume ID string - * _ctl_/_pctlsw[/] - playback control switch ID string - * _ctl_/_cctl[/] - capture control device name - * _ctl_/_cctlvol[/] - capture control volume ID string - * _ctl_/_cctlsw[/] - capture control switch ID string - * _mixer_/_pname[/] - name of playback mixer - * _mixer_/_pid[/] - mixer playback ID - * _mixer_/_cname[/] - name of capture mixer - * _mixer_/_cid[/] - mixer capture ID + * TQ[/] - Tone Quality [for given modifier] + * PlaybackPCM[/] - full PCM playback device name + * CapturePCM[/] - full PCM capture device name + * PlaybackCTL[/] - playback control device name + * PlaybackVolume[/] - playback control volume ID string + * PlaybackSwitch[/] - playback control switch ID string + * CaptureCTL[/] - capture control device name + * CaptureVolume[/] - capture control volume ID string + * CaptureSwitch[/] - capture control switch ID string + * PlaybackMixer[/] - name of playback mixer + * PlaybackMixerID[/] - mixer playback ID + * CaptureMixer[/] - name of capture mixer + * CaptureMixerID[/] - mixer capture ID */ int snd_use_case_get(snd_use_case_mgr_t *uc_mgr, const char *identifier, @@ -251,8 +251,8 @@ int snd_use_case_get(snd_use_case_mgr_t *uc_mgr, * _devstatus/ - return status for given device * _modstatus/ - return status for given modifier */ -int snd_use_case_geti(snd_use_case_mgr_t *uc_mgr, - const char *identifier); +long snd_use_case_geti(snd_use_case_mgr_t *uc_mgr, + const char *identifier); /** * \brief Set new @@ -310,13 +310,6 @@ int snd_use_case_mgr_close(snd_use_case_mgr_t *uc_mgr); */ int snd_use_case_mgr_reset(snd_use_case_mgr_t *uc_mgr); -/** - * \brief Dump current sound card use case control settings - * \param card_name Sound card name - * \return zero if success, otherwise a negative error code - */ -int snd_use_case_dump(const char *card_name); - /** * \} */ diff --git a/src/ucm/main.c b/src/ucm/main.c index a8df44c2..e036c4dd 100644 --- a/src/ucm/main.c +++ b/src/ucm/main.c @@ -31,8 +31,93 @@ */ #include "ucm_local.h" +#include #include +/* + * misc + */ + +static int check_identifier(const char *identifier, const char *prefix) +{ + int len; + + if (strcmp(identifier, prefix) == 0) + return 1; + len = strlen(prefix); + if (memcmp(identifier, prefix, len) == 0 && identifier[len] == '/') + return 1; + return 0; +} + +static int list_count(struct list_head *list) +{ + struct list_head *pos; + int count = 0; + + list_for_each(pos, list) { + count += 1; + } + return count; +} + +static int alloc_str_list(struct list_head *list, int mult, char **result[]) +{ + char **res; + int cnt; + + cnt = list_count(list) * mult; + if (cnt == 0) + return cnt; + res = calloc(mult, cnt * sizeof(char *)); + if (res == NULL) + return -ENOMEM; + *result = res; + return cnt; +} + +/** + * \brief Create an identifier + * \param fmt Format (sprintf like) + * \param ... Optional arguments for sprintf like format + * \return Allocated string identifier or NULL on error + */ +char *snd_use_case_identifier(const char *fmt, ...) +{ + char *str, *res; + int size = strlen(fmt) + 512; + va_list args; + + str = malloc(size); + if (str == NULL) + return NULL; + va_start(args, fmt); + vsnprintf(str, size, fmt, args); + va_end(args); + str[size-1] = '\0'; + res = realloc(str, strlen(str) + 1); + if (res) + return res; + return str; +} + +/** + * \brief Free a string list + * \param list The string list to free + * \param items Count of strings + * \return Zero if success, otherwise a negative error code + */ +int snd_use_case_free_list(const char *list[], int items) +{ + int i; + if (list == NULL) + return 0; + for (i = 0; i < items; i++) + free((void *)list[i]); + free(list); + return 0; +} + /** * \brief Execute the sequence * \param uc_mgr Use case manager @@ -85,15 +170,13 @@ static int import_master_config(snd_use_case_mgr_t *uc_mgr) /** * \brief Universal find - string in a list - * \param uc_mgr Use case manager * \param list List of structures * \param offset Offset of list structure * \param soffset Offset of string structure * \param match String to match * \return structure on success, otherwise a NULL (not found) */ -static void *find0(snd_use_case_mgr_t *uc_mgr ATTRIBUTE_UNUSED, - struct list_head *list, +static void *find0(struct list_head *list, unsigned long offset, unsigned long soffset, const char *match) @@ -110,10 +193,111 @@ static void *find0(snd_use_case_mgr_t *uc_mgr ATTRIBUTE_UNUSED, return NULL; } -#define find(uc_mgr, list, type, member, value, match) \ - find0(uc_mgr, list, \ - (unsigned long)(&((type *)0)->member), \ - (unsigned long)(&((type *)0)->value), match) +#define find(list, type, member, value, match) \ + find0(list, (unsigned long)(&((type *)0)->member), \ + (unsigned long)(&((type *)0)->value), match) + +/** + * \brief Universal string list + * \param list List of structures + * \param result Result list + * \param offset Offset of list structure + * \param s1offset Offset of string structure + * \return count of items on success, otherwise a negative error code + */ +static int get_list0(struct list_head *list, + const char **result[], + unsigned long offset, + unsigned long s1offset) +{ + char **res; + int cnt; + struct list_head *pos; + char *ptr, *str1; + + cnt = alloc_str_list(list, 1, &res); + if (cnt <= 0) + return cnt; + *result = (const char **)res; + list_for_each(pos, list) { + ptr = list_entry_offset(pos, char, offset); + str1 = *((char **)(ptr + s1offset)); + if (str1 != NULL) { + *res = strdup(str1); + if (*res == NULL) + goto __fail; + } else { + *res = NULL; + } + res++; + } + return cnt; + __fail: + snd_use_case_free_list((const char **)res, cnt); + return -ENOMEM; +} + +#define get_list(list, result, type, member, s1) \ + get_list0(list, result, \ + (unsigned long)(&((type *)0)->member), \ + (unsigned long)(&((type *)0)->s1)) + +/** + * \brief Universal string list - pair of strings + * \param list List of structures + * \param result Result list + * \param offset Offset of list structure + * \param s1offset Offset of string structure + * \param s1offset Offset of string structure + * \return count of items on success, otherwise a negative error code + */ +static int get_list20(struct list_head *list, + const char **result[], + unsigned long offset, + unsigned long s1offset, + unsigned long s2offset) +{ + char **res; + int cnt; + struct list_head *pos; + char *ptr, *str1, *str2; + + cnt = alloc_str_list(list, 2, &res); + if (cnt <= 0) + return cnt; + *result = (const char **)res; + list_for_each(pos, list) { + ptr = list_entry_offset(pos, char, offset); + str1 = *((char **)(ptr + s1offset)); + if (str1 != NULL) { + *res = strdup(str1); + if (*res == NULL) + goto __fail; + } else { + *res = NULL; + } + res++; + str2 = *((char **)(ptr + s2offset)); + if (str2 != NULL) { + *res = strdup(str2); + if (*res == NULL) + goto __fail; + } else { + *res = NULL; + } + res++; + } + return cnt; + __fail: + snd_use_case_free_list((const char **)res, cnt); + return -ENOMEM; +} + +#define get_list2(list, result, type, member, s1, s2) \ + get_list20(list, result, \ + (unsigned long)(&((type *)0)->member), \ + (unsigned long)(&((type *)0)->s1), \ + (unsigned long)(&((type *)0)->s2)) /** * \brief Find verb @@ -122,17 +306,33 @@ static void *find0(snd_use_case_mgr_t *uc_mgr ATTRIBUTE_UNUSED, * \return structure on success, otherwise a NULL (not found) */ static inline struct use_case_verb *find_verb(snd_use_case_mgr_t *uc_mgr, - const char *_name) + const char *verb_name) { - return find(uc_mgr, &uc_mgr->verb_list, + return find(&uc_mgr->verb_list, struct use_case_verb, list, name, - _name); + verb_name); +} + +/** + * \brief Find modifier + * \param verb Use case verb + * \param modifier_name modifier to find + * \return structure on success, otherwise a NULL (not found) + */ +static inline struct use_case_modifier * + find_modifier(struct use_case_verb *verb, + const char *modifier_name) +{ + return find(&verb->modifier_list, + struct use_case_modifier, list, name, + modifier_name); } /** * \brief Set verb * \param uc_mgr Use case manager * \param verb verb to set + * \param enable nonzero = enable, zero = disable * \return zero on success, otherwise a negative error code */ static int set_verb(snd_use_case_mgr_t *uc_mgr, @@ -153,6 +353,62 @@ static int set_verb(snd_use_case_mgr_t *uc_mgr, return err; } +/** + * \brief Set modifier + * \param uc_mgr Use case manager + * \param modifier modifier to set + * \param enable nonzero = enable, zero = disable + * \return zero on success, otherwise a negative error code + */ +static int set_modifier(snd_use_case_mgr_t *uc_mgr, + struct use_case_modifier *modifier, + int enable) +{ + struct list_head *seq; + int err; + + if (enable) { + seq = &modifier->enable_list; + } else { + seq = &modifier->disable_list; + } + err = execute_sequence(uc_mgr, seq); + if (enable && err >= 0) { + list_add_tail(&modifier->active_list, &uc_mgr->active_modifiers); + } else if (!enable) { + list_del(&modifier->active_list); + } + return err; +} + +/** + * \brief Set device + * \param uc_mgr Use case manager + * \param device device to set + * \param enable nonzero = enable, zero = disable + * \return zero on success, otherwise a negative error code + */ +static int set_device(snd_use_case_mgr_t *uc_mgr, + struct use_case_device *device, + int enable) +{ + struct list_head *seq; + int err; + + if (enable) { + seq = &device->enable_list; + } else { + seq = &device->disable_list; + } + err = execute_sequence(uc_mgr, seq); + if (enable && err >= 0) { + list_add_tail(&device->active_list, &uc_mgr->active_devices); + } else if (!enable) { + list_del(&device->active_list); + } + return err; +} + /** * \brief Init sound card use case manager. * \param uc_mgr Returned use case manager pointer @@ -249,7 +505,7 @@ int snd_use_case_mgr_reset(snd_use_case_mgr_t *uc_mgr) list_for_each_safe(pos, npos, &uc_mgr->active_modifiers) { modifier = list_entry(pos, struct use_case_modifier, active_list); - err = disable_modifier(uc_mgr, modifier); + err = set_modifier(uc_mgr, modifier, 0); if (err < 0) uc_error("Unable to disable modifier %s", modifier->name); } @@ -258,13 +514,13 @@ int snd_use_case_mgr_reset(snd_use_case_mgr_t *uc_mgr) list_for_each_safe(pos, npos, &uc_mgr->active_devices) { device = list_entry(pos, struct use_case_device, active_list); - err = disable_device(uc_mgr, device); + err = set_device(uc_mgr, device, 0); if (err < 0) uc_error("Unable to disable device %s", device->name); } INIT_LIST_HEAD(&uc_mgr->active_devices); - err = disable_verb(uc_mgr, uc_mgr->active_verb); + err = set_verb(uc_mgr, uc_mgr->active_verb, 0); if (err < 0) { uc_error("Unable to disable verb %s", uc_mgr->active_verb->name); return err; @@ -277,1172 +533,469 @@ int snd_use_case_mgr_reset(snd_use_case_mgr_t *uc_mgr) return err; } -#if 0 -static int enable_use_case_verb(snd_use_case_mgr_t *uc_mgr, int verb_id, - snd_ctl_elem_list_t *list, snd_ctl_t *handle) -{ - struct use_case_verb *verb; - int ret; - - if (verb_id >= uc_mgr->num_verbs) { - uc_error("error: invalid verb id %d", verb_id); - return -EINVAL; - } - verb = &uc_mgr->verb[verb_id]; - - uc_dbg("verb %s", verb->name); - ret = exec_sequence(verb->enable, uc_mgr, list, handle); - if (ret < 0) { - uc_error("error: could not enable verb %s", verb->name); - return ret; - } - uc_mgr->card.current_verb = verb_id; - - return 0; -} - -static int disable_use_case_verb(snd_use_case_mgr_t *uc_mgr, int verb_id, - snd_ctl_elem_list_t *list, snd_ctl_t *handle) -{ - struct use_case_verb *verb; - int ret; - - if (verb_id >= uc_mgr->num_verbs) { - uc_error("error: invalid verb id %d", verb_id); - return -EINVAL; - } - verb = &uc_mgr->verb[verb_id]; - - /* we set the invalid verb at open() but we should still - * check that this succeeded */ - if (verb == NULL) - return 0; - - uc_dbg("verb %s", verb->name); - ret = exec_sequence(verb->disable, uc_mgr, list, handle); - if (ret < 0) { - uc_error("error: could not disable verb %s", verb->name); - return ret; - } - - return 0; -} - -static int enable_use_case_device(snd_use_case_mgr_t *uc_mgr, - int device_id, snd_ctl_elem_list_t *list, snd_ctl_t *handle) -{ - struct use_case_verb *verb = &uc_mgr->verb[uc_mgr->card.current_verb]; - struct use_case_device *device = &verb->device[device_id]; - int ret; - - if (uc_mgr->card.current_verb == VERB_NOT_INITIALISED) - return -EINVAL; - - uc_dbg("device %s", device->name); - ret = exec_sequence(device->enable, uc_mgr, list, handle); - if (ret < 0) { - uc_error("error: could not enable device %s", device->name); - return ret; - } - - set_device_status(uc_mgr, device_id, 1); - return 0; -} - -static int disable_use_case_device(snd_use_case_mgr_t *uc_mgr, - int device_id, snd_ctl_elem_list_t *list, snd_ctl_t *handle) -{ - struct use_case_verb *verb = &uc_mgr->verb[uc_mgr->card.current_verb]; - struct use_case_device *device = &verb->device[device_id]; - int ret; - - if (uc_mgr->card.current_verb == VERB_NOT_INITIALISED) - return -EINVAL; - - uc_dbg("device %s", device->name); - ret = exec_sequence(device->disable, uc_mgr, list, handle); - if (ret < 0) { - uc_error("error: could not disable device %s", device->name); - return ret; - } - - set_device_status(uc_mgr, device_id, 0); - return 0; -} - -static int enable_use_case_modifier(snd_use_case_mgr_t *uc_mgr, - int modifier_id, snd_ctl_elem_list_t *list, snd_ctl_t *handle) -{ - struct use_case_verb *verb = &uc_mgr->verb[uc_mgr->card.current_verb]; - struct use_case_modifier *modifier = &verb->modifier[modifier_id]; - int ret; - - if (uc_mgr->card.current_verb == VERB_NOT_INITIALISED) - return -EINVAL; - - uc_dbg("modifier %s", modifier->name); - ret = exec_sequence(modifier->enable, uc_mgr, list, handle); - if (ret < 0) { - uc_error("error: could not enable modifier %s", modifier->name); - return ret; - } - - set_modifier_status(uc_mgr, modifier_id, 1); - return 0; -} - -static int disable_use_case_modifier(snd_use_case_mgr_t *uc_mgr, - int modifier_id, snd_ctl_elem_list_t *list, snd_ctl_t *handle) -{ - struct use_case_verb *verb = &uc_mgr->verb[uc_mgr->card.current_verb]; - struct use_case_modifier *modifier = &verb->modifier[modifier_id]; - int ret; - - if (uc_mgr->card.current_verb == VERB_NOT_INITIALISED) - return -EINVAL; - - uc_dbg("modifier %s", modifier->name); - ret = exec_sequence(modifier->disable, uc_mgr, list, handle); - if (ret < 0) { - uc_error("error: could not disable modifier %s", modifier->name); - return ret; - } - - set_modifier_status(uc_mgr, modifier_id, 0); - return 0; -} - -/* - * Tear down current use case verb, device and modifier. +/** + * \brief Get list of cards in pair cardname+comment + * \param list Returned list + * \return Number of list entries if success, otherwise a negative error code */ -static int dismantle_use_case(snd_use_case_mgr_t *uc_mgr, - snd_ctl_elem_list_t *list, snd_ctl_t *handle) +static int get_card_list(const char **list[]) { - struct use_case_verb *verb = &uc_mgr->verb[uc_mgr->card.current_verb]; - int ret, i; - - /* No active verb */ - if (uc_mgr->card.current_verb == VERB_NOT_INITIALISED) - return 0; - - /* disable all modifiers that are active */ - for (i = 0; i < verb->num_modifiers; i++) { - if (get_modifier_status(uc_mgr,i)) { - ret = disable_use_case_modifier(uc_mgr, i, list, handle); - if (ret < 0) - return ret; - } - } - - /* disable all devices that are active */ - for (i = 0; i < verb->num_devices; i++) { - if (get_device_status(uc_mgr,i)) { - ret = disable_use_case_device(uc_mgr, i, list, handle); - if (ret < 0) - return ret; - } - } - - /* disable verb */ - ret = disable_use_case_verb(uc_mgr, uc_mgr->card.current_verb, list, handle); - if (ret < 0) - return ret; - - return 0; -} - - /** - * \brief Dump sound card controls in format required for sequencer. - * \param card_name The name of the sound card to be dumped - * \return zero on success, otherwise a negative error code - */ -int snd_use_case_dump(const char *card_name) -{ - snd_ctl_t *handle; - snd_ctl_card_info_t *info; - snd_ctl_elem_list_t *list; - int ret, i, count, idx; - char ctl_name[8]; - - snd_ctl_card_info_alloca(&info); - snd_ctl_elem_list_alloca(&list); - - idx = snd_card_get_index(card_name); - if (idx < 0) - return idx; - sprintf(ctl_name, "hw:%d", idx); - - /* open and load snd card */ - ret = snd_ctl_open(&handle, ctl_name, SND_CTL_READONLY); - if (ret < 0) { - uc_error("error: could not open controls for %s: %s", - card_name, snd_strerror(ret)); - return ret; - } - - ret = snd_ctl_card_info(handle, info); - if (ret < 0) { - uc_error("error: could not get control info for %s:%s", - card_name, snd_strerror(ret)); - goto close; - } - - ret = snd_ctl_elem_list(handle, list); - if (ret < 0) { - uc_error("error: cannot determine controls for %s: %s", - card_name, snd_strerror(ret)); - goto close; - } - - count = snd_ctl_elem_list_get_count(list); - if (count < 0) { - ret = 0; - goto close; - } - - snd_ctl_elem_list_set_offset(list, 0); - if (snd_ctl_elem_list_alloc_space(list, count) < 0) { - uc_error("error: not enough memory for control elements"); - ret = -ENOMEM; - goto close; - } - if ((ret = snd_ctl_elem_list(handle, list)) < 0) { - uc_error("error: cannot determine controls: %s", - snd_strerror(ret)); - goto free; - } - - /* iterate through each kcontrol and add to use - * case manager control list */ - for (i = 0; i < count; ++i) { - snd_ctl_elem_id_t *id; - snd_ctl_elem_id_alloca(&id); - snd_ctl_elem_list_get_id(list, i, id); - - /* dump to stdout in friendly format */ - ret = dump_control(handle, id); - if (ret < 0) { - uc_error("error: control dump failed: %s", - snd_strerror(ret)); - goto free; - } - } -free: - snd_ctl_elem_list_free_space(list); -close: - snd_ctl_close(handle); - return ret; + return -ENXIO; /* Not Yet Implemented */ } /** - * \brief List supported use case verbs for given soundcard - * \param uc_mgr use case manager - * \param verb returned list of supported use case verb id and names - * \return number of use case verbs if success, otherwise a negative error code + * \brief Get list of verbs in pair verbname+comment + * \param list Returned list + * \param verbname For verb (NULL = current) + * \return Number of list entries if success, otherwise a negative error code */ -int snd_use_case_get_verb_list(snd_use_case_mgr_t *uc_mgr, - const char **verb[]) +static int get_verb_list(snd_use_case_mgr_t *uc_mgr, const char **list[]) { - int ret; - - pthread_mutex_lock(&uc_mgr->mutex); - - *verb = uc_mgr->verb_list; - ret = uc_mgr->num_verbs; - - pthread_mutex_unlock(&uc_mgr->mutex); - return ret; + return get_list2(&uc_mgr->verb_list, list, + struct use_case_verb, list, + name, comment); } /** - * \brief List supported use case devices for given verb - * \param uc_mgr use case manager - * \param verb verb id. - * \param device returned list of supported use case device id and names - * \return number of use case devices if success, otherwise a negative error code + * \brief Get list of devices in pair devicename+comment + * \param list Returned list + * \param verbname For verb (NULL = current) + * \return Number of list entries if success, otherwise a negative error code */ -int snd_use_case_get_device_list(snd_use_case_mgr_t *uc_mgr, - const char *verb_name, const char **device[]) +static int get_device_list(snd_use_case_mgr_t *uc_mgr, const char **list[], + char *verbname) { - struct use_case_verb *verb = NULL; - int i, ret = -EINVAL; - - pthread_mutex_lock(&uc_mgr->mutex); - - /* find verb name */ - for (i = 0; i < uc_mgr->num_verbs; i++) { - verb = &uc_mgr->verb[i]; - if (!strcmp(uc_mgr->verb[i].name, verb_name)) - goto found; - } - - uc_error("error: use case verb %s not found", verb_name); - goto out; - -found: - *device = verb->device_list; - ret = verb->num_devices; -out: - pthread_mutex_unlock(&uc_mgr->mutex); - return ret; + struct use_case_verb *verb; + + if (verbname) { + verb = find_verb(uc_mgr, verbname); + } else { + verb = uc_mgr->active_verb; + } + if (verb == NULL) + return -ENOENT; + return get_list2(&verb->device_list, list, + struct use_case_device, list, + name, comment); + return 0; } /** - * \brief List supported use case verb modifiers for given verb - * \param uc_mgr use case manager - * \param verb verb id. - * \param mod returned list of supported use case modifier id and names - * \return number of use case modifiers if success, otherwise a negative error code + * \brief Get list of modifiers in pair devicename+comment + * \param list Returned list + * \param verbname For verb (NULL = current) + * \return Number of list entries if success, otherwise a negative error code */ -int snd_use_case_get_mod_list(snd_use_case_mgr_t *uc_mgr, - const char *verb_name, const char **mod[]) +static int get_modifier_list(snd_use_case_mgr_t *uc_mgr, const char **list[], + char *verbname) { - struct use_case_verb *verb = NULL; - int i, ret = -EINVAL; - - pthread_mutex_lock(&uc_mgr->mutex); - - /* find verb name */ - for (i = 0; i num_verbs; i++) { - verb = &uc_mgr->verb[i]; - if (!strcmp(uc_mgr->verb[i].name, verb_name)) - goto found; - } - - uc_error("error: use case verb %s not found", verb_name); - goto out; - -found: - *mod = verb->modifier_list; - ret = verb->num_modifiers; -out: - pthread_mutex_unlock(&uc_mgr->mutex); - return ret; + struct use_case_verb *verb; + + if (verbname) { + verb = find_verb(uc_mgr, verbname); + } else { + verb = uc_mgr->active_verb; + } + if (verb == NULL) + return -ENOENT; + return get_list2(&verb->modifier_list, list, + struct use_case_modifier, list, + name, comment); + return 0; } -static struct sequence_element *get_transition_sequence( - struct transition_sequence *trans_list, const char *name) +struct myvalue { + struct list_head list; + char *value; +}; + +static int add_values(struct list_head *list, + const char *identifier, + struct list_head *source) { - struct transition_sequence *trans = trans_list; - - while (trans) { - if (trans->name && !strcmp(trans->name, name)) - return trans->transition; - - trans = trans->next; - } - - return NULL; -} - -static int exec_transition_sequence(snd_use_case_mgr_t *uc_mgr, - struct sequence_element *trans_sequence) -{ - int ret; - - ret = exec_sequence(trans_sequence, uc_mgr, uc_mgr->list, - uc_mgr->handle); - if (ret < 0) - uc_error("error: could not exec transition sequence"); - - return ret; -} - -static int handle_transition_verb(snd_use_case_mgr_t *uc_mgr, - int new_verb_id) -{ - struct use_case_verb *old_verb = &uc_mgr->verb[uc_mgr->card.current_verb]; - struct use_case_verb *new_verb; - static struct sequence_element *trans_sequence; - - if (uc_mgr->card.current_verb == VERB_NOT_INITIALISED) - return -EINVAL; - - if (new_verb_id >= uc_mgr->num_verbs) { - uc_error("error: invalid new_verb id %d", new_verb_id); - return -EINVAL; - } - - new_verb = &uc_mgr->verb[new_verb_id]; - - uc_dbg("new verb %s", new_verb->name); - - trans_sequence = get_transition_sequence(old_verb->transition_list, - new_verb->name); - if (trans_sequence != NULL) { - int ret, i; - - uc_dbg("find transition sequence %s->%s", - old_verb->name, new_verb->name); - - /* disable all modifiers that are active */ - for (i = 0; i < old_verb->num_modifiers; i++) { - if (get_modifier_status(uc_mgr,i)) { - ret = disable_use_case_modifier(uc_mgr, i, - uc_mgr->list, uc_mgr->handle); - if (ret < 0) - return ret; - } - } - - /* disable all devices that are active */ - for (i = 0; i < old_verb->num_devices; i++) { - if (get_device_status(uc_mgr,i)) { - ret = disable_use_case_device(uc_mgr, i, - uc_mgr->list, uc_mgr->handle); - if (ret < 0) - return ret; - } - } - - ret = exec_transition_sequence(uc_mgr, trans_sequence); - if (ret) - return ret; - - uc_mgr->card.current_verb = new_verb_id; - - return 0; - } - - return-EINVAL; + struct ucm_value *v; + struct myvalue *val; + struct list_head *pos, *pos1; + int match; + + list_for_each(pos, source) { + v = list_entry(pos, struct ucm_value, list); + if (check_identifier(identifier, v->name)) { + match = 0; + list_for_each(pos1, list) { + val = list_entry(pos1, struct myvalue, list); + if (strcmp(val->value, v->data) == 0) { + match = 1; + break; + } + } + if (!match) { + val = malloc(sizeof(struct myvalue)); + if (val == NULL) + return -ENOMEM; + list_add_tail(&val->list, list); + } + } + } + return 0; } /** - * \brief Set new use case verb for sound card - * \param uc_mgr use case manager - * \param verb verb id - * \return zero if success, otherwise a negative error code + * \brief Get list of values + * \param list Returned list + * \param verbname For verb (NULL = current) + * \return Number of list entries if success, otherwise a negative error code */ -int snd_use_case_set_verb(snd_use_case_mgr_t *uc_mgr, - const char *verb_name) +static int get_value_list(snd_use_case_mgr_t *uc_mgr, + const char *identifier, + const char **list[], + char *verbname) { - int i = 0, ret = -EINVAL, inactive = 0; - - pthread_mutex_lock(&uc_mgr->mutex); - - uc_dbg("uc_mgr %p, verb_name %s", uc_mgr, verb_name); - - /* check for "Inactive" */ - if (!strcmp(verb_name, SND_USE_CASE_VERB_INACTIVE)) { - inactive = 1; - goto found; - } - - /* find verb name */ - for (i = 0; i num_verbs; i++) { - if (!strcmp(uc_mgr->verb[i].name, verb_name)) - goto found; - } - - uc_error("error: use case verb %s not found", verb_name); - goto out; -found: - /* use case verb found - check that we actually changing the verb */ - if (i == uc_mgr->card.current_verb) { - uc_dbg("current verb ID %d", i); - ret = 0; - goto out; - } - - if (handle_transition_verb(uc_mgr, i) == 0) - goto out; - - /* - * Dismantle the old use cases by running it's verb, device and modifier - * disable sequences - */ - ret = dismantle_use_case(uc_mgr, uc_mgr->list, uc_mgr->handle); - if (ret < 0) { - uc_error("error: failed to dismantle current use case: %s", - uc_mgr->verb[i].name); - goto out; - } - - /* we don't need to initialise new verb if inactive */ - if (inactive) - goto out; - - /* Initialise the new use case verb */ - ret = enable_use_case_verb(uc_mgr, i, uc_mgr->list, uc_mgr->handle); - if (ret < 0) - uc_error("error: failed to initialise new use case: %s", - verb_name); -out: - pthread_mutex_unlock(&uc_mgr->mutex); - - return ret; -} - -static int config_use_case_device(snd_use_case_mgr_t *uc_mgr, - const char *device_name, int enable) -{ - struct use_case_verb *verb; - int ret, i; - - pthread_mutex_lock(&uc_mgr->mutex); - - if (uc_mgr->card.current_verb == VERB_NOT_INITIALISED) { - uc_error("error: no valid use case verb set\n"); - ret = -EINVAL; - goto out; - } - - verb = &uc_mgr->verb[uc_mgr->card.current_verb]; - - uc_dbg("current verb %s", verb->name); - uc_dbg("uc_mgr %p device_name %s", uc_mgr, device_name); - - /* find device name and index */ - for (i = 0; i num_devices; i++) { - uc_dbg("verb->num_devices %s", verb->device[i].name); - if (!strcmp(verb->device[i].name, device_name)) - goto found; - } - - uc_error("error: use case device %s not found", device_name); - ret = -EINVAL; - goto out; - -found: - if (enable) { - /* Initialise the new use case device */ - ret = enable_use_case_device(uc_mgr, i, uc_mgr->list, - uc_mgr->handle); - if (ret < 0) - goto out; - } else { - /* disable the old device */ - ret = disable_use_case_device(uc_mgr, i, uc_mgr->list, - uc_mgr->handle); - if (ret < 0) - goto out; - } - -out: - pthread_mutex_unlock(&uc_mgr->mutex); - return ret; + struct list_head mylist, *pos, *npos; + struct myvalue *val; + struct use_case_verb *verb; + struct use_case_device *dev; + struct use_case_modifier *mod; + char **res; + int err; + + if (verbname) { + verb = find_verb(uc_mgr, verbname); + } else { + verb = uc_mgr->active_verb; + } + if (verb == NULL) + return -ENOENT; + INIT_LIST_HEAD(&mylist); + err = add_values(&mylist, identifier, &verb->value_list); + if (err < 0) + goto __fail; + list_for_each(pos, &verb->device_list) { + dev = list_entry(pos, struct use_case_device, list); + err = add_values(&mylist, identifier, &dev->value_list); + if (err < 0) + goto __fail; + } + list_for_each(pos, &verb->modifier_list) { + mod = list_entry(pos, struct use_case_modifier, list); + err = add_values(&mylist, identifier, &mod->value_list); + if (err < 0) + goto __fail; + } + err = alloc_str_list(&mylist, 1, &res); + *list = (const char **)res; + if (err >= 0) { + list_for_each(pos, &mylist) { + val = list_entry(pos, struct myvalue, list); + *res = strdup(val->value); + if (*res == NULL) { + snd_use_case_free_list((const char **)res, err); + err = -ENOMEM; + goto __fail; + } + res++; + } + } + __fail: + list_for_each_safe(pos, npos, &mylist) { + val = list_entry(pos, struct myvalue, list); + list_del(&val->list); + free(val); + } + return err; } /** - * \brief Enable use case device + * \brief Get list of enabled devices + * \param list Returned list + * \param verbname For verb (NULL = current) + * \return Number of list entries if success, otherwise a negative error code + */ +static int get_enabled_device_list(snd_use_case_mgr_t *uc_mgr, + const char **list[]) +{ + if (uc_mgr->active_verb == NULL) + return -EINVAL; + return get_list(&uc_mgr->active_devices, list, + struct use_case_device, active_list, + name); +} + +/** + * \brief Get list of enabled modifiers + * \param list Returned list + * \param verbname For verb (NULL = current) + * \return Number of list entries if success, otherwise a negative error code + */ +static int get_enabled_modifier_list(snd_use_case_mgr_t *uc_mgr, + const char **list[]) +{ + if (uc_mgr->active_verb == NULL) + return -EINVAL; + return get_list(&uc_mgr->active_modifiers, list, + struct use_case_modifier, active_list, + name); +} + +/** + * \brief Obtain a list of entries + * \param uc_mgr Use case manager (may be NULL - card list) + * \param identifier (may be NULL - card list) + * \param list Returned allocated list + * \return Number of list entries if success, otherwise a negative error code + */ +int snd_use_case_get_list(snd_use_case_mgr_t *uc_mgr, + const char *identifier, + const char **list[]) +{ + char *str, *str1; + int err; + + if (uc_mgr == NULL || identifier == NULL) + return get_card_list(list); + pthread_mutex_lock(&uc_mgr->mutex); + if (strcmp(identifier, "_verbs") == 0) + err = get_verb_list(uc_mgr, list); + else if (strcmp(identifier, "_enadevs")) + err = get_enabled_device_list(uc_mgr, list); + else if (strcmp(identifier, "_enamods")) + err = get_enabled_modifier_list(uc_mgr, list); + else { + str1 = strchr(identifier, '/'); + if (str1) { + str = strdup(str1 + 1); + if (str == NULL) { + err = -ENOMEM; + goto __end; + } + } else { + str = NULL; + } + if (check_identifier(identifier, "_devices")) + err = get_device_list(uc_mgr, list, str); + else if (check_identifier(identifier, "_modifiers")) + err = get_modifier_list(uc_mgr, list, str); + else + err = get_value_list(uc_mgr, identifier, list, str); + if (str) + free(str); + } + __end: + pthread_mutex_unlock(&uc_mgr->mutex); + return err; +} + +static int get_value1(const char **value, struct list_head *value_list, + const char *identifier) +{ + struct ucm_value *val; + struct list_head *pos; + + list_for_each(pos, value_list) { + val = list_entry(pos, struct ucm_value, list); + if (check_identifier(identifier, val->name)) { + *value = strdup(val->data); + if (*value == NULL) + return -ENOMEM; + return 0; + } + } + return 0; +} + +/** + * \brief Get value + * \param list Returned list + * \param verbname For verb (NULL = current) + * \return Number of list entries if success, otherwise a negative error code + */ +static int get_value(snd_use_case_mgr_t *uc_mgr, + const char *identifier, + const char **value, + const char *modifier) +{ + struct use_case_modifier *mod; + + if (uc_mgr->active_verb == NULL) + return -ENOENT; + if (modifier == NULL) + return get_value1(value, &uc_mgr->active_verb->value_list, + identifier); + mod = find_modifier(uc_mgr->active_verb, modifier); + if (mod == NULL) + return -EINVAL; + return get_value1(value, &mod->value_list, identifier); +} + +/** + * \brief Get current - string * \param uc_mgr Use case manager - * \param device the device to be enabled - * \return 0 = successful negative = error - */ -int snd_use_case_enable_device(snd_use_case_mgr_t *uc_mgr, - const char *device) -{ - return config_use_case_device(uc_mgr, device, 1); -} - -/** - * \brief Disable use case device - * \param uc_mgr Use case manager - * \param device the device to be disabled - * \return 0 = successful negative = error - */ -int snd_use_case_disable_device(snd_use_case_mgr_t *uc_mgr, - const char *device) -{ - return config_use_case_device(uc_mgr, device, 0); -} - -static struct use_case_device *get_device(snd_use_case_mgr_t *uc_mgr, - const char *name, int *id) -{ - struct use_case_verb *verb; - int i; - - if (uc_mgr->card.current_verb == VERB_NOT_INITIALISED) - return NULL; - - verb = &uc_mgr->verb[uc_mgr->card.current_verb]; - - uc_dbg("current verb %s", verb->name); - - for (i = 0; i < verb->num_devices; i++) { - uc_dbg("device %s", verb->device[i].name); - - if (!strcmp(verb->device[i].name, name)) { - if (id) - *id = i; - return &verb->device[i]; - } - } - - return NULL; -} - -/** - * \brief Disable old_device and then enable new_device. - * If from_device is not enabled just return. - * Check transition sequence firstly. - * \param uc_mgr Use case manager - * \param old the device to be closed - * \param new the device to be opened - * \return 0 = successful negative = error - */ -int snd_use_case_switch_device(snd_use_case_mgr_t *uc_mgr, - const char *old, const char *new) -{ - static struct sequence_element *trans_sequence; - struct use_case_device *old_device; - struct use_case_device *new_device; - int ret = 0, old_id, new_id; - - uc_dbg("old %s, new %s", old, new); - - pthread_mutex_lock(&uc_mgr->mutex); - - old_device = get_device(uc_mgr, old, &old_id); - if (!old_device) { - uc_error("error: device %s not found", old); - ret = -EINVAL; - goto out; - } - - if (!get_device_status(uc_mgr, old_id)) { - uc_error("error: device %s not enabled", old); - goto out; - } - - new_device = get_device(uc_mgr, new, &new_id); - if (!new_device) { - uc_error("error: device %s not found", new); - ret = -EINVAL; - goto out; - } - - trans_sequence = get_transition_sequence(old_device->transition_list, new); - if (trans_sequence != NULL) { - - uc_dbg("find transition sequece %s->%s", old, new); - - ret = exec_transition_sequence(uc_mgr, trans_sequence); - if (ret) - goto out; - - set_device_status(uc_mgr, old_id, 0); - set_device_status(uc_mgr, new_id, 1); - } else { - /* use lock in config_use_case_device */ - pthread_mutex_unlock(&uc_mgr->mutex); - - config_use_case_device(uc_mgr, old, 0); - config_use_case_device(uc_mgr, new, 1); - - return 0; - } -out: - pthread_mutex_unlock(&uc_mgr->mutex); - return ret; -} - -/* - * Check to make sure that the modifier actually supports any of the - * active devices. - */ -static int is_modifier_valid(snd_use_case_mgr_t *uc_mgr, - struct use_case_verb *verb, struct use_case_modifier *modifier) -{ - struct dev_list *dev_list; - int dev; - - /* check modifier list against each enabled device */ - for (dev = 0; dev < verb->num_devices; dev++) { - if (!get_device_status(uc_mgr, dev)) - continue; - - dev_list = modifier->dev_list; - uc_dbg("checking device %s for %s", verb->device[dev].name, - dev_list->name ? dev_list->name : ""); - - while (dev_list) { - uc_dbg("device supports %s", dev_list->name); - if (!strcmp(dev_list->name, verb->device[dev].name)) - return 1; - dev_list = dev_list->next; - } - } - return 0; -} - -static int config_use_case_mod(snd_use_case_mgr_t *uc_mgr, - const char *modifier_name, int enable) -{ - struct use_case_verb *verb; - int ret, i; - - pthread_mutex_lock(&uc_mgr->mutex); - - if (uc_mgr->card.current_verb == VERB_NOT_INITIALISED) { - ret = -EINVAL; - goto out; - } - - verb = &uc_mgr->verb[uc_mgr->card.current_verb]; - - uc_dbg("current verb %s", verb->name); - uc_dbg("uc_mgr %p modifier_name %s", uc_mgr, modifier_name); - - /* find modifier name */ - for (i = 0; i num_modifiers; i++) { - uc_dbg("verb->num_modifiers %d %s", i, verb->modifier[i].name); - if (!strcmp(verb->modifier[i].name, modifier_name) && - is_modifier_valid(uc_mgr, verb, &verb->modifier[i])) - goto found; - } - - uc_error("error: use case modifier %s not found or invalid", - modifier_name); - ret = -EINVAL; - goto out; - -found: - if (enable) { - /* Initialise the new use case device */ - ret = enable_use_case_modifier(uc_mgr, i, uc_mgr->list, - uc_mgr->handle); - if (ret < 0) - goto out; - } else { - /* disable the old device */ - ret = disable_use_case_modifier(uc_mgr, i, uc_mgr->list, - uc_mgr->handle); - if (ret < 0) - goto out; - } - -out: - pthread_mutex_unlock(&uc_mgr->mutex); - return ret; -} - -/** - * \brief Enable use case modifier - * \param uc_mgr Use case manager - * \param modifier the modifier to be enabled - * \return 0 = successful negative = error - */ -int snd_use_case_enable_modifier(snd_use_case_mgr_t *uc_mgr, - const char *modifier) -{ - return config_use_case_mod(uc_mgr, modifier, 1); -} - -/** - * \brief Disable use case modifier - * \param uc_mgr Use case manager - * \param modifier the modifier to be disabled - * \return 0 = successful negative = error - */ -int snd_use_case_disable_modifier(snd_use_case_mgr_t *uc_mgr, - const char *modifier) -{ - return config_use_case_mod(uc_mgr, modifier, 0); -} - -static struct use_case_modifier *get_modifier(snd_use_case_mgr_t *uc_mgr, - const char *name, int *mod_id) -{ - struct use_case_verb *verb; - int i; - - if (uc_mgr->card.current_verb == VERB_NOT_INITIALISED) - return NULL; - - verb = &uc_mgr->verb[uc_mgr->card.current_verb]; - - uc_dbg("current verb %s", verb->name); - - uc_dbg("uc_mgr %p modifier_name %s", uc_mgr, name); - - for (i = 0; i < verb->num_modifiers; i++) { - uc_dbg("verb->num_devices %s", verb->modifier[i].name); - - if (!strcmp(verb->modifier[i].name, name)) { - if (mod_id) - *mod_id = i; - return &verb->modifier[i]; - } - } - - return NULL; -} - -/** - * \brief Disable old_modifier and then enable new_modifier. - * If old_modifier is not enabled just return. - * Check transition sequence firstly. - * \param uc_mgr Use case manager - * \param old the modifier to be closed - * \param new the modifier to be opened - * \return 0 = successful negative = error - */ -int snd_use_case_switch_modifier(snd_use_case_mgr_t *uc_mgr, - const char *old, const char *new) -{ - struct use_case_modifier *old_modifier; - struct use_case_modifier *new_modifier; - static struct sequence_element *trans_sequence; - int ret = 0, old_id, new_id - - uc_dbg("old %s, new %s", old, new); - - pthread_mutex_lock(&uc_mgr->mutex); - - old_modifier = get_modifier(uc_mgr, old, &old_id); - if (!old_modifier) { - uc_error("error: modifier %s not found", old); - ret = -EINVAL; - goto out; - } - - if (!get_modifier_status(uc_mgr, old_id)) { - uc_error("error: modifier %s not enabled", old); - ret = -EINVAL; - goto out; - } - - new_modifier = get_modifier(uc_mgr, new, &new_id); - if (!new_modifier) { - uc_error("error: modifier %s not found", new); - ret = -EINVAL; - goto out; - } - - trans_sequence = get_transition_sequence( - old_modifier->transition_list, new); - if (trans_sequence != NULL) { - uc_dbg("find transition sequence %s->%s", old, new); - - ret = exec_transition_sequence(uc_mgr, trans_sequence); - if (ret) - goto out; - - set_device_status(uc_mgr, old_id, 0); - set_device_status(uc_mgr, new_id, 1); - } else { - /* use lock in config_use_case_mod*/ - pthread_mutex_unlock(&uc_mgr->mutex); - - config_use_case_mod(uc_mgr, old, 0); - config_use_case_mod(uc_mgr, new, 1); - - return 0; - } -out: - pthread_mutex_unlock(&uc_mgr->mutex); - return ret; -} - -/** - * \brief Get current use case verb from sound card - * \param uc_mgr use case manager - * \return Verb Name if success, otherwise NULL - */ -const char *snd_use_case_get_verb(snd_use_case_mgr_t *uc_mgr) -{ - const char *ret = NULL; - - pthread_mutex_lock(&uc_mgr->mutex); - - if (uc_mgr->card.current_verb != VERB_NOT_INITIALISED) - ret = uc_mgr->verb_list[uc_mgr->card.current_verb]; - - pthread_mutex_unlock(&uc_mgr->mutex); - - return ret; -} - -/** - * \brief Get device status for current use case verb - * \param uc_mgr Use case manager - * \param device_name The device we are interested in. - * \return - 1 = enabled, 0 = disabled, negative = error - */ -int snd_use_case_get_device_status(snd_use_case_mgr_t *uc_mgr, - const char *device_name) -{ - struct use_case_device *device; - int ret = -EINVAL, dev_id; - - pthread_mutex_lock(&uc_mgr->mutex); - - device = get_device(uc_mgr, device_name, &dev_id); - if (device == NULL) { - uc_error("error: use case device %s not found", device_name); - goto out; - } - - ret = get_device_status(uc_mgr, dev_id); -out: - pthread_mutex_unlock(&uc_mgr->mutex); - - return ret; -} - -/** - * \brief Get modifier status for current use case verb - * \param uc_mgr Use case manager - * \param device_name The device we are interested in. - * \return - 1 = enabled, 0 = disabled, negative = error - */ -int snd_use_case_get_modifier_status(snd_use_case_mgr_t *uc_mgr, - const char *modifier_name) -{ - struct use_case_modifier *modifier; - int ret = -EINVAL, mod_id; - - pthread_mutex_lock(&uc_mgr->mutex); - - modifier = get_modifier(uc_mgr, modifier_name, &mod_id); - if (modifier == NULL) { - uc_error("error: use case modifier %s not found", modifier_name); - goto out; - } - - ret = get_modifier_status(uc_mgr, mod_id); -out: - pthread_mutex_unlock(&uc_mgr->mutex); - - return ret; -} - -/** - * \brief Get current use case verb QoS - * \param uc_mgr use case manager - * \return QoS level - */ -enum snd_use_case_qos - snd_use_case_get_verb_qos(snd_use_case_mgr_t *uc_mgr) -{ - struct use_case_verb *verb; - enum snd_use_case_qos ret = SND_USE_CASE_QOS_UNKNOWN; - - pthread_mutex_lock(&uc_mgr->mutex); - - if (uc_mgr->card.current_verb != VERB_NOT_INITIALISED) { - verb = &uc_mgr->verb[uc_mgr->card.current_verb]; - ret = verb->qos; - } - - pthread_mutex_unlock(&uc_mgr->mutex); - - return ret; -} - -/** - * \brief Get current use case modifier QoS - * \param uc_mgr use case manager - * \return QoS level - */ -enum snd_use_case_qos - snd_use_case_get_mod_qos(snd_use_case_mgr_t *uc_mgr, - const char *modifier_name) -{ - struct use_case_modifier *modifier; - enum snd_use_case_qos ret = SND_USE_CASE_QOS_UNKNOWN; - - pthread_mutex_lock(&uc_mgr->mutex); - - modifier = get_modifier(uc_mgr, modifier_name, NULL); - if (modifier != NULL) - ret = modifier->qos; - else - uc_error("error: use case modifier %s not found", modifier_name); - pthread_mutex_unlock(&uc_mgr->mutex); - - return ret; -} - -/** - * \brief Get current use case verb playback PCM - * \param uc_mgr use case manager - * \return PCM number if success, otherwise negative - */ -int snd_use_case_get_verb_playback_pcm(snd_use_case_mgr_t *uc_mgr) -{ - struct use_case_verb *verb; - int ret = -EINVAL; - - pthread_mutex_lock(&uc_mgr->mutex); - - if (uc_mgr->card.current_verb != VERB_NOT_INITIALISED) { - verb = &uc_mgr->verb[uc_mgr->card.current_verb]; - ret = verb->playback_pcm; - } - - pthread_mutex_unlock(&uc_mgr->mutex); - - return ret; -} - -/** - * \brief Get current use case verb playback PCM - * \param uc_mgr use case manager - * \return PCM number if success, otherwise negative - */ -int snd_use_case_get_verb_capture_pcm(snd_use_case_mgr_t *uc_mgr) -{ - struct use_case_verb *verb; - int ret = -EINVAL; - - pthread_mutex_lock(&uc_mgr->mutex); - - if (uc_mgr->card.current_verb != VERB_NOT_INITIALISED) { - verb = &uc_mgr->verb[uc_mgr->card.current_verb]; - ret = verb->capture_pcm; - } - - pthread_mutex_unlock(&uc_mgr->mutex); - - return ret; -} - -/** - * \brief Get current use case modifier playback PCM - * \param uc_mgr use case manager - * \return PCM number if success, otherwise negative - */ -int snd_use_case_get_mod_playback_pcm(snd_use_case_mgr_t *uc_mgr, - const char *modifier_name) -{ - struct use_case_modifier *modifier; - int ret = -EINVAL; - - pthread_mutex_lock(&uc_mgr->mutex); - - modifier = get_modifier(uc_mgr, modifier_name, NULL); - if (modifier == NULL) - uc_error("error: use case modifier %s not found", - modifier_name); - else - ret = modifier->playback_pcm; - - pthread_mutex_unlock(&uc_mgr->mutex); - - return ret; -} - -/** - * \brief Get current use case modifier playback PCM - * \param uc_mgr use case manager - * \return PCM number if success, otherwise negative - */ -int snd_use_case_get_mod_capture_pcm(snd_use_case_mgr_t *uc_mgr, - const char *modifier_name) -{ - struct use_case_modifier *modifier; - int ret = -EINVAL; - - pthread_mutex_lock(&uc_mgr->mutex); - - modifier = get_modifier(uc_mgr, modifier_name, NULL); - if (modifier == NULL) - uc_error("error: use case modifier %s not found", - modifier_name); - else - ret = modifier->capture_pcm; - - pthread_mutex_unlock(&uc_mgr->mutex); - - return ret; -} - -/** - * \brief Get volume/mute control name depending on use case device. - * \param uc_mgr use case manager - * \param type the control type we are looking for - * \param device_name The use case device we are interested in. - * \return control name if success, otherwise NULL + * \param identifier + * \param value Value pointer + * \return Zero if success, otherwise a negative error code * - * Get the control id for common volume and mute controls that are aliased - * in the named use case device. - */ -const char *snd_use_case_get_device_ctl_elem_name(snd_use_case_mgr_t *uc_mgr, - enum snd_use_case_control_alias type, const char *device_name) + * Note: String is dynamically allocated, use free() to + * deallocate this string. + */ +int snd_use_case_get(snd_use_case_mgr_t *uc_mgr, + const char *identifier, + const char **value) { - struct use_case_device *device; - const char *kcontrol_name = NULL; + char *str, *str1; + int err; pthread_mutex_lock(&uc_mgr->mutex); - - device = get_device(uc_mgr, device_name, NULL); - if (!device) { - uc_error("error: device %s not found", device_name); - goto out; - } - - switch (type) { - case SND_USE_CASE_ALIAS_PLAYBACK_VOLUME: - kcontrol_name = device->playback_volume_id; - break; - case SND_USE_CASE_ALIAS_CAPTURE_VOLUME: - kcontrol_name = device->capture_volume_id; - break; - case SND_USE_CASE_ALIAS_PLAYBACK_SWITCH: - kcontrol_name = device->playback_switch_id; - break; - case SND_USE_CASE_ALIAS_CAPTURE_SWITCH: - kcontrol_name = device->capture_switch_id; - break; - default: - uc_error("error: invalid control alias %d", type); - break; - } - -out: + if (identifier == NULL) { + *value = strdup(uc_mgr->card_name); + if (*value == NULL) { + err = -ENOMEM; + goto __end; + } + err = 0; + } else if (strcmp(identifier, "_verb") == 0) { + if (uc_mgr->active_verb == NULL) + return -ENOENT; + *value = strdup(uc_mgr->active_verb->name); + if (*value == NULL) { + err = -ENOMEM; + goto __end; + } + err = 0; + } else { + str1 = strchr(identifier, '/'); + if (str1) { + str = strdup(str1 + 1); + if (str == NULL) { + err = -ENOMEM; + goto __end; + } + } else { + str = NULL; + } + err = get_value(uc_mgr, identifier, value, str); + if (str) + free(str); + } + __end: pthread_mutex_unlock(&uc_mgr->mutex); + return err; +} - return kcontrol_name; +long device_status(snd_use_case_mgr_t *uc_mgr, + const char *device_name) +{ + struct use_case_device *dev; + struct list_head *pos; + + list_for_each(pos, &uc_mgr->active_devices) { + dev = list_entry(pos, struct use_case_device, active_list); + if (strcmp(dev->name, device_name) == 0) + return 1; + } + return 0; +} + +long modifier_status(snd_use_case_mgr_t *uc_mgr, + const char *modifier_name) +{ + struct use_case_modifier *mod; + struct list_head *pos; + + list_for_each(pos, &uc_mgr->active_modifiers) { + mod = list_entry(pos, struct use_case_modifier, active_list); + if (strcmp(mod->name, modifier_name) == 0) + return 1; + } + return 0; +} + + +/** + * \brief Get current - integer + * \param uc_mgr Use case manager + * \param identifier + * \return Value if success, otherwise a negative error code + */ +long snd_use_case_geti(snd_use_case_mgr_t *uc_mgr, + const char *identifier) +{ + char *str, *str1; + long err; + + pthread_mutex_lock(&uc_mgr->mutex); + if (0) { + /* nothing here - prepared for fixed identifiers */ + } else { + str1 = strchr(identifier, '/'); + if (str1) { + str = strdup(str1 + 1); + if (str == NULL) { + err = -ENOMEM; + goto __end; + } + } else { + str = NULL; + } + if (check_identifier(identifier, "_devstatus")) + err = device_status(uc_mgr, str); + else if (check_identifier(identifier, "_modstatus")) + err = modifier_status(uc_mgr, str); + else + err = -EINVAL; + if (str) + free(str); + } + __end: + pthread_mutex_unlock(&uc_mgr->mutex); + return err; } /** - * \brief Get volume/mute control IDs depending on use case modifier. - * \param uc_mgr use case manager - * \param type the control type we are looking for - * \param modifier_name The use case modifier we are interested in. - * \return ID if success, otherwise a negative error code - * - * Get the control id for common volume and mute controls that are aliased - * in the named use case device. + * \brief Set new + * \param uc_mgr Use case manager + * \param identifier + * \param value Value + * \return Zero if success, otherwise a negative error code */ -const char *snd_use_case_get_modifier_ctl_elem_name(snd_use_case_mgr_t *uc_mgr, - enum snd_use_case_control_alias type, const char *modifier_name) +int snd_use_case_set(snd_use_case_mgr_t *uc_mgr, + const char *identifier, + const char *value) { - struct use_case_modifier *modifier; - const char *kcontrol_name = NULL; + char *str, *str1; + int err; pthread_mutex_lock(&uc_mgr->mutex); - - modifier = get_modifier(uc_mgr, modifier_name, NULL); - if (!modifier) { - uc_error("error: modifier %s not found", modifier_name); - goto out; - } - - switch (type) { - case SND_USE_CASE_ALIAS_PLAYBACK_VOLUME: - kcontrol_name = modifier->playback_volume_id; - break; - case SND_USE_CASE_ALIAS_CAPTURE_VOLUME: - kcontrol_name = modifier->capture_volume_id; - break; - case SND_USE_CASE_ALIAS_PLAYBACK_SWITCH: - kcontrol_name = modifier->playback_switch_id; - break; - case SND_USE_CASE_ALIAS_CAPTURE_SWITCH: - kcontrol_name = modifier->capture_switch_id; - break; - default: - uc_error("error: invalid control alias %d", type); - break; - } - -out: + if (strcmp(identifier, "_verb") == 0) + err = set_verb_user(uc_mgr, value); + else if (strcmp(identifier, "_enadev") == 0) + err = set_device_user(uc_mgr, value, 1); + else if (strcmp(identifier, "_disdev") == 0) + err = set_device_user(uc_mgr, value, 0); + else if (strcmp(identifier, "_enamod") == 0) + err = set_modifier_user(uc_mgr, value, 1); + else if (strcmp(identifier, "_dismod") == 0) + err = set_modifier_user(uc_mgr, value, 0); + else { + str1 = strchr(identifier, '/'); + if (str1) { + str = strdup(str1 + 1); + if (str == NULL) { + err = -ENOMEM; + goto __end; + } + } else { + str = NULL; + } + if (check_identifier(identifier, "_swdev")) + err = switch_device(uc_mgr, str, value); + else if (check_identifier(identifier, "_swmod")) + err = switch_modifier(uc_mgr, str, value); + else + err = -EINVAL; + if (str) + free(str); + } + __end: pthread_mutex_unlock(&uc_mgr->mutex); - - return kcontrol_name; + return err; } -#endif diff --git a/src/ucm/parser.c b/src/ucm/parser.c index bb047515..e540c20d 100644 --- a/src/ucm/parser.c +++ b/src/ucm/parser.c @@ -219,6 +219,88 @@ static int parse_sequence(snd_use_case_mgr_t *uc_mgr ATTRIBUTE_UNUSED, 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, j, next2; + snd_config_t *n, *n2; + long l; + long long ll; + double d; + snd_config_type_t type; + 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 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(n2); + switch (type) { + case SND_CONFIG_TYPE_INTEGER: + curr->data = malloc(16); + if (curr->data == NULL) + return -ENOMEM; + snd_config_get_integer(n2, &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(n2, &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(n2, &d); + sprintf(curr->data, "%-16g", d); + break; + case SND_CONFIG_TYPE_STRING: + err = parse_string(n2, &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 * @@ -240,10 +322,12 @@ static int parse_sequence(snd_use_case_mgr_t *uc_mgr ATTRIBUTE_UNUSED, * ] * * # Optional TQ and ALSA PCMs - * TQ Voice - * CapturePCM "hw:1" - * MasterPlaybackVolume "name='Master Playback Volume',index=2" - * MasterPlaybackSwitch "name='Master Playback Switch',index=2" + * Value { + * TQ Voice + * CapturePCM "hw:1" + * PlaybackVolume "name='Master Playback Volume',index=2" + * PlaybackSwitch "name='Master Playback Switch',index=2" + * } * * } */ @@ -267,6 +351,7 @@ static int parse_modifier(snd_use_case_mgr_t *uc_mgr, INIT_LIST_HEAD(&modifier->disable_list); INIT_LIST_HEAD(&modifier->transition_list); INIT_LIST_HEAD(&modifier->dev_list); + INIT_LIST_HEAD(&modifier->value_list); list_add_tail(&modifier->list, &verb->modifier_list); err = snd_config_get_id(cfg, &id); if (err < 0) @@ -335,64 +420,10 @@ static int parse_modifier(snd_use_case_mgr_t *uc_mgr, continue; } - if (strcmp(id, "TQ") == 0) { - err = parse_string(n, &modifier->tq); + if (strcmp(id, "Value") == 0) { + err = parse_value(uc_mgr, &modifier->value_list, n); 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"); + uc_error("error: failed to parse Value"); return err; } continue; @@ -422,9 +453,10 @@ static int parse_modifier(snd_use_case_mgr_t *uc_mgr, * ... * ] * - * MasterPlaybackVolume "name='Master Playback Volume',index=2" - * MasterPlaybackSwitch "name='Master Playback Switch',index=2" - * + * Value { + * PlaybackVolume "name='Master Playback Volume',index=2" + * PlaybackSwitch "name='Master Playback Switch',index=2" + * } * } */ static int parse_device_index(snd_use_case_mgr_t *uc_mgr, @@ -446,17 +478,16 @@ static int parse_device_index(snd_use_case_mgr_t *uc_mgr, INIT_LIST_HEAD(&device->enable_list); INIT_LIST_HEAD(&device->disable_list); INIT_LIST_HEAD(&device->transition_list); + INIT_LIST_HEAD(&device->value_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; - } + device->name = malloc(strlen(name) + strlen(id) + 2); + if (device->name == NULL) + return -ENOMEM; + strcpy(device->name, name); + strcat(device->name, "."); + strcat(device->name, id); snd_config_for_each(i, next, cfg) { const char *id; @@ -506,37 +537,10 @@ static int parse_device_index(snd_use_case_mgr_t *uc_mgr, continue; } - if (strcmp(id, "MasterPlaybackVolume") == 0) { - err = parse_string(n, &device->playback_volume_id); + if (strcmp(id, "Value") == 0) { + err = parse_value(uc_mgr, &device->value_list, n); 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"); + uc_error("error: failed to parse Value"); return err; } continue; @@ -577,7 +581,7 @@ static int parse_device(snd_use_case_mgr_t *uc_mgr, * # 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" + * 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" @@ -592,10 +596,11 @@ static int parse_device(snd_use_case_mgr_t *uc_mgr, * ] * * # Optional TQ and ALSA PCMs - * TQ HiFi - * CapturePCM 0 - * PlaybackPCM 0 - * + * Value { + * TQ HiFi + * CapturePCM 0 + * PlaybackPCM 0 + * } * } */ static int parse_verb(snd_use_case_mgr_t *uc_mgr, @@ -643,25 +648,9 @@ static int parse_verb(snd_use_case_mgr_t *uc_mgr, 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 (strcmp(id, "Value") == 0) { + uc_dbg("Parse Value"); + err = parse_value(uc_mgr, &verb->value_list, n); if (err < 0) return err; continue; @@ -703,6 +692,7 @@ static int parse_verb_file(snd_use_case_mgr_t *uc_mgr, 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); verb->name = strdup(use_case_name); if (verb->name == NULL) diff --git a/src/ucm/ucm_local.h b/src/ucm/ucm_local.h index ffef8f00..a3c1dcb4 100644 --- a/src/ucm/ucm_local.h +++ b/src/ucm/ucm_local.h @@ -50,6 +50,12 @@ #define SEQUENCE_ELEMENT_TYPE_SLEEP 2 #define SEQUENCE_ELEMENT_TYPE_EXEC 3 +struct ucm_value { + struct list_head list; + char *name; + char *data; +}; + struct sequence_element { struct list_head list; unsigned int type; @@ -99,20 +105,8 @@ struct use_case_modifier { /* list of supported devices per modifier */ struct list_head dev_list; - /* ALSA PCM devices associated with any modifier PCM streams */ - char *capture_pcm; - char *playback_pcm; - - /* Any modifier stream TQ */ - char *tq; - - /* aliased controls */ - char *playback_ctl; - char *playback_volume_id; - char *playback_switch_id; - char *capture_ctl; - char *capture_volume_id; - char *capture_switch_id; + /* values */ + struct list_head value_list; }; /* @@ -127,7 +121,6 @@ struct use_case_device { char *name; char *comment; - long idx; /* index for similar devices i.e. 2 headphone jacks */ /* device enable and disable sequences */ struct list_head enable_list; @@ -136,13 +129,8 @@ struct use_case_device { /* device transition list */ struct list_head transition_list; - /* aliased controls */ - char *playback_ctl; - char *playback_volume_id; - char *playback_switch_id; - char *capture_ctl; - char *capture_volume_id; - char *capture_switch_id; + /* value list */ + struct list_head value_list; }; /* @@ -164,16 +152,14 @@ struct use_case_verb { /* verb transition list */ struct list_head transition_list; - /* verb PCMs and TQ */ - char *tq; - char *capture_pcm; - char *playback_pcm; - /* hardware devices that can be used with this use case */ struct list_head device_list; /* modifiers that can be used with this use case */ struct list_head modifier_list; + + /* value list */ + struct list_head value_list; }; /* diff --git a/src/ucm/utils.c b/src/ucm/utils.c index 98e3cf65..83926da3 100644 --- a/src/ucm/utils.c +++ b/src/ucm/utils.c @@ -80,6 +80,19 @@ int uc_mgr_config_load(const char *file, snd_config_t **cfg) return 0; } +void uc_mgr_free_value(struct list_head *base) +{ + struct list_head *pos, *npos; + struct ucm_value *val; + + list_for_each_safe(pos, npos, base) { + val = list_entry(pos, struct ucm_value, list); + free(val->name); + free(val->data); + list_del(pos); + } +} + void uc_mgr_free_dev_list(struct list_head *base) { struct list_head *pos, *npos; @@ -147,15 +160,7 @@ void uc_mgr_free_modifier(struct list_head *base) uc_mgr_free_sequence(&mod->disable_list); uc_mgr_free_transition(&mod->transition_list); uc_mgr_free_dev_list(&mod->dev_list); - free(mod->capture_pcm); - free(mod->playback_pcm); - free(mod->tq); - free(mod->playback_ctl); - free(mod->playback_volume_id); - free(mod->playback_switch_id); - free(mod->capture_ctl); - free(mod->capture_volume_id); - free(mod->capture_switch_id); + uc_mgr_free_value(&mod->value_list); free(mod); list_del(pos); } @@ -173,12 +178,7 @@ void uc_mgr_free_device(struct list_head *base) uc_mgr_free_sequence(&dev->enable_list); uc_mgr_free_sequence(&dev->disable_list); uc_mgr_free_transition(&dev->transition_list); - free(dev->playback_ctl); - free(dev->playback_volume_id); - free(dev->playback_switch_id); - free(dev->capture_ctl); - free(dev->capture_volume_id); - free(dev->capture_switch_id); + uc_mgr_free_value(&dev->value_list); free(dev); list_del(pos); } @@ -196,9 +196,7 @@ void uc_mgr_free_verb(snd_use_case_mgr_t *uc_mgr) uc_mgr_free_sequence(&verb->enable_list); uc_mgr_free_sequence(&verb->disable_list); uc_mgr_free_transition(&verb->transition_list); - free(verb->tq); - free(verb->capture_pcm); - free(verb->playback_pcm); + uc_mgr_free_value(&verb->value_list); uc_mgr_free_device(&verb->device_list); uc_mgr_free_modifier(&verb->modifier_list); free(verb); From 6c6dc230bc14e4b7007d8b5049518a5cea5164ee Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Wed, 22 Sep 2010 15:35:11 +0200 Subject: [PATCH 07/30] ucm: added implementation for other ucm parts, only card name list is missing Signed-off-by: Jaroslav Kysela --- src/ucm/main.c | 228 ++++++++++++++++++++++++++++++++++++++++++-- src/ucm/ucm_local.h | 4 - 2 files changed, 220 insertions(+), 12 deletions(-) diff --git a/src/ucm/main.c b/src/ucm/main.c index e036c4dd..e06e3fb7 100644 --- a/src/ucm/main.c +++ b/src/ucm/main.c @@ -313,6 +313,21 @@ static inline struct use_case_verb *find_verb(snd_use_case_mgr_t *uc_mgr, verb_name); } +/** + * \brief Find device + * \param verb Use case verb + * \param device_name device to find + * \return structure on success, otherwise a NULL (not found) + */ +static inline struct use_case_device * + find_device(struct use_case_verb *verb, + const char *device_name) +{ + return find(&verb->device_list, + struct use_case_device, list, name, + device_name); +} + /** * \brief Find modifier * \param verb Use case verb @@ -488,20 +503,16 @@ int snd_use_case_mgr_close(snd_use_case_mgr_t *uc_mgr) return 0; } -/** - * \brief Reset sound card controls to default values. - * \param uc_mgr Use case manager - * \return zero on success, otherwise a negative error code +/* + * Tear down current use case verb, device and modifier. */ -int snd_use_case_mgr_reset(snd_use_case_mgr_t *uc_mgr) +static int dismantle_use_case(snd_use_case_mgr_t *uc_mgr) { struct list_head *pos, *npos; struct use_case_modifier *modifier; struct use_case_device *device; int err; - pthread_mutex_lock(&uc_mgr->mutex); - list_for_each_safe(pos, npos, &uc_mgr->active_modifiers) { modifier = list_entry(pos, struct use_case_modifier, active_list); @@ -529,6 +540,23 @@ int snd_use_case_mgr_reset(snd_use_case_mgr_t *uc_mgr) err = execute_sequence(uc_mgr, &uc_mgr->default_list); + return err; +} + +/** + * \brief Reset sound card controls to default values. + * \param uc_mgr Use case manager + * \return zero on success, otherwise a negative error code + */ +int snd_use_case_mgr_reset(snd_use_case_mgr_t *uc_mgr) +{ + int err; + + pthread_mutex_lock(&uc_mgr->mutex); + err = execute_sequence(uc_mgr, &uc_mgr->default_list); + INIT_LIST_HEAD(&uc_mgr->active_modifiers); + INIT_LIST_HEAD(&uc_mgr->active_devices); + uc_mgr->active_verb = NULL; pthread_mutex_unlock(&uc_mgr->mutex); return err; } @@ -950,6 +978,190 @@ long snd_use_case_geti(snd_use_case_mgr_t *uc_mgr, return err; } +static int handle_transition_verb(snd_use_case_mgr_t *uc_mgr, + struct use_case_verb *new_verb) +{ + struct list_head *pos; + struct transition_sequence *trans; + int err; + + list_for_each(pos, &uc_mgr->active_verb->transition_list) { + trans = list_entry(pos, struct transition_sequence, list); + if (strcmp(trans->name, new_verb->name) == 0) { + err = execute_sequence(uc_mgr, &trans->transition_list); + if (err >= 0) + return 1; + return err; + } + } + return 0; +} + +static int set_verb_user(snd_use_case_mgr_t *uc_mgr, + const char *verb_name) +{ + struct use_case_verb *verb; + int err; + + if (uc_mgr->active_verb && + strcmp(uc_mgr->active_verb->name, verb_name) == 0) + return 0; + if (strcmp(verb_name, SND_USE_CASE_VERB_INACTIVE) != 0) { + verb = find_verb(uc_mgr, verb_name); + if (verb == NULL) + return -ENOENT; + } else { + verb = NULL; + } + if (uc_mgr->active_verb) { + err = handle_transition_verb(uc_mgr, verb); + if (err == 0) { + err = dismantle_use_case(uc_mgr); + if (err < 0) + return err; + } else if (err == 1) { + uc_mgr->active_verb = verb; + verb = NULL; + } else { + verb = NULL; /* show error */ + } + } + if (verb) { + err = set_verb(uc_mgr, verb, 1); + if (err < 0) + uc_error("error: failed to initialize new use case: %s", + verb_name); + } + return err; +} + + +static int set_device_user(snd_use_case_mgr_t *uc_mgr, + const char *device_name, + int enable) +{ + struct use_case_device *device; + + if (uc_mgr->active_verb == NULL) + return -ENOENT; + device = find_device(uc_mgr->active_verb, device_name); + if (device == NULL) + return -ENOENT; + return set_device(uc_mgr, device, enable); +} + +static int set_modifier_user(snd_use_case_mgr_t *uc_mgr, + const char *modifier_name, + int enable) +{ + struct use_case_modifier *modifier; + + if (uc_mgr->active_verb == NULL) + return -ENOENT; + modifier = find_modifier(uc_mgr->active_verb, modifier_name); + if (modifier == NULL) + return -ENOENT; + return set_modifier(uc_mgr, modifier, enable); +} + +static int switch_device(snd_use_case_mgr_t *uc_mgr, + const char *old_device, + const char *new_device) +{ + struct use_case_device *xold, *xnew; + struct transition_sequence *trans; + struct list_head *pos; + int err, seq_found = 0; + + if (uc_mgr->active_verb == NULL) + return -ENOENT; + if (device_status(uc_mgr, old_device) == 0) { + uc_error("error: device %s not enabled", old_device); + return -EINVAL; + } + if (device_status(uc_mgr, new_device) != 0) { + uc_error("error: device %s already enabled", new_device); + return -EINVAL; + } + xold = find_device(uc_mgr->active_verb, old_device); + if (xold == NULL) + return -ENOENT; + xnew = find_device(uc_mgr->active_verb, new_device); + if (xold == NULL) + return -ENOENT; + err = 0; + list_for_each(pos, &xold->transition_list) { + trans = list_entry(pos, struct transition_sequence, list); + if (strcmp(trans->name, new_device) == 0) { + err = execute_sequence(uc_mgr, &trans->transition_list); + if (err >= 0) { + list_del(&xold->active_list); + list_add_tail(&xnew->active_list, &uc_mgr->active_devices); + } + seq_found = 1; + break; + } + } + if (!seq_found) { + err = set_device(uc_mgr, xold, 0); + if (err < 0) + return err; + err = set_device(uc_mgr, xnew, 0); + if (err < 0) + return err; + } + return err; +} + +static int switch_modifier(snd_use_case_mgr_t *uc_mgr, + const char *old_modifier, + const char *new_modifier) +{ + struct use_case_modifier *xold, *xnew; + struct transition_sequence *trans; + struct list_head *pos; + int err, seq_found = 0; + + if (uc_mgr->active_verb == NULL) + return -ENOENT; + if (modifier_status(uc_mgr, old_modifier) == 0) { + uc_error("error: modifier %s not enabled", old_modifier); + return -EINVAL; + } + if (modifier_status(uc_mgr, new_modifier) != 0) { + uc_error("error: modifier %s already enabled", new_modifier); + return -EINVAL; + } + xold = find_modifier(uc_mgr->active_verb, old_modifier); + if (xold == NULL) + return -ENOENT; + xnew = find_modifier(uc_mgr->active_verb, new_modifier); + if (xold == NULL) + return -ENOENT; + err = 0; + list_for_each(pos, &xold->transition_list) { + trans = list_entry(pos, struct transition_sequence, list); + if (strcmp(trans->name, new_modifier) == 0) { + err = execute_sequence(uc_mgr, &trans->transition_list); + if (err >= 0) { + list_del(&xold->active_list); + list_add_tail(&xnew->active_list, &uc_mgr->active_modifiers); + } + seq_found = 1; + break; + } + } + if (!seq_found) { + err = set_modifier(uc_mgr, xold, 0); + if (err < 0) + return err; + err = set_modifier(uc_mgr, xnew, 0); + if (err < 0) + return err; + } + return err; +} + /** * \brief Set new * \param uc_mgr Use case manager @@ -961,7 +1173,7 @@ int snd_use_case_set(snd_use_case_mgr_t *uc_mgr, const char *identifier, const char *value) { - char *str, *str1; + char *str, *str1; int err; pthread_mutex_lock(&uc_mgr->mutex); diff --git a/src/ucm/ucm_local.h b/src/ucm/ucm_local.h index a3c1dcb4..06a1e5f6 100644 --- a/src/ucm/ucm_local.h +++ b/src/ucm/ucm_local.h @@ -39,12 +39,8 @@ #include "local.h" #include "use-case.h" -#define PRE_SEQ 0 -#define POST_SEQ 1 #define MAX_FILE 256 #define ALSA_USE_CASE_DIR ALSA_CONFIG_DIR "/ucm" -#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0])) -#define VERB_NOT_INITIALISED -1 #define SEQUENCE_ELEMENT_TYPE_CSET 1 #define SEQUENCE_ELEMENT_TYPE_SLEEP 2 From 0ae6c43b8615b7df466408e3e041744b788e1fb8 Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Fri, 1 Oct 2010 13:30:59 +0200 Subject: [PATCH 08/30] ucm: cosmetic fix Signed-off-by: Jaroslav Kysela --- include/use-case.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/use-case.h b/include/use-case.h index fdbcaca0..dffcd346 100644 --- a/include/use-case.h +++ b/include/use-case.h @@ -82,7 +82,7 @@ extern "C" { * o Set and Get use case verbs, devices and modifiers for the machine. * o Get the ALSA PCM playback and capture device PCMs for use case verb and * modifier. - * o Get the QoS parameter for each use case verb and modifier. + * o Get the TQ parameter for each use case verb and modifier. * o Get the ALSA master playback and capture volume/switch kcontrols * for each use case. */ From 3a3439450864c8706e4e20d392fd43ecf935153f Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Tue, 12 Oct 2010 13:29:05 +0200 Subject: [PATCH 09/30] ucm: unify snd_use_case_geti(), add snd_use_case_card_list() template Signed-off-by: Jaroslav Kysela --- include/use-case.h | 23 ++++++++++++++++++++--- src/ucm/main.c | 19 ++++++++++++++----- 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/include/use-case.h b/include/use-case.h index dffcd346..d5ca361b 100644 --- a/include/use-case.h +++ b/include/use-case.h @@ -245,14 +245,16 @@ int snd_use_case_get(snd_use_case_mgr_t *uc_mgr, * \brief Get current - integer * \param uc_mgr Use case manager * \param identifier - * \return Value if success, otherwise a negative error code + * \param value result + * \return Zero if success, otherwise a negative error code * * Known identifiers: * _devstatus/ - return status for given device * _modstatus/ - return status for given modifier */ -long snd_use_case_geti(snd_use_case_mgr_t *uc_mgr, - const char *identifier); +int snd_use_case_geti(snd_use_case_mgr_t *uc_mgr, + const char *identifier, + long *value); /** * \brief Set new @@ -310,6 +312,21 @@ int snd_use_case_mgr_close(snd_use_case_mgr_t *uc_mgr); */ int snd_use_case_mgr_reset(snd_use_case_mgr_t *uc_mgr); +/* + * helper functions + */ + +/** + * \brief Obtain a list of cards + * \param uc_mgr Use case manager (may be NULL - card list) + * \param list Returned allocated list + * \return Number of list entries if success, otherwise a negative error code + */ +static inline int snd_use_case_card_list(const char **list[]) +{ + return snd_use_case_get_list(NULL, NULL, list); +} + /** * \} */ diff --git a/src/ucm/main.c b/src/ucm/main.c index e06e3fb7..88389764 100644 --- a/src/ucm/main.c +++ b/src/ucm/main.c @@ -944,8 +944,9 @@ long modifier_status(snd_use_case_mgr_t *uc_mgr, * \param identifier * \return Value if success, otherwise a negative error code */ -long snd_use_case_geti(snd_use_case_mgr_t *uc_mgr, - const char *identifier) +int snd_use_case_geti(snd_use_case_mgr_t *uc_mgr, + const char *identifier, + long *value) { char *str, *str1; long err; @@ -964,11 +965,19 @@ long snd_use_case_geti(snd_use_case_mgr_t *uc_mgr, } else { str = NULL; } - if (check_identifier(identifier, "_devstatus")) + if (check_identifier(identifier, "_devstatus")) { err = device_status(uc_mgr, str); - else if (check_identifier(identifier, "_modstatus")) + if (err >= 0) { + *value = err; + err = 0; + } + } else if (check_identifier(identifier, "_modstatus")) { err = modifier_status(uc_mgr, str); - else + if (err >= 0) { + *value = err; + err = 0; + } + } else err = -EINVAL; if (str) free(str); From e8208666379e10bbd56365b0a6d17f3daab730e3 Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Wed, 13 Oct 2010 11:48:52 +0200 Subject: [PATCH 10/30] ucm: implemented card list feature - also added some test files to test/ucm tree Signed-off-by: Jaroslav Kysela --- src/ucm/main.c | 12 +-- src/ucm/parser.c | 142 +++++++++++++++++++++++--- src/ucm/ucm_local.h | 1 + src/ucm/utils.c | 5 + test/ucm/anothercard/anothercard.conf | 1 + test/ucm/testcard1/testcard1.conf | 0 6 files changed, 134 insertions(+), 27 deletions(-) create mode 100644 test/ucm/anothercard/anothercard.conf create mode 100644 test/ucm/testcard1/testcard1.conf diff --git a/src/ucm/main.c b/src/ucm/main.c index 88389764..e233f414 100644 --- a/src/ucm/main.c +++ b/src/ucm/main.c @@ -561,16 +561,6 @@ int snd_use_case_mgr_reset(snd_use_case_mgr_t *uc_mgr) return err; } -/** - * \brief Get list of cards in pair cardname+comment - * \param list Returned list - * \return Number of list entries if success, otherwise a negative error code - */ -static int get_card_list(const char **list[]) -{ - return -ENXIO; /* Not Yet Implemented */ -} - /** * \brief Get list of verbs in pair verbname+comment * \param list Returned list @@ -780,7 +770,7 @@ int snd_use_case_get_list(snd_use_case_mgr_t *uc_mgr, int err; if (uc_mgr == NULL || identifier == NULL) - return get_card_list(list); + return uc_mgr_scan_master_configs(list); pthread_mutex_lock(&uc_mgr->mutex); if (strcmp(identifier, "_verbs") == 0) err = get_verb_list(uc_mgr, list); diff --git a/src/ucm/parser.c b/src/ucm/parser.c index e540c20d..4e2a9d74 100644 --- a/src/ucm/parser.c +++ b/src/ucm/parser.c @@ -31,6 +31,7 @@ */ #include "ucm_local.h" +#include static int parse_sequence(snd_use_case_mgr_t *uc_mgr, struct list_head *base, @@ -827,7 +828,7 @@ static int parse_master_section(snd_use_case_mgr_t *uc_mgr, snd_config_t *cfg, } /* - * parse and execute controls + * parse controls */ static int parse_controls(snd_use_case_mgr_t *uc_mgr, snd_config_t *cfg) { @@ -851,6 +852,8 @@ static int parse_controls(snd_use_case_mgr_t *uc_mgr, snd_config_t *cfg) * #Example master file for blah sound card * #By Joe Blogs * + * Comment "Nice Abstracted Soundcard" + * * # The file is divided into Use case sections. One section per use case verb. * * SectionUseCase."Voice Call" { @@ -882,7 +885,8 @@ 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; + const char *id; + int err; if (snd_config_get_type(cfg) != SND_CONFIG_TYPE_COMPOUND) { uc_error("compound type expected for master file"); @@ -891,26 +895,35 @@ static int parse_master_file(snd_use_case_mgr_t *uc_mgr, snd_config_t *cfg) /* 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; + 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) { - ret = parse_compound(uc_mgr, n, + err = parse_compound(uc_mgr, n, parse_master_section, NULL, NULL); - if (ret < 0) - return ret; + if (err < 0) + return err; 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; + err = parse_controls(uc_mgr, n); + if (err < 0) + return err; continue; } uc_error("uknown master file field %s", id); @@ -918,25 +931,39 @@ static int parse_master_file(snd_use_case_mgr_t *uc_mgr, snd_config_t *cfg) return 0; } -/* load master use case file for sound card */ -int uc_mgr_import_master_config(snd_use_case_mgr_t *uc_mgr) +/** The name of the environment variable containing the UCM directory */ +#define ALSA_CONFIG_UCM_VAR "ALSA_CONFIG_UCM" + +static int load_master_config(const char *card_name, snd_config_t **cfg) { char filename[MAX_FILE]; - snd_config_t *cfg; + char *env = getenv(ALSA_CONFIG_UCM_VAR); int err; snprintf(filename, sizeof(filename)-1, - "%s/%s/%s.conf", ALSA_USE_CASE_DIR, - uc_mgr->card_name, uc_mgr->card_name); + "%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); + err = uc_mgr_config_load(filename, cfg); if (err < 0) { uc_error("error: could not parse configuration for card %s", - uc_mgr->card_name); + 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) @@ -944,3 +971,86 @@ int uc_mgr_import_master_config(snd_use_case_mgr_t *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]; + char *env = getenv(ALSA_CONFIG_UCM_VAR); + const char **list; + snd_config_t *cfg, *c; + int i, cnt, err; + struct dirent **namelist; + + snprintf(filename, sizeof(filename)-1, + "%s", env ? env : ALSA_USE_CASE_DIR); + filename[MAX_FILE-1] = '\0'; + + err = scandir(filename, &namelist, filename_filter, alphasort); + if (err < 0) { + err = -errno; + uc_error("error: could not scan directory %s: %s", + filename, strerror(-err)); + return err; + } + cnt = err; + + 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; + } + } + 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; +} diff --git a/src/ucm/ucm_local.h b/src/ucm/ucm_local.h index 06a1e5f6..7dae5b6a 100644 --- a/src/ucm/ucm_local.h +++ b/src/ucm/ucm_local.h @@ -193,6 +193,7 @@ void uc_mgr_stdout(const char *fmt, ...); int uc_mgr_config_load(const char *file, snd_config_t **cfg); int uc_mgr_import_master_config(snd_use_case_mgr_t *uc_mgr); +int uc_mgr_scan_master_configs(const char **_list[]); void uc_mgr_free_sequence_element(struct sequence_element *seq); void uc_mgr_free_verb(snd_use_case_mgr_t *uc_mgr); diff --git a/src/ucm/utils.c b/src/ucm/utils.c index 83926da3..0f8207e8 100644 --- a/src/ucm/utils.c +++ b/src/ucm/utils.c @@ -57,8 +57,13 @@ int uc_mgr_config_load(const char *file, snd_config_t **cfg) int err; fp = fopen(file, "r"); + if (fp == NULL) { + err = -errno; + goto __err; + } err = snd_input_stdio_attach(&in, fp, 1); if (err < 0) { + __err: uc_error("could not open configuration file %s", file); return err; } diff --git a/test/ucm/anothercard/anothercard.conf b/test/ucm/anothercard/anothercard.conf new file mode 100644 index 00000000..3d9ed7de --- /dev/null +++ b/test/ucm/anothercard/anothercard.conf @@ -0,0 +1 @@ +Comment "Another Card" diff --git a/test/ucm/testcard1/testcard1.conf b/test/ucm/testcard1/testcard1.conf new file mode 100644 index 00000000..e69de29b From e4083a1182de660960afba5438a4dad47cbeac2e Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Tue, 26 Oct 2010 14:26:46 +0200 Subject: [PATCH 11/30] ucm: debug parser Signed-off-by: Jaroslav Kysela --- src/ucm/parser.c | 372 ++++++++++++++++++++-------------- src/ucm/ucm_local.h | 1 + src/ucm/utils.c | 26 ++- test/ucm/TestHDA/Case1.conf | 24 +++ test/ucm/TestHDA/TestHDA.conf | 11 + 5 files changed, 274 insertions(+), 160 deletions(-) create mode 100644 test/ucm/TestHDA/Case1.conf create mode 100644 test/ucm/TestHDA/TestHDA.conf diff --git a/src/ucm/parser.c b/src/ucm/parser.c index 4e2a9d74..1b5a4310 100644 --- a/src/ucm/parser.c +++ b/src/ucm/parser.c @@ -33,6 +33,9 @@ #include "ucm_local.h" #include +/** 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); @@ -70,43 +73,37 @@ static int parse_transition(snd_use_case_mgr_t *uc_mgr, 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; + 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); - err = -EINVAL; - goto __err; - } - - err = parse_sequence(uc_mgr, &tseq->transition_list, n); - if (err < 0) - return err; - } + if (snd_config_get_id(n, &id) < 0) + return -EINVAL; - list_add(&tseq->list, tlist); + 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; - __err: - free(tseq->name); - free(tseq); - return err; } /* @@ -128,7 +125,7 @@ static int parse_compound(snd_use_case_mgr_t *uc_mgr, snd_config_t *cfg, uc_error("compound type expected for %s", id); return -EINVAL; } - /* parse master config sections */ + /* parse compound */ snd_config_for_each(i, next, cfg) { n = snd_config_iterator_entry(i); @@ -145,6 +142,47 @@ static int parse_compound(snd_use_case_mgr_t *uc_mgr, snd_config_t *cfg, return 0; } + +/* + * Parse transition + */ +static int parse_supported_device(snd_use_case_mgr_t *uc_mgr ATTRIBUTE_UNUSED, + struct list_head *dlist, + snd_config_t *cfg) +{ + struct dev_list *sdev; + 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; + + 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, dlist); + } + return 0; +} + /* * Parse sequences. * @@ -163,58 +201,60 @@ static int parse_sequence(snd_use_case_mgr_t *uc_mgr ATTRIBUTE_UNUSED, snd_config_t *cfg) { struct sequence_element *curr; - snd_config_iterator_t i, next, j, next2; - snd_config_t *n, *n2; + snd_config_iterator_t i, next; + snd_config_t *n; int err; + 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; 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; + err = snd_config_get_id(n, &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); + /* 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, "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; } - - 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); + continue; } + + if (strcmp(id, "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(id, "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; @@ -237,65 +277,66 @@ static int parse_value(snd_use_case_mgr_t *uc_mgr ATTRIBUTE_UNUSED, snd_config_t *cfg) { struct ucm_value *curr; - snd_config_iterator_t i, next, j, next2; - snd_config_t *n, *n2; + 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); - 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; + err = snd_config_get_id(n, &id); + if (err < 0) + continue; - /* alloc new value */ - curr = calloc(1, sizeof(struct ucm_value)); - if (curr == NULL) + /* 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; - list_add_tail(&curr->list, base); - curr->name = strdup(id); - if (curr->name == NULL) + 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; - type = snd_config_get_type(n2); - switch (type) { - case SND_CONFIG_TYPE_INTEGER: - curr->data = malloc(16); - if (curr->data == NULL) - return -ENOMEM; - snd_config_get_integer(n2, &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(n2, &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(n2, &d); - sprintf(curr->data, "%-16g", d); - break; - case SND_CONFIG_TYPE_STRING: - err = parse_string(n2, &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; + 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; } } @@ -322,6 +363,10 @@ static int parse_value(snd_use_case_mgr_t *uc_mgr ATTRIBUTE_UNUSED, * ... * ] * + * TransitionSequence."ToModifierName" [ + * ... + * ] + * * # Optional TQ and ALSA PCMs * Value { * TQ Voice @@ -377,18 +422,12 @@ static int parse_modifier(snd_use_case_mgr_t *uc_mgr, } 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); + err = parse_supported_device(uc_mgr, &modifier->dev_list, n); if (err < 0) { - free(sdev); + uc_error("error: failed to parse supported" + " device list"); return err; } - list_add(&sdev->list, &modifier->dev_list); - continue; } if (strcmp(id, "EnableSequence") == 0) { @@ -411,7 +450,7 @@ static int parse_modifier(snd_use_case_mgr_t *uc_mgr, continue; } - if (strcmp(id, "TransitionModifier") == 0) { + if (strcmp(id, "TransitionSequence") == 0) { err = parse_transition(uc_mgr, &modifier->transition_list, n); if (err < 0) { uc_error("error: failed to parse transition" @@ -454,6 +493,10 @@ static int parse_modifier(snd_use_case_mgr_t *uc_mgr, * ... * ] * + * TransitionSequence."ToDevice" [ + * ... + * ] + * * Value { * PlaybackVolume "name='Master Playback Volume',index=2" * PlaybackSwitch "name='Master Playback Switch',index=2" @@ -527,8 +570,8 @@ static int parse_device_index(snd_use_case_mgr_t *uc_mgr, continue; } - if (strcmp(id, "TransitionDevice") == 0) { - uc_dbg("TransitionDevice"); + 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" @@ -596,11 +639,16 @@ static int parse_device(snd_use_case_mgr_t *uc_mgr, * 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 0 - * PlaybackPCM 0 + * CapturePCM "hw:0" + * PlaybackPCM "hw:0" * } * } */ @@ -621,7 +669,7 @@ static int parse_verb(snd_use_case_mgr_t *uc_mgr, if (strcmp(id, "EnableSequence") == 0) { uc_dbg("Parse EnableSequence"); - err = parse_sequence(uc_mgr, &verb->enable_list, cfg); + err = parse_sequence(uc_mgr, &verb->enable_list, n); if (err < 0) { uc_error("error: failed to parse verb enable sequence"); return err; @@ -631,7 +679,7 @@ static int parse_verb(snd_use_case_mgr_t *uc_mgr, if (strcmp(id, "DisableSequence") == 0) { uc_dbg("Parse DisableSequence"); - err = parse_sequence(uc_mgr, &verb->disable_list, cfg); + err = parse_sequence(uc_mgr, &verb->disable_list, n); if (err < 0) { uc_error("error: failed to parse verb disable sequence"); return err; @@ -639,11 +687,11 @@ static int parse_verb(snd_use_case_mgr_t *uc_mgr, continue; } - if (strcmp(id, "TransitionVerb") == 0) { - uc_dbg("Parse TransitionVerb"); + 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 verb"); + uc_error("error: failed to parse transition sequence"); return err; } continue; @@ -682,6 +730,7 @@ static int parse_verb_file(snd_use_case_mgr_t *uc_mgr, struct use_case_verb *verb; snd_config_t *cfg; char filename[MAX_FILE]; + char *env = getenv(ALSA_CONFIG_UCM_VAR); int err; /* allocate verb */ @@ -703,7 +752,8 @@ static int parse_verb_file(snd_use_case_mgr_t *uc_mgr, return -ENOMEM; /* open Verb file for reading */ - snprintf(filename, sizeof(filename), "%s/%s/%s", ALSA_USE_CASE_DIR, + snprintf(filename, sizeof(filename), "%s/%s/%s", + env ? env : ALSA_USE_CASE_DIR, uc_mgr->card_name, file); filename[sizeof(filename)-1] = '\0'; @@ -832,16 +882,18 @@ static int parse_master_section(snd_use_case_mgr_t *uc_mgr, snd_config_t *cfg, */ 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 (!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; } - printf("parse_controls - not yet implemented\n"); + return 0; } @@ -931,9 +983,6 @@ static int parse_master_file(snd_use_case_mgr_t *uc_mgr, snd_config_t *cfg) return 0; } -/** The name of the environment variable containing the UCM directory */ -#define ALSA_CONFIG_UCM_VAR "ALSA_CONFIG_UCM" - static int load_master_config(const char *card_name, snd_config_t **cfg) { char filename[MAX_FILE]; @@ -992,18 +1041,19 @@ static int filename_filter(const struct dirent *dirent) /* scan all cards and comments */ int uc_mgr_scan_master_configs(const char **_list[]) { - char filename[MAX_FILE]; + 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'; - err = scandir(filename, &namelist, filename_filter, alphasort); + err = scandir(filename, &namelist, filename_filter, versionsort); if (err < 0) { err = -errno; uc_error("error: could not scan directory %s: %s", @@ -1012,6 +1062,20 @@ int uc_mgr_scan_master_configs(const char **_list[]) } 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; @@ -1036,6 +1100,14 @@ int uc_mgr_scan_master_configs(const char **_list[]) 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; diff --git a/src/ucm/ucm_local.h b/src/ucm/ucm_local.h index 7dae5b6a..0515de17 100644 --- a/src/ucm/ucm_local.h +++ b/src/ucm/ucm_local.h @@ -196,5 +196,6 @@ int uc_mgr_import_master_config(snd_use_case_mgr_t *uc_mgr); int uc_mgr_scan_master_configs(const char **_list[]); void uc_mgr_free_sequence_element(struct sequence_element *seq); +void uc_mgr_free_transition_element(struct transition_sequence *seq); void uc_mgr_free_verb(snd_use_case_mgr_t *uc_mgr); void uc_mgr_free(snd_use_case_mgr_t *uc_mgr); diff --git a/src/ucm/utils.c b/src/ucm/utils.c index 0f8207e8..267a13c9 100644 --- a/src/ucm/utils.c +++ b/src/ucm/utils.c @@ -94,7 +94,8 @@ void uc_mgr_free_value(struct list_head *base) val = list_entry(pos, struct ucm_value, list); free(val->name); free(val->data); - list_del(pos); + list_del(&val->list); + free(val); } } @@ -106,8 +107,8 @@ void uc_mgr_free_dev_list(struct list_head *base) list_for_each_safe(pos, npos, base) { dlist = list_entry(pos, struct dev_list, list); free(dlist->name); + list_del(&dlist->list); free(dlist); - list_del(pos); } } @@ -133,11 +134,18 @@ void uc_mgr_free_sequence(struct list_head *base) list_for_each_safe(pos, npos, base) { seq = list_entry(pos, struct sequence_element, list); + list_del(&seq->list); uc_mgr_free_sequence_element(seq); - list_del(pos); } } +void uc_mgr_free_transition_element(struct transition_sequence *tseq) +{ + free(tseq->name); + uc_mgr_free_sequence(&tseq->transition_list); + free(tseq); +} + void uc_mgr_free_transition(struct list_head *base) { struct list_head *pos, *npos; @@ -145,10 +153,8 @@ void uc_mgr_free_transition(struct list_head *base) list_for_each_safe(pos, npos, base) { tseq = list_entry(pos, struct transition_sequence, list); - free(tseq->name); - uc_mgr_free_sequence(&tseq->transition_list); - free(tseq); - list_del(pos); + list_del(&tseq->list); + uc_mgr_free_transition_element(tseq); } } @@ -166,8 +172,8 @@ void uc_mgr_free_modifier(struct list_head *base) uc_mgr_free_transition(&mod->transition_list); uc_mgr_free_dev_list(&mod->dev_list); uc_mgr_free_value(&mod->value_list); + list_del(&mod->list); free(mod); - list_del(pos); } } @@ -184,8 +190,8 @@ void uc_mgr_free_device(struct list_head *base) uc_mgr_free_sequence(&dev->disable_list); uc_mgr_free_transition(&dev->transition_list); uc_mgr_free_value(&dev->value_list); + list_del(&dev->list); free(dev); - list_del(pos); } } @@ -204,8 +210,8 @@ void uc_mgr_free_verb(snd_use_case_mgr_t *uc_mgr) uc_mgr_free_value(&verb->value_list); uc_mgr_free_device(&verb->device_list); uc_mgr_free_modifier(&verb->modifier_list); + list_del(&verb->list); free(verb); - list_del(pos); } uc_mgr_free_sequence(&uc_mgr->default_list); free(uc_mgr->comment); diff --git a/test/ucm/TestHDA/Case1.conf b/test/ucm/TestHDA/Case1.conf new file mode 100644 index 00000000..65af2600 --- /dev/null +++ b/test/ucm/TestHDA/Case1.conf @@ -0,0 +1,24 @@ +SectionVerb { + EnableSequence [ + exec "Case1 enable seq" + ] + DisableSequence [ + exec "Case2 disable seq" + ] + TransitionVerb."Case2" [ + exec "Case1->Case2 transition seq" + ] + Value { + TestValue1 "123" + } +} + +SectionDevice."Device1".0 { + +} + +SectionModifier."Modifier1" { + SupportedDevice [ + "Device1" + ] +} diff --git a/test/ucm/TestHDA/TestHDA.conf b/test/ucm/TestHDA/TestHDA.conf new file mode 100644 index 00000000..9b93b96e --- /dev/null +++ b/test/ucm/TestHDA/TestHDA.conf @@ -0,0 +1,11 @@ +Comment "A test HDA card" + +SectionUseCase."Case1" { + File "Case1.conf" + Comment "Case1 Comment" +} + +SectionDefaults [ + exec "my prg" + msleep 1 +] From 9b6df1cf64ea7ccb329b4922d138c1f36ace00c0 Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Thu, 4 Nov 2010 19:58:44 +0100 Subject: [PATCH 12/30] control: add ASCII parsers from amixer Signed-off-by: Jaroslav Kysela --- include/control.h | 7 + src/control/Makefile.am | 3 +- src/control/ctlparse.c | 351 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 360 insertions(+), 1 deletion(-) create mode 100644 src/control/ctlparse.c diff --git a/include/control.h b/include/control.h index 3d6b0a5f..e8f38bb4 100644 --- a/include/control.h +++ b/include/control.h @@ -284,6 +284,13 @@ unsigned int snd_ctl_event_elem_get_index(const snd_ctl_event_t *obj); int snd_ctl_elem_list_alloc_space(snd_ctl_elem_list_t *obj, unsigned int entries); void snd_ctl_elem_list_free_space(snd_ctl_elem_list_t *obj); +char *snd_ctl_ascii_elem_id_get(snd_ctl_elem_id_t *id); +int snd_ctl_ascii_elem_id_parse(snd_ctl_elem_id_t *dst, const char *str); +int snd_ctl_ascii_value_parse(snd_ctl_t *handle, + snd_ctl_elem_value_t *dst, + snd_ctl_elem_info_t *info, + const char *value); + size_t snd_ctl_elem_id_sizeof(void); /** \hideinitializer * \brief allocate an invalid #snd_ctl_elem_id_t using standard alloca diff --git a/src/control/Makefile.am b/src/control/Makefile.am index d4c50c01..8076c732 100644 --- a/src/control/Makefile.am +++ b/src/control/Makefile.am @@ -1,7 +1,8 @@ EXTRA_LTLIBRARIES = libcontrol.la libcontrol_la_SOURCES = cards.c tlv.c namehint.c hcontrol.c \ - control.c control_hw.c setup.c control_symbols.c + control.c control_hw.c setup.c ctlparse.c \ + control_symbols.c if BUILD_CTL_PLUGIN_SHM libcontrol_la_SOURCES += control_shm.c endif diff --git a/src/control/ctlparse.c b/src/control/ctlparse.c new file mode 100644 index 00000000..c8300f3b --- /dev/null +++ b/src/control/ctlparse.c @@ -0,0 +1,351 @@ +/** + * \file control/control.c + * \brief CTL interface - parse ASCII identifiers and values + * \author Jaroslav Kysela + * \date 2010 + */ +/* + * Control Interface - ASCII parser + * Copyright (c) 2010 by Jaroslav Kysela + * + * + * 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.1 of + * the License, or (at your option) any later version. + * + * This program 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 + * + */ + +#include +#include +#include +#include +#include "control_local.h" + +/* Function to convert from percentage to volume. val = percentage */ + +#define convert_prange1(val, min, max) \ + ceil((val) * ((max) - (min)) * 0.01 + (min)) + +#define check_range(val, min, max) \ + ((val < min) ? (min) : ((val > max) ? (max) : (val))) + +static long get_integer(const char **ptr, long min, long max) +{ + long val = min; + char *p = (char *)*ptr, *s; + + if (*p == ':') + p++; + if (*p == '\0' || (!isdigit(*p) && *p != '-')) + goto out; + + s = p; + val = strtol(s, &p, 10); + if (*p == '.') { + p++; + strtol(p, &p, 10); + } + if (*p == '%') { + val = (long)convert_prange1(strtod(s, NULL), min, max); + p++; + } + val = check_range(val, min, max); + if (*p == ',') + p++; + out: + *ptr = p; + return val; +} + +static long long get_integer64(const char **ptr, long long min, long long max) +{ + long long val = min; + char *p = (char *)*ptr, *s; + + if (*p == ':') + p++; + if (*p == '\0' || (!isdigit(*p) && *p != '-')) + goto out; + + s = p; + val = strtol(s, &p, 10); + if (*p == '.') { + p++; + strtol(p, &p, 10); + } + if (*p == '%') { + val = (long long)convert_prange1(strtod(s, NULL), min, max); + p++; + } + val = check_range(val, min, max); + if (*p == ',') + p++; + out: + *ptr = p; + return val; +} + +/** + * \brief return ASCII CTL element identifier name + * \param id CTL identifier + * \return ascii identifier of CTL element + * + * The string is allocated using strdup(). + */ +char *snd_ctl_ascii_elem_id_get(snd_ctl_elem_id_t *id) +{ + unsigned int index, device, subdevice; + char buf[256], buf1[32]; + + snprintf(buf, sizeof(buf), "numid=%u,iface=%s,name='%s'", + snd_ctl_elem_id_get_numid(id), + snd_ctl_elem_iface_name( + snd_ctl_elem_id_get_interface(id)), + snd_ctl_elem_id_get_name(id)); + buf[sizeof(buf)-1] = '\0'; + index = snd_ctl_elem_id_get_index(id); + device = snd_ctl_elem_id_get_device(id); + subdevice = snd_ctl_elem_id_get_subdevice(id); + if (index) { + snprintf(buf1, sizeof(buf1), ",index=%i", index); + if (strlen(buf) + strlen(buf1) < sizeof(buf)) + strcat(buf, buf1); + } + if (device) { + snprintf(buf1, sizeof(buf1), ",device=%i", device); + if (strlen(buf) + strlen(buf1) < sizeof(buf)) + strcat(buf, buf1); + } + if (subdevice) { + snprintf(buf1, sizeof(buf1), ",subdevice=%i", subdevice); + if (strlen(buf) + strlen(buf1) < sizeof(buf)) + strcat(buf, buf1); + } + return strdup(buf); +} + +/** + * \brief parse ASCII string as CTL element identifier + * \param dst destination CTL identifier + * \param str source ASCII string + * \return zero on success, otherwise a negative error code + */ +int snd_ctl_ascii_elem_id_parse(snd_ctl_elem_id_t *dst, const char *str) +{ + int c, size, numid; + char *ptr; + + while (*str == ' ' || *str == '\t') + str++; + if (!(*str)) + return -EINVAL; + snd_ctl_elem_id_set_interface(dst, SND_CTL_ELEM_IFACE_MIXER); /* default */ + while (*str) { + if (!strncasecmp(str, "numid=", 6)) { + str += 6; + numid = atoi(str); + if (numid <= 0) { + fprintf(stderr, "amixer: Invalid numid %d\n", numid); + return -EINVAL; + } + snd_ctl_elem_id_set_numid(dst, atoi(str)); + while (isdigit(*str)) + str++; + } else if (!strncasecmp(str, "iface=", 6)) { + str += 6; + if (!strncasecmp(str, "card", 4)) { + snd_ctl_elem_id_set_interface(dst, SND_CTL_ELEM_IFACE_CARD); + str += 4; + } else if (!strncasecmp(str, "mixer", 5)) { + snd_ctl_elem_id_set_interface(dst, SND_CTL_ELEM_IFACE_MIXER); + str += 5; + } else if (!strncasecmp(str, "pcm", 3)) { + snd_ctl_elem_id_set_interface(dst, SND_CTL_ELEM_IFACE_PCM); + str += 3; + } else if (!strncasecmp(str, "rawmidi", 7)) { + snd_ctl_elem_id_set_interface(dst, SND_CTL_ELEM_IFACE_RAWMIDI); + str += 7; + } else if (!strncasecmp(str, "timer", 5)) { + snd_ctl_elem_id_set_interface(dst, SND_CTL_ELEM_IFACE_TIMER); + str += 5; + } else if (!strncasecmp(str, "sequencer", 9)) { + snd_ctl_elem_id_set_interface(dst, SND_CTL_ELEM_IFACE_SEQUENCER); + str += 9; + } else { + return -EINVAL; + } + } else if (!strncasecmp(str, "name=", 5)) { + char buf[64]; + str += 5; + ptr = buf; + size = 0; + if (*str == '\'' || *str == '\"') { + c = *str++; + while (*str && *str != c) { + if (size < (int)sizeof(buf)) { + *ptr++ = *str; + size++; + } + str++; + } + if (*str == c) + str++; + } else { + while (*str && *str != ',') { + if (size < (int)sizeof(buf)) { + *ptr++ = *str; + size++; + } + str++; + } + *ptr = '\0'; + } + snd_ctl_elem_id_set_name(dst, buf); + } else if (!strncasecmp(str, "index=", 6)) { + str += 6; + snd_ctl_elem_id_set_index(dst, atoi(str)); + while (isdigit(*str)) + str++; + } else if (!strncasecmp(str, "device=", 7)) { + str += 7; + snd_ctl_elem_id_set_device(dst, atoi(str)); + while (isdigit(*str)) + str++; + } else if (!strncasecmp(str, "subdevice=", 10)) { + str += 10; + snd_ctl_elem_id_set_subdevice(dst, atoi(str)); + while (isdigit(*str)) + str++; + } + if (*str == ',') { + str++; + } else { + if (*str) + return -EINVAL; + } + } + return 0; +} + +static int get_ctl_enum_item_index(snd_ctl_t *handle, + snd_ctl_elem_info_t *info, + const char **ptrp) +{ + char *ptr = (char *)*ptrp; + int items, i, len; + const char *name; + + items = snd_ctl_elem_info_get_items(info); + if (items <= 0) + return -1; + + for (i = 0; i < items; i++) { + snd_ctl_elem_info_set_item(info, i); + if (snd_ctl_elem_info(handle, info) < 0) + return -1; + name = snd_ctl_elem_info_get_item_name(info); + len = strlen(name); + if (! strncmp(name, ptr, len)) { + if (! ptr[len] || ptr[len] == ',' || ptr[len] == '\n') { + ptr += len; + *ptrp = ptr; + return i; + } + } + } + return -1; +} + +/** + * \brief parse ASCII string as CTL element value + * \param dst destination CTL element value + * \param info CTL element info structure + * \param value source ASCII string + * \return zero on success, otherwise a negative error code + */ +int snd_ctl_ascii_value_parse(snd_ctl_t *handle, + snd_ctl_elem_value_t *dst, + snd_ctl_elem_info_t *info, + const char *value) +{ + const char *ptr = value; + snd_ctl_elem_id_t *myid; + snd_ctl_elem_type_t type; + unsigned int idx, count; + long tmp; + long long tmp64; + + snd_ctl_elem_id_alloca(&myid); + snd_ctl_elem_info_get_id(info, myid); + type = snd_ctl_elem_info_get_type(info); + count = snd_ctl_elem_info_get_count(info); + snd_ctl_elem_value_set_id(dst, myid); + + for (idx = 0; idx < count && idx < 128 && ptr && *ptr; idx++) { + switch (type) { + case SND_CTL_ELEM_TYPE_BOOLEAN: + tmp = 0; + if (!strncasecmp(ptr, "on", 2) || + !strncasecmp(ptr, "up", 2)) { + tmp = 1; + ptr += 2; + } else if (!strncasecmp(ptr, "yes", 3)) { + tmp = 1; + ptr += 3; + } else if (!strncasecmp(ptr, "toggle", 6)) { + tmp = snd_ctl_elem_value_get_boolean(dst, idx); + tmp = tmp > 0 ? 0 : 1; + ptr += 6; + } else if (isdigit(*ptr)) { + tmp = atoi(ptr) > 0 ? 1 : 0; + while (isdigit(*ptr)) + ptr++; + } else { + while (*ptr && *ptr != ',') + ptr++; + } + snd_ctl_elem_value_set_boolean(dst, idx, tmp); + break; + case SND_CTL_ELEM_TYPE_INTEGER: + tmp = get_integer(&ptr, + snd_ctl_elem_info_get_min(info), + snd_ctl_elem_info_get_max(info)); + snd_ctl_elem_value_set_integer(dst, idx, tmp); + break; + case SND_CTL_ELEM_TYPE_INTEGER64: + tmp64 = get_integer64(&ptr, + snd_ctl_elem_info_get_min64(info), + snd_ctl_elem_info_get_max64(info)); + snd_ctl_elem_value_set_integer64(dst, idx, tmp64); + break; + case SND_CTL_ELEM_TYPE_ENUMERATED: + tmp = get_ctl_enum_item_index(handle, info, &ptr); + if (tmp < 0) + tmp = get_integer(&ptr, 0, + snd_ctl_elem_info_get_items(info) - 1); + snd_ctl_elem_value_set_enumerated(dst, idx, tmp); + break; + case SND_CTL_ELEM_TYPE_BYTES: + tmp = get_integer(&ptr, 0, 255); + snd_ctl_elem_value_set_byte(dst, idx, tmp); + break; + default: + break; + } + if (!strchr(value, ',')) + ptr = value; + else if (*ptr == ',') + ptr++; + } + return 0; +} From cdc9dd50bf37cc01782c9eb50dc7d217c4b79d7f Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Wed, 10 Nov 2010 16:06:29 +0100 Subject: [PATCH 13/30] ucm: fix parser for sequences and fix wrong strcmp The sequences are not parsed correctly. First cfg value is the command and second value is the command argument. Also, fix strcmp calls in ucm/main.c (reported by abraham duenas ). Signed-off-by: Jaroslav Kysela --- src/ucm/main.c | 4 ++-- src/ucm/parser.c | 18 ++++++++++++++---- test/ucm/TestHDA/Case1.conf | 1 + 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/ucm/main.c b/src/ucm/main.c index e233f414..a73595d8 100644 --- a/src/ucm/main.c +++ b/src/ucm/main.c @@ -774,9 +774,9 @@ int snd_use_case_get_list(snd_use_case_mgr_t *uc_mgr, pthread_mutex_lock(&uc_mgr->mutex); if (strcmp(identifier, "_verbs") == 0) err = get_verb_list(uc_mgr, list); - else if (strcmp(identifier, "_enadevs")) + else if (strcmp(identifier, "_enadevs") == 0) err = get_enabled_device_list(uc_mgr, list); - else if (strcmp(identifier, "_enamods")) + else if (strcmp(identifier, "_enamods") == 0) err = get_enabled_modifier_list(uc_mgr, list); else { str1 = strchr(identifier, '/'); diff --git a/src/ucm/parser.c b/src/ucm/parser.c index 1b5a4310..69a84545 100644 --- a/src/ucm/parser.c +++ b/src/ucm/parser.c @@ -203,7 +203,8 @@ static int parse_sequence(snd_use_case_mgr_t *uc_mgr ATTRIBUTE_UNUSED, struct sequence_element *curr; snd_config_iterator_t i, next; snd_config_t *n; - int err; + 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"); @@ -212,10 +213,19 @@ static int parse_sequence(snd_use_case_mgr_t *uc_mgr ATTRIBUTE_UNUSED, 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)); @@ -223,7 +233,7 @@ static int parse_sequence(snd_use_case_mgr_t *uc_mgr ATTRIBUTE_UNUSED, return -ENOMEM; list_add_tail(&curr->list, base); - if (strcmp(id, "cset") == 0) { + if (strcmp(cmd, "cset") == 0) { curr->type = SEQUENCE_ELEMENT_TYPE_CSET; err = parse_string(n, &curr->data.cset); if (err < 0) { @@ -233,7 +243,7 @@ static int parse_sequence(snd_use_case_mgr_t *uc_mgr ATTRIBUTE_UNUSED, continue; } - if (strcmp(id, "usleep") == 0) { + if (strcmp(cmd, "usleep") == 0) { curr->type = SEQUENCE_ELEMENT_TYPE_SLEEP; err = snd_config_get_integer(n, &curr->data.sleep); if (err < 0) { @@ -243,7 +253,7 @@ static int parse_sequence(snd_use_case_mgr_t *uc_mgr ATTRIBUTE_UNUSED, continue; } - if (strcmp(id, "exec") == 0) { + if (strcmp(cmd, "exec") == 0) { curr->type = SEQUENCE_ELEMENT_TYPE_EXEC; err = parse_string(n, &curr->data.exec); if (err < 0) { diff --git a/test/ucm/TestHDA/Case1.conf b/test/ucm/TestHDA/Case1.conf index 65af2600..3d8cddf8 100644 --- a/test/ucm/TestHDA/Case1.conf +++ b/test/ucm/TestHDA/Case1.conf @@ -1,6 +1,7 @@ SectionVerb { EnableSequence [ exec "Case1 enable seq" + exec "Case1 enable seq 2" ] DisableSequence [ exec "Case2 disable seq" From aaf55f16414eb81e8e805f669182f280496b41dd Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Tue, 23 Nov 2010 15:58:14 +0100 Subject: [PATCH 14/30] ucm: add cdev Signed-off-by: Jaroslav Kysela --- src/ucm/main.c | 3 +++ src/ucm/parser.c | 11 +++++++++++ src/ucm/ucm_local.h | 8 +++++--- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/ucm/main.c b/src/ucm/main.c index a73595d8..9f950e42 100644 --- a/src/ucm/main.c +++ b/src/ucm/main.c @@ -133,6 +133,9 @@ static int execute_sequence(snd_use_case_mgr_t *uc_mgr ATTRIBUTE_UNUSED, list_for_each(pos, seq) { s = list_entry(pos, struct sequence_element, list); switch (s->type) { + case SEQUENCE_ELEMENT_TYPE_CDEV: + uc_error("cdev not yet implemented: '%s'", s->data.cdev); + break; case SEQUENCE_ELEMENT_TYPE_CSET: uc_error("cset not yet implemented: '%s'", s->data.cset); break; diff --git a/src/ucm/parser.c b/src/ucm/parser.c index 69a84545..2407d399 100644 --- a/src/ucm/parser.c +++ b/src/ucm/parser.c @@ -188,6 +188,7 @@ static int parse_supported_device(snd_use_case_mgr_t *uc_mgr ATTRIBUTE_UNUSED, * * Sequence controls elements are in the following form:- * + * cdev "hw:0,0" * cset "element_id_syntax value_syntax" * usleep time * exec "any unix command with arguments" @@ -233,6 +234,16 @@ static int parse_sequence(snd_use_case_mgr_t *uc_mgr ATTRIBUTE_UNUSED, 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); diff --git a/src/ucm/ucm_local.h b/src/ucm/ucm_local.h index 0515de17..479cc976 100644 --- a/src/ucm/ucm_local.h +++ b/src/ucm/ucm_local.h @@ -42,9 +42,10 @@ #define MAX_FILE 256 #define ALSA_USE_CASE_DIR ALSA_CONFIG_DIR "/ucm" -#define SEQUENCE_ELEMENT_TYPE_CSET 1 -#define SEQUENCE_ELEMENT_TYPE_SLEEP 2 -#define SEQUENCE_ELEMENT_TYPE_EXEC 3 +#define SEQUENCE_ELEMENT_TYPE_CDEV 1 +#define SEQUENCE_ELEMENT_TYPE_CSET 2 +#define SEQUENCE_ELEMENT_TYPE_SLEEP 3 +#define SEQUENCE_ELEMENT_TYPE_EXEC 4 struct ucm_value { struct list_head list; @@ -57,6 +58,7 @@ struct sequence_element { unsigned int type; union { long sleep; /* Sleep time in msecs if sleep element, else 0 */ + char *cdev; char *cset; char *exec; } data; From d8b78161966972c10415bb8eaea34533d0d7b279 Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Mon, 29 Nov 2010 15:41:34 +0100 Subject: [PATCH 15/30] ucm: add ValueDefaults section to the master file - the get_value() function is recoded (tries to find the value in parent's list) Signed-off-by: Jaroslav Kysela --- src/ucm/main.c | 39 ++++++++++++++++++++++++++------------- src/ucm/parser.c | 42 ++++++++++++++++++++++++++++++------------ src/ucm/ucm_local.h | 3 +++ src/ucm/utils.c | 1 + 4 files changed, 60 insertions(+), 25 deletions(-) diff --git a/src/ucm/main.c b/src/ucm/main.c index 9f950e42..9ba5c744 100644 --- a/src/ucm/main.c +++ b/src/ucm/main.c @@ -445,6 +445,7 @@ int snd_use_case_mgr_open(snd_use_case_mgr_t **mgr, return -ENOMEM; INIT_LIST_HEAD(&uc_mgr->verb_list); INIT_LIST_HEAD(&uc_mgr->default_list); + INIT_LIST_HEAD(&uc_mgr->value_list); pthread_mutex_init(&uc_mgr->mutex, NULL); uc_mgr->card_name = strdup(card_name); @@ -688,6 +689,9 @@ static int get_value_list(snd_use_case_mgr_t *uc_mgr, if (verb == NULL) return -ENOENT; INIT_LIST_HEAD(&mylist); + err = add_values(&mylist, identifier, &uc_mgr->value_list); + if (err < 0) + goto __fail; err = add_values(&mylist, identifier, &verb->value_list); if (err < 0) goto __fail; @@ -821,14 +825,16 @@ static int get_value1(const char **value, struct list_head *value_list, return 0; } } - return 0; + return -ENOENT; } /** * \brief Get value - * \param list Returned list - * \param verbname For verb (NULL = current) - * \return Number of list entries if success, otherwise a negative error code + * \param uc_mgr Use case manager + * \param identifier Value identifier (string) + * \param value Returned value string + * \param modifier modifier name (string) + * \return Zero on success (value is filled), otherwise a negative error code */ static int get_value(snd_use_case_mgr_t *uc_mgr, const char *identifier, @@ -836,16 +842,23 @@ static int get_value(snd_use_case_mgr_t *uc_mgr, const char *modifier) { struct use_case_modifier *mod; + int err; - if (uc_mgr->active_verb == NULL) - return -ENOENT; - if (modifier == NULL) - return get_value1(value, &uc_mgr->active_verb->value_list, - identifier); - mod = find_modifier(uc_mgr->active_verb, modifier); - if (mod == NULL) - return -EINVAL; - return get_value1(value, &mod->value_list, identifier); + if (modifier != NULL) { + mod = find_modifier(uc_mgr->active_verb, modifier); + if (mod != NULL) { + err = get_value1(value, &mod->value_list, identifier); + if (err >= 0 || err != -ENOENT) + return err; + } + } + err = get_value1(value, &uc_mgr->active_verb->value_list, identifier); + if (err >= 0 || err != -ENOENT) + return err; + err = get_value1(value, &uc_mgr->value_list, identifier); + if (err >= 0 || err != -ENOENT) + return err; + return -ENOENT; } /** diff --git a/src/ucm/parser.c b/src/ucm/parser.c index 2407d399..b85ac0a2 100644 --- a/src/ucm/parser.c +++ b/src/ucm/parser.c @@ -930,26 +930,33 @@ static int parse_controls(snd_use_case_mgr_t *uc_mgr, snd_config_t *cfg) * # 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." + * File "voice_call_blah" + * Comment "Make a voice phone call." * } * * SectionUseCase."HiFi" { - * File "hifi_blah" - * Comment "Play and record HiFi quality Music." + * File "hifi_blah" + * Comment "Play and record HiFi quality Music." + * } + * + * # Define Value defaults + * + * ValueDefaults { + * PlaybackCTL "hw:CARD=0,DEV=0" + * CaptureCTL "hw:CARD=0,DEV=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 - * ........ + * 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. @@ -999,6 +1006,17 @@ static int parse_master_file(snd_use_case_mgr_t *uc_mgr, snd_config_t *cfg) 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; diff --git a/src/ucm/ucm_local.h b/src/ucm/ucm_local.h index 479cc976..3060f960 100644 --- a/src/ucm/ucm_local.h +++ b/src/ucm/ucm_local.h @@ -173,6 +173,9 @@ struct snd_use_case_mgr { /* default settings - sequence */ struct list_head default_list; + /* default settings - value list */ + struct list_head value_list; + /* current status */ struct use_case_verb *active_verb; struct list_head active_devices; diff --git a/src/ucm/utils.c b/src/ucm/utils.c index 267a13c9..8de2de41 100644 --- a/src/ucm/utils.c +++ b/src/ucm/utils.c @@ -214,6 +214,7 @@ void uc_mgr_free_verb(snd_use_case_mgr_t *uc_mgr) free(verb); } uc_mgr_free_sequence(&uc_mgr->default_list); + uc_mgr_free_value(&uc_mgr->value_list); free(uc_mgr->comment); uc_mgr->comment = NULL; uc_mgr->active_verb = NULL; From c0469e2a2835f4881c54c910bdca5498cefe2170 Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Mon, 29 Nov 2010 15:49:13 +0100 Subject: [PATCH 16/30] ucm: CTL devices are only one per card (remove DEV from comments) Signed-off-by: Jaroslav Kysela --- src/ucm/parser.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ucm/parser.c b/src/ucm/parser.c index b85ac0a2..3d2fd717 100644 --- a/src/ucm/parser.c +++ b/src/ucm/parser.c @@ -188,7 +188,7 @@ static int parse_supported_device(snd_use_case_mgr_t *uc_mgr ATTRIBUTE_UNUSED, * * Sequence controls elements are in the following form:- * - * cdev "hw:0,0" + * cdev "hw:0" * cset "element_id_syntax value_syntax" * usleep time * exec "any unix command with arguments" @@ -942,8 +942,8 @@ static int parse_controls(snd_use_case_mgr_t *uc_mgr, snd_config_t *cfg) * # Define Value defaults * * ValueDefaults { - * PlaybackCTL "hw:CARD=0,DEV=0" - * CaptureCTL "hw:CARD=0,DEV=0" + * PlaybackCTL "hw:CARD=0" + * CaptureCTL "hw:CARD=0" * } * * # This file also stores the default sound card state. From 7d3de218ce967c46c76b80ed8ee4380c4adcdb90 Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Mon, 29 Nov 2010 17:33:27 +0100 Subject: [PATCH 17/30] ucm: initial implementation for cdev/cset Signed-off-by: Jaroslav Kysela --- src/ucm/main.c | 196 +++++++++++++++++++++++++++++++++++++++++--- src/ucm/ucm_local.h | 4 + src/ucm/utils.c | 6 ++ 3 files changed, 193 insertions(+), 13 deletions(-) diff --git a/src/ucm/main.c b/src/ucm/main.c index 9ba5c744..ecf07f93 100644 --- a/src/ucm/main.c +++ b/src/ucm/main.c @@ -38,6 +38,14 @@ * misc */ +static int get_value1(const char **value, struct list_head *value_list, + const char *identifier); +static int get_value3(const char **value, + const char *identifier, + struct list_head *value_list1, + struct list_head *value_list2, + struct list_head *value_list3); + static int check_identifier(const char *identifier, const char *prefix) { int len; @@ -118,26 +126,140 @@ int snd_use_case_free_list(const char *list[], int items) return 0; } +static int open_ctl(snd_use_case_mgr_t *uc_mgr, + snd_ctl_t **ctl, + const char *ctl_dev) +{ + int err; + + /* FIXME: add a list of ctl devices to uc_mgr structure and + cache accesses for multiple opened ctl devices */ + if (uc_mgr->ctl_dev != NULL && strcmp(ctl_dev, uc_mgr->ctl_dev) == 0) { + *ctl = uc_mgr->ctl; + return 0; + } + if (uc_mgr->ctl_dev) { + free(uc_mgr->ctl_dev); + uc_mgr->ctl_dev = NULL; + snd_ctl_close(uc_mgr->ctl); + + } + err = snd_ctl_open(ctl, ctl_dev, 0); + if (err < 0) + return err; + uc_mgr->ctl_dev = strdup(ctl_dev); + if (uc_mgr->ctl_dev == NULL) { + snd_ctl_close(*ctl); + return -ENOMEM; + } + uc_mgr->ctl = *ctl; + return 0; +} + +static int execute_cset(snd_ctl_t *ctl, char *cset) +{ + char *pos; + int err; + snd_ctl_elem_id_t *id; + snd_ctl_elem_value_t *value; + snd_ctl_elem_info_t *info; + + snd_ctl_elem_id_malloc(&id); + snd_ctl_elem_value_malloc(&value); + snd_ctl_elem_info_malloc(&info); + + pos = strchr(cset, ' '); + if (pos == NULL) { + uc_error("undefined value for cset >%s<", cset); + return -EINVAL; + } + *pos = '\0'; + err = snd_ctl_ascii_elem_id_parse(id, cset); + if (err < 0) + goto __fail; + snd_ctl_elem_value_set_id(value, id); + snd_ctl_elem_info_set_id(info, id); + err = snd_ctl_elem_read(ctl, value); + if (err < 0) + goto __fail; + err = snd_ctl_elem_info(ctl, info); + if (err < 0) + goto __fail; + err = snd_ctl_ascii_value_parse(ctl, value, info, pos + 1); + if (err < 0) + goto __fail; + err = snd_ctl_elem_write(ctl, value); + if (err < 0) + goto __fail; + err = 0; + __fail: + *pos = ' '; + return err; +} + /** * \brief Execute the sequence * \param uc_mgr Use case manager * \param seq Sequence * \return zero on success, otherwise a negative error code */ -static int execute_sequence(snd_use_case_mgr_t *uc_mgr ATTRIBUTE_UNUSED, - struct list_head *seq) +static int execute_sequence(snd_use_case_mgr_t *uc_mgr, + struct list_head *seq, + struct list_head *value_list1, + struct list_head *value_list2, + struct list_head *value_list3) { struct list_head *pos; struct sequence_element *s; + char *cdev = NULL; + snd_ctl_t *ctl = NULL; + int err = 0; list_for_each(pos, seq) { s = list_entry(pos, struct sequence_element, list); switch (s->type) { case SEQUENCE_ELEMENT_TYPE_CDEV: - uc_error("cdev not yet implemented: '%s'", s->data.cdev); + cdev = strdup(s->data.cdev); + if (cdev == NULL) + goto __fail_nomem; break; case SEQUENCE_ELEMENT_TYPE_CSET: - uc_error("cset not yet implemented: '%s'", s->data.cset); + if (cdev == NULL) { + const char *cdev1 = NULL, *cdev2 = NULL; + err = get_value3(&cdev1, "PlaybackCTL", + value_list1, + value_list2, + value_list3); + if (err < 0 && err != ENOENT) { + uc_error("cdev is not defined!"); + return err; + } + err = get_value3(&cdev1, "CaptureCTL", + value_list1, + value_list2, + value_list3); + if (err < 0 && err != ENOENT) { + free((char *)cdev1); + uc_error("cdev is not defined!"); + return err; + } + if (cdev1 == NULL || cdev2 == NULL || + strcmp(cdev1, cdev2) == 0) { + cdev = (char *)cdev1; + free((char *)cdev2); + } else { + free((char *)cdev1); + free((char *)cdev2); + } + } + if (ctl == NULL) { + err = open_ctl(uc_mgr, &ctl, cdev); + if (err < 0) + goto __fail; + } + err = execute_cset(ctl, s->data.cset); + if (err < 0) + goto __fail; break; case SEQUENCE_ELEMENT_TYPE_SLEEP: usleep(s->data.sleep); @@ -150,7 +272,14 @@ static int execute_sequence(snd_use_case_mgr_t *uc_mgr ATTRIBUTE_UNUSED, break; } } + free(cdev); return 0; + __fail_nomem: + err = -ENOMEM; + __fail: + free(cdev); + return err; + } /** @@ -165,7 +294,8 @@ static int import_master_config(snd_use_case_mgr_t *uc_mgr) err = uc_mgr_import_master_config(uc_mgr); if (err < 0) return err; - err = execute_sequence(uc_mgr, &uc_mgr->default_list); + err = execute_sequence(uc_mgr, &uc_mgr->default_list, + &uc_mgr->value_list, NULL, NULL); if (err < 0) uc_error("Unable to execute default sequence"); return err; @@ -365,7 +495,10 @@ static int set_verb(snd_use_case_mgr_t *uc_mgr, } else { seq = &verb->disable_list; } - err = execute_sequence(uc_mgr, seq); + err = execute_sequence(uc_mgr, seq, + &verb->value_list, + &uc_mgr->value_list, + NULL); if (enable && err >= 0) uc_mgr->active_verb = verb; return err; @@ -390,7 +523,10 @@ static int set_modifier(snd_use_case_mgr_t *uc_mgr, } else { seq = &modifier->disable_list; } - err = execute_sequence(uc_mgr, seq); + err = execute_sequence(uc_mgr, seq, + &modifier->value_list, + &uc_mgr->active_verb->value_list, + &uc_mgr->value_list); if (enable && err >= 0) { list_add_tail(&modifier->active_list, &uc_mgr->active_modifiers); } else if (!enable) { @@ -418,7 +554,10 @@ static int set_device(snd_use_case_mgr_t *uc_mgr, } else { seq = &device->disable_list; } - err = execute_sequence(uc_mgr, seq); + err = execute_sequence(uc_mgr, seq, + &device->value_list, + &uc_mgr->active_verb->value_list, + &uc_mgr->value_list); if (enable && err >= 0) { list_add_tail(&device->active_list, &uc_mgr->active_devices); } else if (!enable) { @@ -542,7 +681,8 @@ static int dismantle_use_case(snd_use_case_mgr_t *uc_mgr) } uc_mgr->active_verb = NULL; - err = execute_sequence(uc_mgr, &uc_mgr->default_list); + err = execute_sequence(uc_mgr, &uc_mgr->default_list, + &uc_mgr->value_list, NULL, NULL); return err; } @@ -557,7 +697,8 @@ int snd_use_case_mgr_reset(snd_use_case_mgr_t *uc_mgr) int err; pthread_mutex_lock(&uc_mgr->mutex); - err = execute_sequence(uc_mgr, &uc_mgr->default_list); + err = execute_sequence(uc_mgr, &uc_mgr->default_list, + &uc_mgr->value_list, NULL, NULL); INIT_LIST_HEAD(&uc_mgr->active_modifiers); INIT_LIST_HEAD(&uc_mgr->active_devices); uc_mgr->active_verb = NULL; @@ -828,6 +969,26 @@ static int get_value1(const char **value, struct list_head *value_list, return -ENOENT; } +static int get_value3(const char **value, + const char *identifier, + struct list_head *value_list1, + struct list_head *value_list2, + struct list_head *value_list3) +{ + int err; + + err = get_value1(value, value_list1, identifier); + if (err >= 0 || err != -ENOENT) + return err; + err = get_value1(value, value_list2, identifier); + if (err >= 0 || err != -ENOENT) + return err; + err = get_value1(value, value_list3, identifier); + if (err >= 0 || err != -ENOENT) + return err; + return -ENOENT; +} + /** * \brief Get value * \param uc_mgr Use case manager @@ -1003,7 +1164,10 @@ static int handle_transition_verb(snd_use_case_mgr_t *uc_mgr, list_for_each(pos, &uc_mgr->active_verb->transition_list) { trans = list_entry(pos, struct transition_sequence, list); if (strcmp(trans->name, new_verb->name) == 0) { - err = execute_sequence(uc_mgr, &trans->transition_list); + err = execute_sequence(uc_mgr, &trans->transition_list, + &uc_mgr->active_verb->value_list, + &uc_mgr->value_list, + NULL); if (err >= 0) return 1; return err; @@ -1108,7 +1272,10 @@ static int switch_device(snd_use_case_mgr_t *uc_mgr, list_for_each(pos, &xold->transition_list) { trans = list_entry(pos, struct transition_sequence, list); if (strcmp(trans->name, new_device) == 0) { - err = execute_sequence(uc_mgr, &trans->transition_list); + err = execute_sequence(uc_mgr, &trans->transition_list, + &xold->value_list, + &uc_mgr->active_verb->value_list, + &uc_mgr->value_list); if (err >= 0) { list_del(&xold->active_list); list_add_tail(&xnew->active_list, &uc_mgr->active_devices); @@ -1157,7 +1324,10 @@ static int switch_modifier(snd_use_case_mgr_t *uc_mgr, list_for_each(pos, &xold->transition_list) { trans = list_entry(pos, struct transition_sequence, list); if (strcmp(trans->name, new_modifier) == 0) { - err = execute_sequence(uc_mgr, &trans->transition_list); + err = execute_sequence(uc_mgr, &trans->transition_list, + &xold->value_list, + &uc_mgr->active_verb->value_list, + &uc_mgr->value_list); if (err >= 0) { list_del(&xold->active_list); list_add_tail(&xnew->active_list, &uc_mgr->active_modifiers); diff --git a/src/ucm/ucm_local.h b/src/ucm/ucm_local.h index 3060f960..9f71d42b 100644 --- a/src/ucm/ucm_local.h +++ b/src/ucm/ucm_local.h @@ -183,6 +183,10 @@ struct snd_use_case_mgr { /* locking */ pthread_mutex_t mutex; + + /* change to list of ctl handles */ + snd_ctl_t *ctl; + char *ctl_dev; }; #define uc_error SNDERR diff --git a/src/ucm/utils.c b/src/ucm/utils.c index 8de2de41..2def0b8b 100644 --- a/src/ucm/utils.c +++ b/src/ucm/utils.c @@ -220,6 +220,12 @@ void uc_mgr_free_verb(snd_use_case_mgr_t *uc_mgr) uc_mgr->active_verb = NULL; INIT_LIST_HEAD(&uc_mgr->active_devices); INIT_LIST_HEAD(&uc_mgr->active_modifiers); + if (uc_mgr->ctl != NULL) { + snd_ctl_close(uc_mgr->ctl); + uc_mgr->ctl = NULL; + } + free(uc_mgr->ctl_dev); + uc_mgr->ctl_dev = NULL; } void uc_mgr_free(snd_use_case_mgr_t *uc_mgr) From 54e9639b626a110637910a65020f672efe848675 Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Tue, 21 Dec 2010 23:11:50 +0100 Subject: [PATCH 18/30] ucm: removed unused 'active' member in struct use_case_device Signed-off-by: Liam Girdwood Signed-off-by: Jaroslav Kysela --- src/ucm/ucm_local.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/ucm/ucm_local.h b/src/ucm/ucm_local.h index 9f71d42b..13e82da9 100644 --- a/src/ucm/ucm_local.h +++ b/src/ucm/ucm_local.h @@ -115,8 +115,6 @@ struct use_case_device { struct list_head list; struct list_head active_list; - unsigned int active: 1; - char *name; char *comment; From 868d21cf0a5fb13c04a06df0cee602f40b489b4a Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Tue, 21 Dec 2010 23:11:51 +0100 Subject: [PATCH 19/30] ucm: modifiers names must be compound parse Modifier names must end in a .index to ensure we can support same named modifiers. However the modifier index will be for internal use only and the client will not use the index when setting modifier. The modifier selection for same name modifiers will be based upon supported device. Signed-off-by: Liam Girdwood Signed-off-by: Jaroslav Kysela --- src/ucm/parser.c | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/src/ucm/parser.c b/src/ucm/parser.c index 3d2fd717..7d547f4a 100644 --- a/src/ucm/parser.c +++ b/src/ucm/parser.c @@ -401,10 +401,11 @@ static int parse_value(snd_use_case_mgr_t *uc_mgr ATTRIBUTE_UNUSED, static int parse_modifier(snd_use_case_mgr_t *uc_mgr, snd_config_t *cfg, void *data1, - void *data2 ATTRIBUTE_UNUSED) + void *data2) { struct use_case_verb *verb = data1; struct use_case_modifier *modifier; + char *name = data2; const char *id; snd_config_iterator_t i, next; snd_config_t *n; @@ -423,9 +424,12 @@ static int parse_modifier(snd_use_case_mgr_t *uc_mgr, err = snd_config_get_id(cfg, &id); if (err < 0) return err; - modifier->name = strdup(id); + modifier->name = malloc(strlen(name) + strlen(id) + 2); if (modifier->name == NULL) - return -EINVAL; + return -ENOMEM; + strcpy(modifier->name, name); + strcat(modifier->name, "."); + strcat(modifier->name, id); snd_config_for_each(i, next, cfg) { const char *id; @@ -636,6 +640,21 @@ static int parse_device(snd_use_case_mgr_t *uc_mgr, return parse_compound(uc_mgr, cfg, parse_device_name, verb, NULL); } +static int parse_modifier_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_modifier, + data1, (void *)id); +} + /* * Parse Verb Section * @@ -817,7 +836,7 @@ static int parse_verb_file(snd_use_case_mgr_t *uc_mgr, /* find modifier sections and parse them */ if (strcmp(id, "SectionModifier") == 0) { err = parse_compound(uc_mgr, n, - parse_modifier, verb, NULL); + parse_modifier_name, verb, NULL); if (err < 0) { uc_error("error: %s failed to parse modifier", file); From 8524941f2a1b3dcad2b1ec5edcdb556bd2396da8 Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Tue, 21 Dec 2010 23:11:52 +0100 Subject: [PATCH 20/30] ucm: remove parse_device() and call parse_compound() directly Signed-off-by: Liam Girdwood --- src/ucm/parser.c | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/ucm/parser.c b/src/ucm/parser.c index 7d547f4a..f3a75e6d 100644 --- a/src/ucm/parser.c +++ b/src/ucm/parser.c @@ -633,13 +633,6 @@ static int parse_device_name(snd_use_case_mgr_t *uc_mgr, 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); -} - static int parse_modifier_name(snd_use_case_mgr_t *uc_mgr, snd_config_t *cfg, void *data1, @@ -824,7 +817,8 @@ static int parse_verb_file(snd_use_case_mgr_t *uc_mgr, /* find device sections and parse them */ if (strcmp(id, "SectionDevice") == 0) { - err = parse_device(uc_mgr, verb, n); + err = parse_compound(uc_mgr, n, + parse_device_name, verb, NULL); if (err < 0) { uc_error("error: %s failed to parse device", file); From 02bc123aaefb9173d735919625e2979b1e52d03a Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Tue, 21 Dec 2010 23:11:54 +0100 Subject: [PATCH 21/30] ucm: make sure active modifier and device lists are initialised Signed-off-by: Liam Girdwood --- src/ucm/main.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ucm/main.c b/src/ucm/main.c index ecf07f93..ff201da4 100644 --- a/src/ucm/main.c +++ b/src/ucm/main.c @@ -585,6 +585,8 @@ int snd_use_case_mgr_open(snd_use_case_mgr_t **mgr, INIT_LIST_HEAD(&uc_mgr->verb_list); INIT_LIST_HEAD(&uc_mgr->default_list); INIT_LIST_HEAD(&uc_mgr->value_list); + INIT_LIST_HEAD(&uc_mgr->active_modifiers); + INIT_LIST_HEAD(&uc_mgr->active_devices); pthread_mutex_init(&uc_mgr->mutex, NULL); uc_mgr->card_name = strdup(card_name); From 6cda65ba982eb0bc38d8bdab048e4a583dcd8037 Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Tue, 21 Dec 2010 23:11:55 +0100 Subject: [PATCH 22/30] ucm: remove unused code. Signed-off-by: Liam Girdwood --- src/ucm/main.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/ucm/main.c b/src/ucm/main.c index ff201da4..ff7b5e47 100644 --- a/src/ucm/main.c +++ b/src/ucm/main.c @@ -742,7 +742,6 @@ static int get_device_list(snd_use_case_mgr_t *uc_mgr, const char **list[], return get_list2(&verb->device_list, list, struct use_case_device, list, name, comment); - return 0; } /** @@ -766,7 +765,6 @@ static int get_modifier_list(snd_use_case_mgr_t *uc_mgr, const char **list[], return get_list2(&verb->modifier_list, list, struct use_case_modifier, list, name, comment); - return 0; } struct myvalue { From ebb52f69117f9975de46623f5ba32f3c081fe691 Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Tue, 21 Dec 2010 23:11:56 +0100 Subject: [PATCH 23/30] ucm: check for valid value list before dereference. Signed-off-by: Liam Girdwood --- src/ucm/main.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/ucm/main.c b/src/ucm/main.c index ff7b5e47..aafa4839 100644 --- a/src/ucm/main.c +++ b/src/ucm/main.c @@ -957,6 +957,9 @@ static int get_value1(const char **value, struct list_head *value_list, struct ucm_value *val; struct list_head *pos; + if (!value_list) + return -ENOENT; + list_for_each(pos, value_list) { val = list_entry(pos, struct ucm_value, list); if (check_identifier(identifier, val->name)) { From 116fabd6039ed04ee4a65098e936a1691a7b3956 Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Tue, 21 Dec 2010 23:11:57 +0100 Subject: [PATCH 24/30] ucm: check the correct return value for modifier Signed-off-by: Liam Girdwood --- src/ucm/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ucm/main.c b/src/ucm/main.c index aafa4839..a3af3aad 100644 --- a/src/ucm/main.c +++ b/src/ucm/main.c @@ -1321,7 +1321,7 @@ static int switch_modifier(snd_use_case_mgr_t *uc_mgr, if (xold == NULL) return -ENOENT; xnew = find_modifier(uc_mgr->active_verb, new_modifier); - if (xold == NULL) + if (xnew == NULL) return -ENOENT; err = 0; list_for_each(pos, &xold->transition_list) { From 265cf2c3ee70a825d921a1aa621e4dafcc4b0ca1 Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Tue, 21 Dec 2010 23:11:53 +0100 Subject: [PATCH 25/30] ucm: implement basic script exec functionality Signed-off-by: Liam Girdwood --- src/ucm/main.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ucm/main.c b/src/ucm/main.c index a3af3aad..4c13547a 100644 --- a/src/ucm/main.c +++ b/src/ucm/main.c @@ -265,7 +265,9 @@ static int execute_sequence(snd_use_case_mgr_t *uc_mgr, usleep(s->data.sleep); break; case SEQUENCE_ELEMENT_TYPE_EXEC: - uc_error("exec not yet implemented: '%s'", s->data.exec); + err = system(s->data.exec); + if (err < 0) + goto __fail; break; default: uc_error("unknown sequence command %i", s->type); From eba547181963a198f82458d1c7b36561176ae941 Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Tue, 21 Dec 2010 23:11:58 +0100 Subject: [PATCH 26/30] ucm: only select modifier when supported device is enabled Make sure the supported device of a modifier is enabled before we enable the modifier. Signed-off-by: Liam Girdwood --- src/ucm/main.c | 54 ++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 44 insertions(+), 10 deletions(-) diff --git a/src/ucm/main.c b/src/ucm/main.c index 4c13547a..5f44461f 100644 --- a/src/ucm/main.c +++ b/src/ucm/main.c @@ -463,19 +463,52 @@ static inline struct use_case_device * device_name); } +static int is_modifier_supported(snd_use_case_mgr_t *uc_mgr, + struct use_case_modifier *modifier) +{ + struct dev_list *device; + struct list_head *pos; + + list_for_each(pos, &modifier->dev_list) { + device = list_entry(pos, struct dev_list, list); + if (find(&uc_mgr->active_devices, + struct use_case_device, active_list, name, device->name)) + return 1; + + } + return 0; +} + /** * \brief Find modifier * \param verb Use case verb * \param modifier_name modifier to find * \return structure on success, otherwise a NULL (not found) */ -static inline struct use_case_modifier * - find_modifier(struct use_case_verb *verb, - const char *modifier_name) +static struct use_case_modifier * + find_modifier(snd_use_case_mgr_t *uc_mgr, const char *modifier_name) { - return find(&verb->modifier_list, - struct use_case_modifier, list, name, - modifier_name); + struct use_case_modifier *modifier; + struct use_case_verb *verb = uc_mgr->active_verb; + struct list_head *pos; + char name[64], *cpos; + + list_for_each(pos, &verb->modifier_list) { + modifier = list_entry(pos, struct use_case_modifier, list); + + strncpy(name, modifier->name, sizeof(name)); + cpos = strchr(name, '.'); + if (!cpos) + continue; + *cpos= '\0'; + + if (strcmp(name, modifier_name)) + continue; + + if (is_modifier_supported(uc_mgr, modifier)) + return modifier; + } + return NULL; } /** @@ -1011,7 +1044,7 @@ static int get_value(snd_use_case_mgr_t *uc_mgr, int err; if (modifier != NULL) { - mod = find_modifier(uc_mgr->active_verb, modifier); + mod = find_modifier(uc_mgr, modifier); if (mod != NULL) { err = get_value1(value, &mod->value_list, identifier); if (err >= 0 || err != -ENOENT) @@ -1242,7 +1275,8 @@ static int set_modifier_user(snd_use_case_mgr_t *uc_mgr, if (uc_mgr->active_verb == NULL) return -ENOENT; - modifier = find_modifier(uc_mgr->active_verb, modifier_name); + + modifier = find_modifier(uc_mgr, modifier_name); if (modifier == NULL) return -ENOENT; return set_modifier(uc_mgr, modifier, enable); @@ -1319,10 +1353,10 @@ static int switch_modifier(snd_use_case_mgr_t *uc_mgr, uc_error("error: modifier %s already enabled", new_modifier); return -EINVAL; } - xold = find_modifier(uc_mgr->active_verb, old_modifier); + xold = find_modifier(uc_mgr, old_modifier); if (xold == NULL) return -ENOENT; - xnew = find_modifier(uc_mgr->active_verb, new_modifier); + xnew = find_modifier(uc_mgr, new_modifier); if (xnew == NULL) return -ENOENT; err = 0; From df760d8b311607bacd821a5d6a3f341c2a853567 Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Tue, 21 Dec 2010 23:11:59 +0100 Subject: [PATCH 27/30] ucm: fix handling of cset with spaces in the control name Always terminate the cset command based on the last space found within the cset command since the control name may contain spaces. --- src/ucm/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ucm/main.c b/src/ucm/main.c index 5f44461f..73050427 100644 --- a/src/ucm/main.c +++ b/src/ucm/main.c @@ -168,7 +168,7 @@ static int execute_cset(snd_ctl_t *ctl, char *cset) snd_ctl_elem_value_malloc(&value); snd_ctl_elem_info_malloc(&info); - pos = strchr(cset, ' '); + pos = strrchr(cset, ' '); if (pos == NULL) { uc_error("undefined value for cset >%s<", cset); return -EINVAL; From 7b259bc00e5204029ac414c2f99688cf2d0b408c Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Tue, 21 Dec 2010 23:12:00 +0100 Subject: [PATCH 28/30] ucm: ctlparse - always terminate cset buffer Signed-off-by: Liam Girdwood --- src/control/ctlparse.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/control/ctlparse.c b/src/control/ctlparse.c index c8300f3b..a9298167 100644 --- a/src/control/ctlparse.c +++ b/src/control/ctlparse.c @@ -208,8 +208,8 @@ int snd_ctl_ascii_elem_id_parse(snd_ctl_elem_id_t *dst, const char *str) } str++; } - *ptr = '\0'; } + *ptr = '\0'; snd_ctl_elem_id_set_name(dst, buf); } else if (!strncasecmp(str, "index=", 6)) { str += 6; From e1b4bd6c89b194c54e892626727264e49464a3f1 Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Tue, 11 Jan 2011 19:05:12 +0100 Subject: [PATCH 29/30] ucm: add snd_use_case_verb_list() inline function Signed-off-by: Jaroslav Kysela --- include/use-case.h | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/include/use-case.h b/include/use-case.h index d5ca361b..f5776284 100644 --- a/include/use-case.h +++ b/include/use-case.h @@ -318,7 +318,6 @@ int snd_use_case_mgr_reset(snd_use_case_mgr_t *uc_mgr); /** * \brief Obtain a list of cards - * \param uc_mgr Use case manager (may be NULL - card list) * \param list Returned allocated list * \return Number of list entries if success, otherwise a negative error code */ @@ -327,6 +326,18 @@ static inline int snd_use_case_card_list(const char **list[]) return snd_use_case_get_list(NULL, NULL, list); } +/** + * \brief Obtain a list of verbs + * \param uc_mgr Use case manager + * \param list Returned list of verbs + * \return Number of list entries if success, otherwise a negative error code + */ +static inline int snd_use_case_verb_list(snd_use_case_mgr_t *uc_mgr, + const char **list[]) +{ + return snd_use_case_get_list(uc_mgr, "_verbs", list); +} + /** * \} */ From a618cdf0574a21bdd04e936fe2ac00d32a64e99d Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Tue, 11 Jan 2011 18:56:30 +0100 Subject: [PATCH 30/30] ucm: fix switch device & modifier when no transition is found Fix some logic bugs in switch device and switch modifier when transition sequences are not found. Also fix check for new device. Reported-by: w0806.kim@samsung.com Signed-off-by: Liam Girdwood --- src/ucm/main.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ucm/main.c b/src/ucm/main.c index 73050427..030c9b1c 100644 --- a/src/ucm/main.c +++ b/src/ucm/main.c @@ -1305,7 +1305,7 @@ static int switch_device(snd_use_case_mgr_t *uc_mgr, if (xold == NULL) return -ENOENT; xnew = find_device(uc_mgr->active_verb, new_device); - if (xold == NULL) + if (xnew == NULL) return -ENOENT; err = 0; list_for_each(pos, &xold->transition_list) { @@ -1327,7 +1327,7 @@ static int switch_device(snd_use_case_mgr_t *uc_mgr, err = set_device(uc_mgr, xold, 0); if (err < 0) return err; - err = set_device(uc_mgr, xnew, 0); + err = set_device(uc_mgr, xnew, 1); if (err < 0) return err; } @@ -1379,7 +1379,7 @@ static int switch_modifier(snd_use_case_mgr_t *uc_mgr, err = set_modifier(uc_mgr, xold, 0); if (err < 0) return err; - err = set_modifier(uc_mgr, xnew, 0); + err = set_modifier(uc_mgr, xnew, 1); if (err < 0) return err; }