From 9d8218ccc24fb4d099a31621c4b517e4861e59cb Mon Sep 17 00:00:00 2001 From: Packit Date: Aug 31 2020 13:22:26 +0000 Subject: Apply patch alsa-git.patch patch_name: alsa-git.patch present_in_specfile: true --- diff --git a/include/use-case.h b/include/use-case.h index 8e7e838..134303a 100644 --- a/include/use-case.h +++ b/include/use-case.h @@ -114,10 +114,22 @@ extern "C" { * * Physical system devices the render and capture audio. Devices can be OR'ed * together to support audio on simultaneous devices. + * + * If multiple devices with the same name exists, the number suffixes should + * be added to these names like HDMI1,HDMI2,HDMI3 etc. No number gaps are + * allowed. The names with numbers must be continuous. + * + * If EnableSequence/DisableSequence controls independent paths in the hardware + * it is also recommended to split playback and capture UCM devices and use + * the number suffixes. Example use case: Use the integrated microphone + * in the laptop instead the microphone in headphones. + * + * The preference of the devices is determined by the priority value. */ #define SND_USE_CASE_DEV_NONE "None" /**< None Device */ #define SND_USE_CASE_DEV_SPEAKER "Speaker" /**< Speaker Device */ #define SND_USE_CASE_DEV_LINE "Line" /**< Line Device */ +#define SND_USE_CASE_DEV_MIC "Mic" /**< Microphone Device */ #define SND_USE_CASE_DEV_HEADPHONES "Headphones" /**< Headphones Device */ #define SND_USE_CASE_DEV_HEADSET "Headset" /**< Headset Device */ #define SND_USE_CASE_DEV_HANDSET "Handset" /**< Handset Device */ @@ -206,6 +218,7 @@ int snd_use_case_free_list(const char *list[], int items); * - _enadevs - get list of enabled devices * - _enamods - get list of enabled modifiers * + * - _identifiers/{modifier}|{device}[/{verb}] - list of value identifiers * - _supporteddevs/{modifier}|{device}[/{verb}] - list of supported devices * - _conflictingdevs/{modifier}|{device}[/{verb}] - list of conflicting devices * @@ -261,6 +274,10 @@ int snd_use_case_get_list(snd_use_case_mgr_t *uc_mgr, * Recommended names for values: * - TQ * - Tone Quality + * - Priority + * - priority value (1-10000), higher value means higher priority + * - valid only for verbs + * - for devices - PlaybackPriority and CapturePriority * - PlaybackPCM * - full PCM playback device name * - PlaybackPCMIsDummy @@ -288,7 +305,7 @@ int snd_use_case_get_list(snd_use_case_mgr_t *uc_mgr, * - playback control switch identifier string * - can be parsed using snd_use_case_parse_ctl_elem_id() * - PlaybackPriority - * - priority value (1-10000), default value is 100, higher value means lower priority + * - priority value (1-10000), higher value means higher priority * - CaptureRate * - capture device sample rate * - CaptureChannels @@ -302,7 +319,7 @@ int snd_use_case_get_list(snd_use_case_mgr_t *uc_mgr, * - capture control switch identifier string * - can be parsed using snd_use_case_parse_ctl_elem_id() * - CapturePriority - * - priority value (1-10000), default value is 100, higher value means lower priority + * - priority value (1-10000), higher value means higher priority * - PlaybackMixer * - name of playback mixer * - PlaybackMixerElem @@ -310,6 +327,7 @@ int snd_use_case_get_list(snd_use_case_mgr_t *uc_mgr, * - can be parsed using snd_use_case_parse_selem_id() * - PlaybackMasterElem * - mixer element playback identifier for the master control + * - can be parsed using snd_use_case_parse_selem_id() * - PlaybackMasterType * - type of the master volume control * - Valid values: "soft" (software attenuation) @@ -320,30 +338,38 @@ int snd_use_case_get_list(snd_use_case_mgr_t *uc_mgr, * - can be parsed using snd_use_case_parse_selem_id() * - CaptureMasterElem * - mixer element playback identifier for the master control + * - can be parsed using snd_use_case_parse_selem_id() * - CaptureMasterType * - type of the master volume control * - Valid values: "soft" (software attenuation) * - EDIDFile * - Path to EDID file for HDMI devices - * - JackControl, JackDev, JackHWMute - * - Jack information for a device. The jack status can be reported via - * a kcontrol and/or via an input device. **JackControl** is the - * kcontrol name of the jack, and **JackDev** is the input device id of - * the jack (if the full input device path is /dev/input/by-id/foo, the - * JackDev value should be "foo"). UCM configuration files should - * contain both JackControl and JackDev when possible, because - * applications are likely to support only one or the other. - * - * If **JackHWMute** is set, it indicates that when the jack is plugged - * in, the hardware automatically mutes some other device(s). The - * JackHWMute value is a space-separated list of device names (this - * isn't compatible with device names with spaces in them, so don't use - * such device names!). Note that JackHWMute should be used only when - * the hardware enforces the automatic muting. If the hardware doesn't - * enforce any muting, it may still be tempting to set JackHWMute to - * trick upper software layers to e.g. automatically mute speakers when - * headphones are plugged in, but that's application policy - * configuration that doesn't belong to UCM configuration files. + * - JackCTL + * - jack control device name + * - JackControl + * - jack control identificator + * - can be parsed using snd_use_case_parse_ctl_elem_id() + * - UCM configuration files should contain both JackControl and JackDev + * when possible, because applications are likely to support only one + * or the other + * - JackDev + * - the input device id of the jack (if the full input device path is + * /dev/input/by-id/foo, the JackDev value should be "foo") + * - UCM configuration files should contain both JackControl and JackDev + * when possible, because applications are likely to support only one + * or the other + * - JackHWMute + * If this value is set, it indicates that when the jack is plugged + * in, the hardware automatically mutes some other device(s). The + * value is a space-separated list of device names. If the device + * name contains space, it must be enclosed to ' or ", e.g.: + * JackHWMute "'Dock Headphone' Headphone" + * Note that JackHWMute should be used only when the hardware enforces + * the automatic muting. If the hardware doesn't enforce any muting, it + * may still be tempting to set JackHWMute to trick upper software layers + * to e.g. automatically mute speakers when headphones are plugged in, + * but that's application policy configuration that doesn't belong + * to UCM configuration files. * - MinBufferLevel * - This is used on platform where reported buffer level is not accurate. * E.g. "512", which holds 512 samples in device buffer. Note: this will diff --git a/include/use-case.h.alsa-git b/include/use-case.h.alsa-git new file mode 100644 index 0000000..8e7e838 --- /dev/null +++ b/include/use-case.h.alsa-git @@ -0,0 +1,500 @@ +/** + * \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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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 + +#include + +/** + * \defgroup ucm Use Case Interface + * The ALSA Use Case manager interface. + * See \ref Usecase page for more details. + * \{ + */ + +/*! \page Usecase 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. + * + * + Incoming phone call when the device is playing music + * + Recording sections of a phone call + * + 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 :- + * + * + Query the supported use case verbs, devices and modifiers for the machine. + * + Set and Get use case verbs, devices and modifiers for the machine. + * + Get the ALSA PCM playback and capture device PCMs for use case verb, + * use case device and modifier. + * + Get the TQ parameter for each use case verb, use case device and + * modifier. + * + Get the ALSA master playback and capture volume/switch kcontrols + * or mixer elements 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" /**< Inactive Verb */ +#define SND_USE_CASE_VERB_HIFI "HiFi" /**< HiFi Verb */ +#define SND_USE_CASE_VERB_HIFI_LOW_POWER "HiFi Low Power" /**< HiFi Low Power Verb */ +#define SND_USE_CASE_VERB_VOICE "Voice" /**< Voice Verb */ +#define SND_USE_CASE_VERB_VOICE_LOW_POWER "Voice Low Power" /**< Voice Low Power Verb */ +#define SND_USE_CASE_VERB_VOICECALL "Voice Call" /**< Voice Call Verb */ +#define SND_USE_CASE_VERB_IP_VOICECALL "Voice Call IP" /**< Voice Call IP Verb */ +#define SND_USE_CASE_VERB_ANALOG_RADIO "FM Analog Radio" /**< FM Analog Radio Verb */ +#define SND_USE_CASE_VERB_DIGITAL_RADIO "FM Digital Radio" /**< FM Digital Radio Verb */ +/* 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 simultaneous devices. + */ +#define SND_USE_CASE_DEV_NONE "None" /**< None Device */ +#define SND_USE_CASE_DEV_SPEAKER "Speaker" /**< Speaker Device */ +#define SND_USE_CASE_DEV_LINE "Line" /**< Line Device */ +#define SND_USE_CASE_DEV_HEADPHONES "Headphones" /**< Headphones Device */ +#define SND_USE_CASE_DEV_HEADSET "Headset" /**< Headset Device */ +#define SND_USE_CASE_DEV_HANDSET "Handset" /**< Handset Device */ +#define SND_USE_CASE_DEV_BLUETOOTH "Bluetooth" /**< Bluetooth Device */ +#define SND_USE_CASE_DEV_EARPIECE "Earpiece" /**< Earpiece Device */ +#define SND_USE_CASE_DEV_SPDIF "SPDIF" /**< SPDIF Device */ +#define SND_USE_CASE_DEV_HDMI "HDMI" /**< HDMI Device */ +/* 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("CapturePCM") 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("PlaybackPCM") to get ALSA PCM sink name for + * ringtone pcm data. + */ +#define SND_USE_CASE_MOD_CAPTURE_VOICE "Capture Voice" /**< Capture Voice Modifier */ +#define SND_USE_CASE_MOD_CAPTURE_MUSIC "Capture Music" /**< Capture Music Modifier */ +#define SND_USE_CASE_MOD_PLAY_MUSIC "Play Music" /**< Play Music Modifier */ +#define SND_USE_CASE_MOD_PLAY_VOICE "Play Voice" /**< Play Voice Modifier */ +#define SND_USE_CASE_MOD_PLAY_TONE "Play Tone" /**< Play Tone Modifier */ +#define SND_USE_CASE_MOD_ECHO_REF "Echo Reference" /**< Echo Reference Modifier */ +/* add new modifiers to end of list */ + + +/** + * TQ - Tone Quality + * + * 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. + * + */ +#define SND_USE_CASE_TQ_MUSIC "Music" /**< Music Tone Quality */ +#define SND_USE_CASE_TQ_VOICE "Voice" /**< Voice Tone Quality */ +#define SND_USE_CASE_TQ_TONES "Tones" /**< Tones Tone Quality */ + +/** use case container */ +typedef struct snd_use_case_mgr snd_use_case_mgr_t; + +/** + * \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 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); + +/** + * \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 + * + * Defined identifiers: + * - NULL - get card list + * (in pair cardname+comment) + * - _verbs - get verb list + * (in pair verb+comment) + * - _devices[/{verb}] - get list of supported devices + * (in pair device+comment) + * - _modifiers[/{verb}] - get list of supported modifiers + * (in pair modifier+comment) + * - TQ[/{verb}] - get list of TQ identifiers + * - _enadevs - get list of enabled devices + * - _enamods - get list of enabled modifiers + * + * - _supporteddevs/{modifier}|{device}[/{verb}] - list of supported devices + * - _conflictingdevs/{modifier}|{device}[/{verb}] - list of conflicting devices + * + * Note that at most one of the supported/conflicting devs lists has + * any entries, and when neither is present, all devices are supported. + * + */ +int snd_use_case_get_list(snd_use_case_mgr_t *uc_mgr, + const char *identifier, + const char **list[]); + + +/** + * \brief Get current - string + * \param uc_mgr Use case manager + * \param identifier + * \param value Value pointer + * \return Zero if success, otherwise a negative error code + * + * Note: The returned string is dynamically allocated, use free() to + * deallocate this string. (Yes, the value parameter shouldn't be marked as + * "const", but it's too late to fix it, sorry about that.) + * + * Known identifiers: + * - NULL - return current card + * - _verb - return current verb + * - _file - return configuration file loaded for current card + * + * - [=]{NAME}[/[{modifier}|{/device}][/{verb}]] + * - value identifier {NAME} + * - Search starts at given modifier or device if any, + * else at a verb + * - Search starts at given verb if any, + * else current verb + * - Searches modifier/device, then verb, then defaults + * - Specify a leading "=" to search only the exact + * device/modifier/verb specified, and not search + * through each object in turn. + * - Examples: + * - "PlaybackPCM/Play Music" + * - "CapturePCM/SPDIF" + * - From ValueDefaults only: + * "=Variable" + * - From current active verb: + * "=Variable//" + * - From verb "Verb": + * "=Variable//Verb" + * - From "Modifier" in current active verb: + * "=Variable/Modifier/" + * - From "Modifier" in "Verb": + * "=Variable/Modifier/Verb" + * + * Recommended names for values: + * - TQ + * - Tone Quality + * - PlaybackPCM + * - full PCM playback device name + * - PlaybackPCMIsDummy + * - Valid values: "yes" and "no". If set to "yes", the PCM named by the + * PlaybackPCM value is a dummy device, meaning that opening it enables + * an audio path in the hardware, but writing to the PCM device has no + * effect. + * - CapturePCM + * - full PCM capture device name + * - CapturePCMIsDummy + * - Valid values: "yes" and "no". If set to "yes", the PCM named by the + * CapturePCM value is a dummy device, meaning that opening it enables + * an audio path in the hardware, but reading from the PCM device has no + * effect. + * - PlaybackRate + * - playback device sample rate + * - PlaybackChannels + * - playback device channel count + * - PlaybackCTL + * - playback control device name + * - PlaybackVolume + * - playback control volume identifier string + * - can be parsed using snd_use_case_parse_ctl_elem_id() + * - PlaybackSwitch + * - playback control switch identifier string + * - can be parsed using snd_use_case_parse_ctl_elem_id() + * - PlaybackPriority + * - priority value (1-10000), default value is 100, higher value means lower priority + * - CaptureRate + * - capture device sample rate + * - CaptureChannels + * - capture device channel count + * - CaptureCTL + * - capture control device name + * - CaptureVolume + * - capture control volume identifier string + * - can be parsed using snd_use_case_parse_ctl_elem_id() + * - CaptureSwitch + * - capture control switch identifier string + * - can be parsed using snd_use_case_parse_ctl_elem_id() + * - CapturePriority + * - priority value (1-10000), default value is 100, higher value means lower priority + * - PlaybackMixer + * - name of playback mixer + * - PlaybackMixerElem + * - mixer element playback identifier + * - can be parsed using snd_use_case_parse_selem_id() + * - PlaybackMasterElem + * - mixer element playback identifier for the master control + * - PlaybackMasterType + * - type of the master volume control + * - Valid values: "soft" (software attenuation) + * - CaptureMixer + * - name of capture mixer + * - CaptureMixerElem + * - mixer element capture identifier + * - can be parsed using snd_use_case_parse_selem_id() + * - CaptureMasterElem + * - mixer element playback identifier for the master control + * - CaptureMasterType + * - type of the master volume control + * - Valid values: "soft" (software attenuation) + * - EDIDFile + * - Path to EDID file for HDMI devices + * - JackControl, JackDev, JackHWMute + * - Jack information for a device. The jack status can be reported via + * a kcontrol and/or via an input device. **JackControl** is the + * kcontrol name of the jack, and **JackDev** is the input device id of + * the jack (if the full input device path is /dev/input/by-id/foo, the + * JackDev value should be "foo"). UCM configuration files should + * contain both JackControl and JackDev when possible, because + * applications are likely to support only one or the other. + * + * If **JackHWMute** is set, it indicates that when the jack is plugged + * in, the hardware automatically mutes some other device(s). The + * JackHWMute value is a space-separated list of device names (this + * isn't compatible with device names with spaces in them, so don't use + * such device names!). Note that JackHWMute should be used only when + * the hardware enforces the automatic muting. If the hardware doesn't + * enforce any muting, it may still be tempting to set JackHWMute to + * trick upper software layers to e.g. automatically mute speakers when + * headphones are plugged in, but that's application policy + * configuration that doesn't belong to UCM configuration files. + * - MinBufferLevel + * - This is used on platform where reported buffer level is not accurate. + * E.g. "512", which holds 512 samples in device buffer. Note: this will + * increase latency. + */ +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 + * \param value result + * \return Zero if success, otherwise a negative error code + * + * Known identifiers: + * - _devstatus/{device} - return status for given device + * - _modstatus/{modifier} - return status for given modifier + */ +int snd_use_case_geti(snd_use_case_mgr_t *uc_mgr, + const char *identifier, + long *value); + +/** + * \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/{old_device} - 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/{old_modifier} - 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 + * \param uc_mgr Returned use case manager pointer + * \param card_name Sound card name. + * \return zero if success, otherwise a negative error code + * + * By default only first card is used when the driver card + * name or long name is passed in the card_name argument. + * + * The "strict:" prefix in the card_name defines that + * there is no driver name / long name matching. The straight + * configuration is used. + * + * The "hw:" prefix in the card_name will load the configuration + * for the ALSA card specified by the card index (value) or + * the card string identificator. + * + * The sound card might be also composed from several physical + * sound cards (for the default and strict card_name). + * The application cannot expect that the device names will refer + * only one ALSA sound card in this case. + */ +int snd_use_case_mgr_open(snd_use_case_mgr_t **uc_mgr, + 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); + +/* + * helper functions + */ + +/** + * \brief Obtain a list of cards + * \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); +} + +/** + * \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); +} + +/** + * \brief Parse control element identifier + * \param elem_id Element identifier + * \param ucm_id Use case identifier + * \param value String value to be parsed + * \return Zero if success, otherwise a negative error code + */ +int snd_use_case_parse_ctl_elem_id(snd_ctl_elem_id_t *dst, + const char *ucm_id, + const char *value); + +/** + * \brief Parse mixer element identifier + * \param dst Simple mixer element identifier + * \param ucm_id Use case identifier + * \param value String value to be parsed + * \return Zero if success, otherwise a negative error code + */ +int snd_use_case_parse_selem_id(snd_mixer_selem_id_t *dst, + const char *ucm_id, + const char *value); + +/** + * \} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* __ALSA_USE_CASE_H */ diff --git a/src/control/namehint.c b/src/control/namehint.c index 808df6b..60c48ae 100644 --- a/src/control/namehint.c +++ b/src/control/namehint.c @@ -348,6 +348,13 @@ static int try_config(snd_config_t *config, goto __cleanup; if (snd_config_search(res, "@args", &cfg) >= 0) { snd_config_for_each(i, next, cfg) { + /* skip the argument list */ + if (snd_config_get_id(snd_config_iterator_entry(i), &str) < 0) + continue; + while (*str && *str >= '0' && *str <= '9') str++; + if (*str == '\0') + continue; + /* the argument definition must have the default */ if (snd_config_search(snd_config_iterator_entry(i), "default", NULL) < 0) { err = -EINVAL; diff --git a/src/control/namehint.c.alsa-git b/src/control/namehint.c.alsa-git new file mode 100644 index 0000000..808df6b --- /dev/null +++ b/src/control/namehint.c.alsa-git @@ -0,0 +1,706 @@ +/** + * \file control/namehint.c + * \brief Give device name hints + * \author Jaroslav Kysela + * \date 2006 + */ +/* + * Give device name hints - main file + * Copyright (c) 2006 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "local.h" + +#ifndef DOC_HIDDEN +#define DEV_SKIP 9999 /* some non-existing device number */ +struct hint_list { + char **list; + unsigned int count; + unsigned int allocated; + const char *siface; + snd_ctl_elem_iface_t iface; + snd_ctl_t *ctl; + snd_ctl_card_info_t *info; + int card; + int device; + long device_input; + long device_output; + int stream; + int show_all; + char *cardname; +}; +#endif + +static int hint_list_add(struct hint_list *list, + const char *name, + const char *description) +{ + char *x; + + if (list->count + 1 >= list->allocated) { + char **n = realloc(list->list, (list->allocated + 10) * sizeof(char *)); + if (n == NULL) + return -ENOMEM; + memset(n + list->allocated, 0, 10 * sizeof(*n)); + list->allocated += 10; + list->list = n; + } + if (name == NULL) { + x = NULL; + } else { + x = malloc(4 + strlen(name) + (description != NULL ? (4 + strlen(description) + 1) : 0) + 1); + if (x == NULL) + return -ENOMEM; + memcpy(x, "NAME", 4); + strcpy(x + 4, name); + if (description != NULL) { + strcat(x, "|DESC"); + strcat(x, description); + } + } + list->list[list->count++] = x; + return 0; +} + +static void zero_handler(const char *file ATTRIBUTE_UNUSED, + int line ATTRIBUTE_UNUSED, + const char *function ATTRIBUTE_UNUSED, + int err ATTRIBUTE_UNUSED, + const char *fmt ATTRIBUTE_UNUSED, + va_list arg ATTRIBUTE_UNUSED) +{ +} + +static int get_dev_name1(struct hint_list *list, char **res, int device, + int stream) +{ + *res = NULL; + if (device < 0 || device == DEV_SKIP) + return 0; + switch (list->iface) { +#ifdef BUILD_HWDEP + case SND_CTL_ELEM_IFACE_HWDEP: + { + snd_hwdep_info_t info = {0}; + snd_hwdep_info_set_device(&info, device); + if (snd_ctl_hwdep_info(list->ctl, &info) < 0) + return 0; + *res = strdup(snd_hwdep_info_get_name(&info)); + return 0; + } +#endif +#ifdef BUILD_PCM + case SND_CTL_ELEM_IFACE_PCM: + { + snd_pcm_info_t info = {0}; + snd_pcm_info_set_device(&info, device); + snd_pcm_info_set_stream(&info, stream ? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK); + if (snd_ctl_pcm_info(list->ctl, &info) < 0) + return 0; + switch (snd_pcm_info_get_class(&info)) { + case SND_PCM_CLASS_MODEM: + case SND_PCM_CLASS_DIGITIZER: + return -ENODEV; + default: + break; + } + *res = strdup(snd_pcm_info_get_name(&info)); + return 0; + } +#endif +#ifdef BUILD_RAWMIDI + case SND_CTL_ELEM_IFACE_RAWMIDI: + { + snd_rawmidi_info_t info = {0}; + snd_rawmidi_info_set_device(&info, device); + snd_rawmidi_info_set_stream(&info, stream ? SND_RAWMIDI_STREAM_INPUT : SND_RAWMIDI_STREAM_OUTPUT); + if (snd_ctl_rawmidi_info(list->ctl, &info) < 0) + return 0; + *res = strdup(snd_rawmidi_info_get_name(&info)); + return 0; + } +#endif + default: + return 0; + } +} + +static char *get_dev_name(struct hint_list *list) +{ + char *str1, *str2, *res; + int device; + + device = list->device_input >= 0 ? list->device_input : list->device; + if (get_dev_name1(list, &str1, device, 1) < 0) + return NULL; + device = list->device_output >= 0 ? list->device_output : list->device; + if (get_dev_name1(list, &str2, device, 0) < 0) { + if (str1) + free(str1); + return NULL; + } + if (str1 != NULL || str2 != NULL) { + if (str1 != NULL && str2 != NULL) { + if (strcmp(str1, str2) == 0) { + res = malloc(strlen(list->cardname) + strlen(str2) + 3); + if (res != NULL) { + strcpy(res, list->cardname); + strcat(res, ", "); + strcat(res, str2); + } + } else { + res = malloc(strlen(list->cardname) + strlen(str2) + strlen(str1) + 6); + if (res != NULL) { + strcpy(res, list->cardname); + strcat(res, ", "); + strcat(res, str2); + strcat(res, " / "); + strcat(res, str1); + } + } + free(str2); + free(str1); + return res; + } else { + if (str1 != NULL) { + str2 = "Input"; + } else { + str1 = str2; + str2 = "Output"; + } + res = malloc(strlen(list->cardname) + strlen(str1) + 19); + if (res == NULL) { + free(str1); + return NULL; + } + strcpy(res, list->cardname); + strcat(res, ", "); + strcat(res, str1); + strcat(res, "|IOID"); + strcat(res, str2); + free(str1); + return res; + } + } + /* if the specified device doesn't exist, skip this entry */ + if (list->device >= 0 || list->device_input >= 0 || list->device_output >= 0) + return NULL; + return strdup(list->cardname); +} + +#ifndef DOC_HIDDEN +#define BUF_SIZE 128 +#endif + +static int try_config(snd_config_t *config, + struct hint_list *list, + const char *base, + const char *name) +{ + snd_local_error_handler_t eh; + snd_config_t *res = NULL, *cfg, *cfg1, *n; + snd_config_iterator_t i, next; + char *buf, *buf1 = NULL, *buf2; + const char *str; + int err = 0, level; + long dev = list->device; + int cleanup_res = 0; + + list->device_input = -1; + list->device_output = -1; + buf = malloc(BUF_SIZE); + if (buf == NULL) + return -ENOMEM; + sprintf(buf, "%s.%s", base, name); + /* look for redirection */ + if (snd_config_search(config, buf, &cfg) >= 0 && + snd_config_get_string(cfg, &str) >= 0 && + ((strncmp(base, str, strlen(base)) == 0 && + str[strlen(base)] == '.') || strchr(str, '.') == NULL)) + goto __skip_add; + if (list->card >= 0 && list->device >= 0) + sprintf(buf, "%s:CARD=%s,DEV=%i", name, snd_ctl_card_info_get_id(list->info), list->device); + else if (list->card >= 0) + sprintf(buf, "%s:CARD=%s", name, snd_ctl_card_info_get_id(list->info)); + else + strcpy(buf, name); + eh = snd_lib_error_set_local(&zero_handler); + err = snd_config_search_definition(config, base, buf, &res); + snd_lib_error_set_local(eh); + if (err < 0) + goto __skip_add; + cleanup_res = 1; + err = -EINVAL; + if (snd_config_get_type(res) != SND_CONFIG_TYPE_COMPOUND) + goto __cleanup; + if (snd_config_search(res, "type", NULL) < 0) + goto __cleanup; + +#if 0 /* for debug purposes */ + { + snd_output_t *out; + fprintf(stderr, "********* PCM '%s':\n", buf); + snd_output_stdio_attach(&out, stderr, 0); + snd_config_save(res, out); + snd_output_close(out); + fprintf(stderr, "\n"); + } +#endif + + cfg1 = res; + level = 0; + __hint: + level++; + if (snd_config_search(cfg1, "type", &cfg) >= 0 && + snd_config_get_string(cfg, &str) >= 0 && + strcmp(str, "hw") == 0) { + list->device_input = -1; + list->device_output = -1; + if (snd_config_search(cfg1, "device", &cfg) >= 0) { + if (snd_config_get_integer(cfg, &dev) < 0) { + SNDERR("(%s) device must be an integer", buf); + err = -EINVAL; + goto __cleanup; + } + } + } + + if (snd_config_search(cfg1, "hint", &cfg) >= 0) { + if (snd_config_get_type(cfg) != SND_CONFIG_TYPE_COMPOUND) { + SNDERR("hint (%s) must be a compound", buf); + err = -EINVAL; + goto __cleanup; + } + if (level == 1 && + snd_config_search(cfg, "show", &n) >= 0 && + snd_config_get_bool(n) <= 0) + goto __skip_add; + if (buf1 == NULL && + snd_config_search(cfg, "description", &n) >= 0 && + snd_config_get_string(n, &str) >= 0) { + buf1 = strdup(str); + if (buf1 == NULL) { + err = -ENOMEM; + goto __cleanup; + } + } + if (snd_config_search(cfg, "device", &n) >= 0) { + if (snd_config_get_integer(n, &dev) < 0) { + SNDERR("(%s) device must be an integer", buf); + err = -EINVAL; + goto __cleanup; + } + list->device_input = dev; + list->device_output = dev; + } + if (snd_config_search(cfg, "device_input", &n) >= 0) { + if (snd_config_get_integer(n, &list->device_input) < 0) { + SNDERR("(%s) device_input must be an integer", buf); + err = -EINVAL; + goto __cleanup; + } + /* skip the counterpart if only a single direction is defined */ + if (list->device_output < 0) + list->device_output = DEV_SKIP; + } + if (snd_config_search(cfg, "device_output", &n) >= 0) { + if (snd_config_get_integer(n, &list->device_output) < 0) { + SNDERR("(%s) device_output must be an integer", buf); + err = -EINVAL; + goto __cleanup; + } + /* skip the counterpart if only a single direction is defined */ + if (list->device_input < 0) + list->device_input = DEV_SKIP; + } + } else if (level == 1 && !list->show_all) + goto __skip_add; + if (snd_config_search(cfg1, "slave", &cfg) >= 0 && + snd_config_search(cfg, base, &cfg1) >= 0) + goto __hint; + snd_config_delete(res); + res = NULL; + cleanup_res = 0; + if (strchr(buf, ':') != NULL) + goto __ok; + /* find, if all parameters have a default, */ + /* otherwise filter this definition */ + eh = snd_lib_error_set_local(&zero_handler); + err = snd_config_search_alias_hooks(config, base, buf, &res); + snd_lib_error_set_local(eh); + if (err < 0) + goto __cleanup; + if (snd_config_search(res, "@args", &cfg) >= 0) { + snd_config_for_each(i, next, cfg) { + if (snd_config_search(snd_config_iterator_entry(i), + "default", NULL) < 0) { + err = -EINVAL; + goto __cleanup; + } + } + } + __ok: + err = 0; + __cleanup: + if (err >= 0) { + list->device = dev; + str = list->card >= 0 ? get_dev_name(list) : NULL; + if (str != NULL) { + level = (buf1 == NULL ? 0 : strlen(buf1)) + 1 + strlen(str); + buf2 = realloc((char *)str, level + 1); + if (buf2 != NULL) { + if (buf1 != NULL) { + str = strchr(buf2, '|'); + if (str != NULL) + memmove(buf2 + (level - strlen(str)), str, strlen(str)); + else + str = buf2 + strlen(buf2); + *(char *)str++ = '\n'; + memcpy((char *)str, buf1, strlen(buf1)); + buf2[level] = '\0'; + free(buf1); + } + buf1 = buf2; + } else { + free((char *)str); + } + } else if (list->device >= 0) + goto __skip_add; + err = hint_list_add(list, buf, buf1); + } + __skip_add: + if (res && cleanup_res) + snd_config_delete(res); + if (buf1) + free(buf1); + free(buf); + return err; +} + +#ifndef DOC_HIDDEN +#define IFACE(v, fcn) [SND_CTL_ELEM_IFACE_##v] = (next_devices_t)fcn + +typedef int (*next_devices_t)(snd_ctl_t *, int *); + +static const next_devices_t next_devices[] = { + IFACE(CARD, NULL), + IFACE(HWDEP, snd_ctl_hwdep_next_device), + IFACE(MIXER, NULL), + IFACE(PCM, snd_ctl_pcm_next_device), + IFACE(RAWMIDI, snd_ctl_rawmidi_next_device), + IFACE(TIMER, NULL), + IFACE(SEQUENCER, NULL) +}; +#endif + +static int add_card(snd_config_t *config, snd_config_t *rw_config, struct hint_list *list, int card) +{ + int err, ok; + snd_config_t *conf, *n; + snd_config_iterator_t i, next; + const char *str; + char ctl_name[16]; + snd_ctl_card_info_t info = {0}; + int device, max_device = 0; + + list->info = &info; + err = snd_config_search(config, list->siface, &conf); + if (err < 0) + return err; + sprintf(ctl_name, "hw:%i", card); + err = snd_ctl_open(&list->ctl, ctl_name, 0); + if (err < 0) + return err; + err = snd_ctl_card_info(list->ctl, &info); + if (err < 0) + goto __error; + snd_config_for_each(i, next, conf) { + n = snd_config_iterator_entry(i); + if (snd_config_get_id(n, &str) < 0) + continue; + + if (next_devices[list->iface] != NULL) { + list->card = card; + device = max_device = -1; + err = next_devices[list->iface](list->ctl, &device); + if (device < 0) + err = -EINVAL; + else + max_device = device; + while (err >= 0 && device >= 0) { + err = next_devices[list->iface](list->ctl, &device); + if (err >= 0 && device > max_device) + max_device = device; + } + ok = 0; + for (device = 0; err >= 0 && device <= max_device; device++) { + list->device = device; + err = try_config(rw_config, list, list->siface, str); + if (err < 0) + break; + ok++; + } + if (ok) + continue; + } else { + err = -EINVAL; + } + if (err == -EXDEV) + continue; + if (err < 0) { + list->card = card; + list->device = -1; + err = try_config(rw_config, list, list->siface, str); + } + if (err == -ENOMEM) + goto __error; + } + err = 0; + __error: + snd_ctl_close(list->ctl); + return err; +} + +static int get_card_name(struct hint_list *list, int card) +{ + char scard[16], *s; + int err; + + free(list->cardname); + list->cardname = NULL; + err = snd_card_get_name(card, &list->cardname); + if (err <= 0) + return 0; + sprintf(scard, " #%i", card); + s = realloc(list->cardname, strlen(list->cardname) + strlen(scard) + 1); + if (s == NULL) + return -ENOMEM; + list->cardname = s; + return 0; +} + +static int add_software_devices(snd_config_t *config, snd_config_t *rw_config, + struct hint_list *list) +{ + int err; + snd_config_t *conf, *n; + snd_config_iterator_t i, next; + const char *str; + + err = snd_config_search(config, list->siface, &conf); + if (err < 0) + return err; + snd_config_for_each(i, next, conf) { + n = snd_config_iterator_entry(i); + if (snd_config_get_id(n, &str) < 0) + continue; + list->card = -1; + list->device = -1; + err = try_config(rw_config, list, list->siface, str); + if (err == -ENOMEM) + return -ENOMEM; + } + return 0; +} + +/** + * \brief Get a set of device name hints + * \param card Card number or -1 (means all cards) + * \param iface Interface identification (like "pcm", "rawmidi", "timer", "seq") + * \param hints Result - array of device name hints + * \result zero if success, otherwise a negative error code + * + * hints will receive a NULL-terminated array of device name hints, + * which can be passed to #snd_device_name_get_hint to extract usable + * values. When no longer needed, hints should be passed to + * #snd_device_name_free_hint to release resources. + * + * User-defined hints are gathered from namehint.IFACE tree like: + * + * + * namehint.pcm {
+ * myfile "file:FILE=/tmp/soundwave.raw|Save sound output to /tmp/soundwave.raw"
+ * myplug "plug:front:Do all conversions for front speakers"
+ * } + *
+ * + * Note: The device description is separated with '|' char. + * + * Special variables: defaults.namehint.showall specifies if all device + * definitions are accepted (boolean type). + */ +int snd_device_name_hint(int card, const char *iface, void ***hints) +{ + struct hint_list list; + char ehints[24]; + const char *str; + snd_config_t *conf, *local_config = NULL, *local_config_rw = NULL; + snd_config_update_t *local_config_update = NULL; + snd_config_iterator_t i, next; + int err; + + if (hints == NULL) + return -EINVAL; + err = snd_config_update_r(&local_config, &local_config_update, NULL); + if (err < 0) + return err; + err = snd_config_copy(&local_config_rw, local_config); + if (err < 0) + return err; + list.list = NULL; + list.count = list.allocated = 0; + list.siface = iface; + list.show_all = 0; + list.cardname = NULL; + if (strcmp(iface, "card") == 0) + list.iface = SND_CTL_ELEM_IFACE_CARD; + else if (strcmp(iface, "pcm") == 0) + list.iface = SND_CTL_ELEM_IFACE_PCM; + else if (strcmp(iface, "rawmidi") == 0) + list.iface = SND_CTL_ELEM_IFACE_RAWMIDI; + else if (strcmp(iface, "timer") == 0) + list.iface = SND_CTL_ELEM_IFACE_TIMER; + else if (strcmp(iface, "seq") == 0) + list.iface = SND_CTL_ELEM_IFACE_SEQUENCER; + else if (strcmp(iface, "hwdep") == 0) + list.iface = SND_CTL_ELEM_IFACE_HWDEP; + else if (strcmp(iface, "ctl") == 0) + list.iface = SND_CTL_ELEM_IFACE_MIXER; + else { + err = -EINVAL; + goto __error; + } + + if (snd_config_search(local_config, "defaults.namehint.showall", &conf) >= 0) + list.show_all = snd_config_get_bool(conf) > 0; + if (card >= 0) { + err = get_card_name(&list, card); + if (err >= 0) + err = add_card(local_config, local_config_rw, &list, card); + } else { + add_software_devices(local_config, local_config_rw, &list); + err = snd_card_next(&card); + if (err < 0) + goto __error; + while (card >= 0) { + err = get_card_name(&list, card); + if (err < 0) + goto __error; + err = add_card(local_config, local_config_rw, &list, card); + if (err < 0) + goto __error; + err = snd_card_next(&card); + if (err < 0) + goto __error; + } + } + sprintf(ehints, "namehint.%s", list.siface); + err = snd_config_search(local_config, ehints, &conf); + if (err >= 0) { + snd_config_for_each(i, next, conf) { + if (snd_config_get_string(snd_config_iterator_entry(i), + &str) < 0) + continue; + err = hint_list_add(&list, str, NULL); + if (err < 0) + goto __error; + } + } + err = 0; + __error: + /* add an empty entry if nothing has been added yet; the caller + * expects non-NULL return + */ + if (!err && !list.list) + err = hint_list_add(&list, NULL, NULL); + if (err < 0) + snd_device_name_free_hint((void **)list.list); + else + *hints = (void **)list.list; + free(list.cardname); + if (local_config_rw) + snd_config_delete(local_config_rw); + if (local_config) + snd_config_delete(local_config); + if (local_config_update) + snd_config_update_free(local_config_update); + return err; +} + +/** + * \brief Free a list of device name hints. + * \param hints List to free + * \result zero if success, otherwise a negative error code + */ +int snd_device_name_free_hint(void **hints) +{ + char **h; + + if (hints == NULL) + return 0; + h = (char **)hints; + while (*h) { + free(*h); + h++; + } + free(hints); + return 0; +} + +/** + * \brief Extract a value from a hint + * \param hint A pointer to hint + * \param id Hint value to extract ("NAME", "DESC", or "IOID", see below) + * \result an allocated ASCII string if success, otherwise NULL + * + * List of valid IDs: + * NAME - name of device + * DESC - description of device + * IOID - input / output identification ("Input" or "Output"), NULL means both + * + * The return value should be freed when no longer needed. + */ +char *snd_device_name_get_hint(const void *hint, const char *id) +{ + const char *hint1 = (const char *)hint, *delim; + char *res; + unsigned size; + + if (strlen(id) != 4) + return NULL; + while (*hint1 != '\0') { + delim = strchr(hint1, '|'); + if (memcmp(id, hint1, 4) != 0) { + if (delim == NULL) + return NULL; + hint1 = delim + 1; + continue; + } + if (delim == NULL) + return strdup(hint1 + 4); + size = delim - hint1 - 4; + res = malloc(size + 1); + if (res != NULL) { + memcpy(res, hint1 + 4, size); + res[size] = '\0'; + } + return res; + } + return NULL; +} diff --git a/src/ucm/main.c b/src/ucm/main.c index b0b6ffb..61922f1 100644 --- a/src/ucm/main.c +++ b/src/ucm/main.c @@ -61,11 +61,13 @@ 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] == '/') + if (strncmp(identifier, prefix, len) != 0) + return 0; + + if (identifier[len] == 0 || identifier[len] == '/') return 1; + return 0; } @@ -1070,7 +1072,6 @@ int snd_use_case_mgr_reset(snd_use_case_mgr_t *uc_mgr) /** * \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 */ static int get_verb_list(snd_use_case_mgr_t *uc_mgr, const char **list[]) @@ -1158,8 +1159,10 @@ static int get_supcon_device_list(snd_use_case_mgr_t *uc_mgr, modifier = find_modifier(uc_mgr, verb, name, 0); if (modifier) { - if (modifier->dev_list.type != type) + if (modifier->dev_list.type != type) { + *list = NULL; return 0; + } return get_list(&modifier->dev_list.list, list, struct dev_list_node, list, name); @@ -1167,15 +1170,16 @@ static int get_supcon_device_list(snd_use_case_mgr_t *uc_mgr, device = find_device(uc_mgr, verb, name, 0); if (device) { - if (device->dev_list.type != type) + if (device->dev_list.type != type) { + *list = NULL; return 0; + } return get_list(&device->dev_list.list, list, struct dev_list_node, list, name); } return -ENOENT; - } /** @@ -1204,41 +1208,201 @@ static int get_conflicting_device_list(snd_use_case_mgr_t *uc_mgr, #ifndef DOC_HIDDEN struct myvalue { - struct list_head list; - char *value; + struct list_head list; + const char *text; }; #endif +/** + * \brief Convert myvalue list string list + * \param list myvalue list + * \param res string list + * \retval Number of list entries if success, otherwise a negativer error code + */ +static int myvalue_to_str_list(struct list_head *list, char ***res) +{ + struct list_head *pos; + struct myvalue *value; + char **p; + int cnt; + + cnt = alloc_str_list(list, 1, res); + if (cnt < 0) + return cnt; + p = *res; + list_for_each(pos, list) { + value = list_entry(pos, struct myvalue, list); + *p = strdup(value->text); + if (*p == NULL) { + snd_use_case_free_list((const char **)p, cnt); + return -ENOMEM; + } + p++; + } + return cnt; +} + +/** + * \brief Free myvalue list + * \param list myvalue list + */ +static void myvalue_list_free(struct list_head *list) +{ + struct list_head *pos, *npos; + struct myvalue *value; + + list_for_each_safe(pos, npos, list) { + value = list_entry(pos, struct myvalue, list); + list_del(&value->list); + free(value); + } +} + +/** + * \brief Merge one value to the myvalue list + * \param list The list with values + * \param value The value to be merged (without duplicates) + * \return 1 if dup, 0 if success, otherwise a negative error code + */ +static int merge_value(struct list_head *list, const char *text) +{ + struct list_head *pos; + struct myvalue *value; + + list_for_each(pos, list) { + value = list_entry(pos, struct myvalue, list); + if (strcmp(value->text, text) == 0) + return 1; + } + value = malloc(sizeof(*value)); + if (value == NULL) + return -ENOMEM; + value->text = text; + list_add_tail(&value->list, list); + return 0; +} + +/** + * \brief Find all values for given identifier + * \param list Returned list + * \param source Source list with ucm_value structures + * \return Zero if success, otherwise a negative error code + */ +static int add_identifiers(struct list_head *list, + struct list_head *source) +{ + struct ucm_value *v; + struct list_head *pos; + int err; + + list_for_each(pos, source) { + v = list_entry(pos, struct ucm_value, list); + err = merge_value(list, v->name); + if (err < 0) + return err; + } + return 0; +} + +/** + * \brief Find all values for given identifier + * \param list Returned list + * \param identifier Identifier + * \param source Source list with ucm_value structures + */ static int add_values(struct list_head *list, const char *identifier, struct list_head *source) { - struct ucm_value *v; - struct myvalue *val; - struct list_head *pos, *pos1; - int match; + struct ucm_value *v; + struct list_head *pos; + int err; - 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; - val->value = v->data; - list_add_tail(&val->list, list); - } - } - } - return 0; + list_for_each(pos, source) { + v = list_entry(pos, struct ucm_value, list); + if (check_identifier(identifier, v->name)) { + err = merge_value(list, v->data); + if (err < 0) + return err; + } + } + return 0; +} + +/** + * \brief compare two identifiers + */ +static int identifier_cmp(const void *_a, const void *_b) +{ + const char * const *a = _a; + const char * const *b = _b; + return strcmp(*a, *b); +} + +/** + * \brief Get list of available identifiers + * \param list Returned list + * \param name Name of verb or modifier to query + * \return Number of list entries if success, otherwise a negative error code + */ +static int get_identifiers_list(snd_use_case_mgr_t *uc_mgr, + const char **list[], char *name) +{ + struct use_case_verb *verb; + struct use_case_modifier *modifier; + struct use_case_device *device; + struct list_head mylist; + struct list_head *value_list; + char *str, **res; + int err; + + if (!name) + return -ENOENT; + + str = strchr(name, '/'); + if (str) { + *str = '\0'; + verb = find_verb(uc_mgr, str + 1); + } + else { + verb = uc_mgr->active_verb; + } + if (!verb) + return -ENOENT; + + value_list = NULL; + modifier = find_modifier(uc_mgr, verb, name, 0); + if (modifier) { + value_list = &modifier->value_list; + } else { + device = find_device(uc_mgr, verb, name, 0); + if (device) + value_list = &device->value_list; + } + if (value_list == NULL) + return -ENOENT; + + INIT_LIST_HEAD(&mylist); + err = add_identifiers(&mylist, &uc_mgr->value_list); + if (err < 0) + goto __fail; + err = add_identifiers(&mylist, &verb->value_list); + if (err < 0) + goto __fail; + err = add_identifiers(&mylist, value_list); + if (err < 0) + goto __fail; + err = myvalue_to_str_list(&mylist, &res); + if (err > 0) + *list = (const char **)res; + else if (err == 0) + *list = NULL; +__fail: + myvalue_list_free(&mylist); + if (err <= 0) + return err; + qsort(*list, err, sizeof(char *), identifier_cmp); + return err; } /** @@ -1252,8 +1416,7 @@ static int get_value_list(snd_use_case_mgr_t *uc_mgr, const char **list[], char *verbname) { - struct list_head mylist, *pos, *npos; - struct myvalue *val; + struct list_head mylist, *pos; struct use_case_verb *verb; struct use_case_device *dev; struct use_case_modifier *mod; @@ -1286,26 +1449,13 @@ static int get_value_list(snd_use_case_mgr_t *uc_mgr, if (err < 0) goto __fail; } - err = alloc_str_list(&mylist, 1, &res); - if (err >= 0) { + err = myvalue_to_str_list(&mylist, &res); + if (err > 0) *list = (const char **)res; - 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++; - } - } + else if (err == 0) + *list = NULL; __fail: - list_for_each_safe(pos, npos, &mylist) { - val = list_entry(pos, struct myvalue, list); - list_del(&val->list); - free(val); - } + myvalue_list_free(&mylist); return err; } @@ -1375,21 +1525,23 @@ int snd_use_case_get_list(snd_use_case_mgr_t *uc_mgr, } else { str = NULL; } - if (check_identifier(identifier, "_devices")) - err = get_device_list(uc_mgr, list, str); + 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 if (check_identifier(identifier, "_supporteddevs")) - err = get_supported_device_list(uc_mgr, list, str); - else if (check_identifier(identifier, "_conflictingdevs")) - err = get_conflicting_device_list(uc_mgr, list, str); + err = get_modifier_list(uc_mgr, list, str); + else if (check_identifier(identifier, "_identifiers")) + err = get_identifiers_list(uc_mgr, list, str); + else if (check_identifier(identifier, "_supporteddevs")) + err = get_supported_device_list(uc_mgr, list, str); + else if (check_identifier(identifier, "_conflictingdevs")) + err = get_conflicting_device_list(uc_mgr, list, str); else if (identifier[0] == '_') err = -ENOENT; - else - err = get_value_list(uc_mgr, identifier, list, str); - if (str) - free(str); - } + else + err = get_value_list(uc_mgr, identifier, list, str); + if (str) + free(str); + } __end: pthread_mutex_unlock(&uc_mgr->mutex); return err; @@ -1963,8 +2115,10 @@ int snd_use_case_parse_selem_id(snd_mixer_selem_id_t *dst, const char *ucm_id, const char *value) { +#ifdef BUILD_MIXER if (strcmp(ucm_id, "PlaybackMixerId") == 0 || strcmp(ucm_id, "CaptureMixerId") == 0) return snd_mixer_selem_id_parse(dst, value); +#endif return -EINVAL; } diff --git a/src/ucm/main.c.alsa-git b/src/ucm/main.c.alsa-git new file mode 100644 index 0000000..b0b6ffb --- /dev/null +++ b/src/ucm/main.c.alsa-git @@ -0,0 +1,1970 @@ +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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 +#include +#include +#include +#include + +/* + * misc + */ + +static int get_value1(snd_use_case_mgr_t *uc_mgr, char **value, + struct list_head *value_list, const char *identifier); +static int get_value3(snd_use_case_mgr_t *uc_mgr, + char **value, + const char *identifier, + struct list_head *value_list1, + struct list_head *value_list2, + struct list_head *value_list3); + +static int execute_component_seq(snd_use_case_mgr_t *uc_mgr, + struct component_sequence *cmpt_seq, + struct list_head *value_list1, + struct list_head *value_list2, + struct list_head *value_list3, + char *cdev); + +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) { + *result = NULL; + 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; +} + +static int read_tlv_file(unsigned int **res, + const char *filepath) +{ + int err = 0; + int fd; + struct stat st; + size_t sz; + ssize_t sz_read; + struct snd_ctl_tlv *tlv; + + fd = open(filepath, O_RDONLY); + if (fd < 0) { + err = -errno; + return err; + } + if (fstat(fd, &st) == -1) { + err = -errno; + goto __fail; + } + sz = st.st_size; + if (sz > 16 * 1024 * 1024 || sz < 8 || sz % 4) { + uc_error("File size should be less than 16 MB " + "and multiple of 4"); + err = -EINVAL; + goto __fail; + } + *res = malloc(sz); + if (res == NULL) { + err = -ENOMEM; + goto __fail; + } + sz_read = read(fd, *res, sz); + if (sz_read < 0 || (size_t)sz_read != sz) { + err = -EIO; + free(*res); + *res = NULL; + } + /* Check if the tlv file specifies valid size. */ + tlv = (struct snd_ctl_tlv *)(*res); + if (tlv->length + 2 * sizeof(unsigned int) != sz) { + uc_error("Invalid tlv size: %d", tlv->length); + err = -EINVAL; + free(*res); + *res = NULL; + } + +__fail: + close(fd); + return err; +} + +static int binary_file_parse(snd_ctl_elem_value_t *dst, + snd_ctl_elem_info_t *info, + const char *filepath) +{ + int err = 0; + int fd; + struct stat st; + size_t sz; + ssize_t sz_read; + char *res; + snd_ctl_elem_type_t type; + unsigned int idx, count; + + type = snd_ctl_elem_info_get_type(info); + if (type != SND_CTL_ELEM_TYPE_BYTES) { + uc_error("only support byte type!"); + err = -EINVAL; + return err; + } + fd = open(filepath, O_RDONLY); + if (fd < 0) { + err = -errno; + return err; + } + if (stat(filepath, &st) == -1) { + err = -errno; + goto __fail; + } + sz = st.st_size; + count = snd_ctl_elem_info_get_count(info); + if (sz != count || sz > sizeof(dst->value.bytes)) { + uc_error("invalid parameter size %d!", sz); + err = -EINVAL; + goto __fail; + } + res = malloc(sz); + if (res == NULL) { + err = -ENOMEM; + goto __fail; + } + sz_read = read(fd, res, sz); + if (sz_read < 0 || (size_t)sz_read != sz) { + err = -errno; + goto __fail_read; + } + for (idx = 0; idx < sz; idx++) + snd_ctl_elem_value_set_byte(dst, idx, *(res + idx)); + __fail_read: + free(res); + __fail: + close(fd); + return err; +} + +extern int __snd_ctl_ascii_elem_id_parse(snd_ctl_elem_id_t *dst, + const char *str, + const char **ret_ptr); + +static int execute_cset(snd_ctl_t *ctl, const char *cset, unsigned int type) +{ + const char *pos; + int err; + snd_ctl_elem_id_t *id; + snd_ctl_elem_value_t *value; + snd_ctl_elem_info_t *info; + unsigned int *res = NULL; + + snd_ctl_elem_id_malloc(&id); + snd_ctl_elem_value_malloc(&value); + snd_ctl_elem_info_malloc(&info); + + err = __snd_ctl_ascii_elem_id_parse(id, cset, &pos); + if (err < 0) + goto __fail; + while (*pos && isspace(*pos)) + pos++; + if (!*pos) { + uc_error("undefined value for cset >%s<", cset); + err = -EINVAL; + goto __fail; + } + snd_ctl_elem_info_set_id(info, id); + err = snd_ctl_elem_info(ctl, info); + if (err < 0) + goto __fail; + if (type == SEQUENCE_ELEMENT_TYPE_CSET_TLV) { + if (!snd_ctl_elem_info_is_tlv_writable(info)) { + err = -EINVAL; + goto __fail; + } + err = read_tlv_file(&res, pos); + if (err < 0) + goto __fail; + err = snd_ctl_elem_tlv_write(ctl, id, res); + if (err < 0) + goto __fail; + } else { + snd_ctl_elem_value_set_id(value, id); + err = snd_ctl_elem_read(ctl, value); + if (err < 0) + goto __fail; + if (type == SEQUENCE_ELEMENT_TYPE_CSET_BIN_FILE) + err = binary_file_parse(value, info, pos); + else + err = snd_ctl_ascii_value_parse(ctl, value, info, pos); + if (err < 0) + goto __fail; + err = snd_ctl_elem_write(ctl, value); + if (err < 0) + goto __fail; + } + err = 0; + __fail: + if (id != NULL) + free(id); + if (value != NULL) + free(value); + if (info != NULL) + free(info); + if (res != NULL) + free(res); + + 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, + 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: + cdev = strdup(s->data.cdev); + if (cdev == NULL) + goto __fail_nomem; + break; + case SEQUENCE_ELEMENT_TYPE_CSET: + case SEQUENCE_ELEMENT_TYPE_CSET_BIN_FILE: + case SEQUENCE_ELEMENT_TYPE_CSET_TLV: + if (cdev == NULL && uc_mgr->in_component_domain) { + /* For sequence of a component device, use + * its parent's cdev stored by ucm manager. + */ + if (uc_mgr->cdev == NULL) { + uc_error("cdev is not defined!"); + return err; + } + + cdev = strndup(uc_mgr->cdev, PATH_MAX); + if (!cdev) + return -ENOMEM; + } else if (cdev == NULL) { + char *playback_ctl = NULL; + char *capture_ctl = NULL; + + err = get_value3(uc_mgr, &playback_ctl, "PlaybackCTL", + value_list1, + value_list2, + value_list3); + if (err < 0 && err != -ENOENT) { + uc_error("cdev is not defined!"); + return err; + } + err = get_value3(uc_mgr, &capture_ctl, "CaptureCTL", + value_list1, + value_list2, + value_list3); + if (err < 0 && err != -ENOENT) { + free(playback_ctl); + uc_error("cdev is not defined!"); + return err; + } + if (playback_ctl == NULL && + capture_ctl == NULL) { + uc_error("cdev is not defined!"); + return -EINVAL; + } + if (playback_ctl != NULL && + capture_ctl != NULL && + strcmp(playback_ctl, capture_ctl) != 0) { + free(playback_ctl); + free(capture_ctl); + uc_error("cdev is not equal for playback and capture!"); + return -EINVAL; + } + if (playback_ctl != NULL) { + cdev = playback_ctl; + free(capture_ctl); + } else { + cdev = capture_ctl; + } + } + if (ctl == NULL) { + err = uc_mgr_open_ctl(uc_mgr, &ctl, cdev); + if (err < 0) { + uc_error("unable to open ctl device '%s'", cdev); + goto __fail; + } + } + err = execute_cset(ctl, s->data.cset, s->type); + if (err < 0) { + uc_error("unable to execute cset '%s'", s->data.cset); + goto __fail; + } + break; + case SEQUENCE_ELEMENT_TYPE_SLEEP: + usleep(s->data.sleep); + break; + case SEQUENCE_ELEMENT_TYPE_EXEC: + err = system(s->data.exec); + if (err < 0) + goto __fail; + break; + case SEQUENCE_ELEMENT_TYPE_CMPT_SEQ: + /* Execute enable or disable sequence of a component + * device. Pass the cdev defined by the machine device. + */ + err = execute_component_seq(uc_mgr, + &s->data.cmpt_seq, + value_list1, + value_list2, + value_list3, + cdev); + if (err < 0) + goto __fail; + break; + default: + uc_error("unknown sequence command %i", s->type); + break; + } + } + free(cdev); + return 0; + __fail_nomem: + err = -ENOMEM; + __fail: + free(cdev); + return err; + +} + +/* Execute enable or disable sequence of a component device. + * + * For a component device (a codec or embedded DSP), its sequence doesn't + * specify the sound card device 'cdev', because a component can be reused + * by different sound cards (machines). So when executing its sequence, a + * parameter 'cdev' is used to pass cdev defined by the sequence of its + * parent, the machine device. UCM manger will store the cdev when entering + * the component domain. + */ +static int execute_component_seq(snd_use_case_mgr_t *uc_mgr, + struct component_sequence *cmpt_seq, + struct list_head *value_list1 ATTRIBUTE_UNUSED, + struct list_head *value_list2 ATTRIBUTE_UNUSED, + struct list_head *value_list3 ATTRIBUTE_UNUSED, + char *cdev) +{ + struct use_case_device *device = cmpt_seq->device; + struct list_head *seq; + int err; + + /* enter component domain and store cdev for the component */ + uc_mgr->in_component_domain = 1; + uc_mgr->cdev = cdev; + + /* choose enable or disable sequence of the component device */ + if (cmpt_seq->enable) + seq = &device->enable_list; + else + seq = &device->disable_list; + + /* excecute the sequence of the component dev */ + err = execute_sequence(uc_mgr, seq, + &device->value_list, + &uc_mgr->active_verb->value_list, + &uc_mgr->value_list); + + /* exit component domain and clear cdev */ + uc_mgr->in_component_domain = 0; + uc_mgr->cdev = NULL; + + return err; +} + +static int add_auto_value(snd_use_case_mgr_t *uc_mgr, const char *key, char *value) +{ + char *s; + int err; + + err = get_value1(uc_mgr, &value, &uc_mgr->value_list, key); + if (err == -ENOENT) { + s = strdup(value); + if (s == NULL) + return -ENOMEM; + return uc_mgr_add_value(&uc_mgr->value_list, key, s); + } else if (err < 0) { + return err; + } + free(value); + return 0; +} + +static int add_auto_values(snd_use_case_mgr_t *uc_mgr) +{ + struct ctl_list *ctl_list; + const char *id; + char buf[40]; + int err; + + ctl_list = uc_mgr_get_one_ctl(uc_mgr); + if (ctl_list) { + id = snd_ctl_card_info_get_id(ctl_list->ctl_info); + snprintf(buf, sizeof(buf), "hw:%s", id); + err = add_auto_value(uc_mgr, "PlaybackCTL", buf); + if (err < 0) + return err; + err = add_auto_value(uc_mgr, "CaptureCTL", buf); + if (err < 0) + return err; + } + 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 = add_auto_values(uc_mgr); + if (err < 0) + return err; + 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; +} + +/** + * \brief Universal find - string in a list + * \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(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(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) { + *result = NULL; + 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) { + *result = NULL; + 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 + * \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 *verb_name) +{ + return find(&uc_mgr->verb_list, + struct use_case_verb, list, name, + verb_name); +} + +static int is_devlist_supported(snd_use_case_mgr_t *uc_mgr, + struct dev_list *dev_list) +{ + struct dev_list_node *device; + struct use_case_device *adev; + struct list_head *pos, *pos1; + int found_ret; + + switch (dev_list->type) { + case DEVLIST_NONE: + default: + return 1; + case DEVLIST_SUPPORTED: + found_ret = 1; + break; + case DEVLIST_CONFLICTING: + found_ret = 0; + break; + } + + list_for_each(pos, &dev_list->list) { + device = list_entry(pos, struct dev_list_node, list); + + list_for_each(pos1, &uc_mgr->active_devices) { + adev = list_entry(pos1, struct use_case_device, + active_list); + if (!strcmp(device->name, adev->name)) + return found_ret; + } + } + return 1 - found_ret; +} + +static inline int is_modifier_supported(snd_use_case_mgr_t *uc_mgr, + struct use_case_modifier *modifier) +{ + return is_devlist_supported(uc_mgr, &modifier->dev_list); +} + +static inline int is_device_supported(snd_use_case_mgr_t *uc_mgr, + struct use_case_device *device) +{ + return is_devlist_supported(uc_mgr, &device->dev_list); +} + +/** + * \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(snd_use_case_mgr_t *uc_mgr, struct use_case_verb *verb, + const char *device_name, int check_supported) +{ + struct use_case_device *device; + struct list_head *pos; + + list_for_each(pos, &verb->device_list) { + device = list_entry(pos, struct use_case_device, list); + + if (strcmp(device_name, device->name)) + continue; + + if (check_supported && + !is_device_supported(uc_mgr, device)) + continue; + + return device; + } + return NULL; +} + +/** + * \brief Find modifier + * \param verb Use case verb + * \param modifier_name modifier to find + * \return structure on success, otherwise a NULL (not found) + */ +static struct use_case_modifier * + find_modifier(snd_use_case_mgr_t *uc_mgr, struct use_case_verb *verb, + const char *modifier_name, int check_supported) +{ + struct use_case_modifier *modifier; + struct list_head *pos; + + list_for_each(pos, &verb->modifier_list) { + modifier = list_entry(pos, struct use_case_modifier, list); + + if (strcmp(modifier->name, modifier_name)) + continue; + + if (check_supported && + !is_modifier_supported(uc_mgr, modifier)) + continue; + + return modifier; + } + return NULL; +} + +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 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, + 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, + &verb->value_list, + &uc_mgr->value_list, + NULL); + if (enable && err >= 0) + uc_mgr->active_verb = verb; + 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 (modifier_status(uc_mgr, modifier->name) == enable) + return 0; + + if (enable) { + seq = &modifier->enable_list; + } else { + seq = &modifier->disable_list; + } + 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) { + 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 (device_status(uc_mgr, device->name) == enable) + return 0; + + if (enable) { + seq = &device->enable_list; + } else { + seq = &device->disable_list; + } + 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) { + list_del(&device->active_list); + } + 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 **uc_mgr, + const char *card_name) +{ + snd_use_case_mgr_t *mgr; + int err; + + /* create a new UCM */ + mgr = calloc(1, sizeof(snd_use_case_mgr_t)); + if (mgr == NULL) + return -ENOMEM; + INIT_LIST_HEAD(&mgr->verb_list); + INIT_LIST_HEAD(&mgr->default_list); + INIT_LIST_HEAD(&mgr->value_list); + INIT_LIST_HEAD(&mgr->active_modifiers); + INIT_LIST_HEAD(&mgr->active_devices); + INIT_LIST_HEAD(&mgr->ctl_list); + pthread_mutex_init(&mgr->mutex, NULL); + + mgr->card_name = strdup(card_name); + if (mgr->card_name == NULL) { + free(mgr); + return -ENOMEM; + } + + /* get info on use_cases and verify against card */ + err = import_master_config(mgr); + if (err < 0) { + uc_error("error: failed to import %s use case configuration %d", + card_name, err); + goto err; + } + + *uc_mgr = mgr; + return 0; + +err: + uc_mgr_free(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"); + 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; +} + +/* + * Tear down current use case verb, device and modifier. + */ +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; + + list_for_each_safe(pos, npos, &uc_mgr->active_modifiers) { + modifier = list_entry(pos, struct use_case_modifier, + active_list); + err = set_modifier(uc_mgr, modifier, 0); + 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 = 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 = 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; + } + uc_mgr->active_verb = NULL; + + err = execute_sequence(uc_mgr, &uc_mgr->default_list, + &uc_mgr->value_list, NULL, NULL); + + 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, + &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; + pthread_mutex_unlock(&uc_mgr->mutex); + return err; +} + +/** + * \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 + */ +static int get_verb_list(snd_use_case_mgr_t *uc_mgr, const char **list[]) +{ + return get_list2(&uc_mgr->verb_list, list, + struct use_case_verb, list, + name, comment); +} + +/** + * \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 + */ +static int get_device_list(snd_use_case_mgr_t *uc_mgr, const char **list[], + char *verbname) +{ + 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); +} + +/** + * \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 + */ +static int get_modifier_list(snd_use_case_mgr_t *uc_mgr, const char **list[], + char *verbname) +{ + 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); +} + +/** + * \brief Get list of supported/conflicting devices + * \param list Returned list + * \param name Name of modifier or verb to query + * \param type Type of device list entries to return + * \return Number of list entries if success, otherwise a negative error code + */ +static int get_supcon_device_list(snd_use_case_mgr_t *uc_mgr, + const char **list[], char *name, + enum dev_list_type type) +{ + char *str; + struct use_case_verb *verb; + struct use_case_modifier *modifier; + struct use_case_device *device; + + if (!name) + return -ENOENT; + + str = strchr(name, '/'); + if (str) { + *str = '\0'; + verb = find_verb(uc_mgr, str + 1); + } + else { + verb = uc_mgr->active_verb; + } + if (!verb) + return -ENOENT; + + modifier = find_modifier(uc_mgr, verb, name, 0); + if (modifier) { + if (modifier->dev_list.type != type) + return 0; + return get_list(&modifier->dev_list.list, list, + struct dev_list_node, list, + name); + } + + device = find_device(uc_mgr, verb, name, 0); + if (device) { + if (device->dev_list.type != type) + return 0; + return get_list(&device->dev_list.list, list, + struct dev_list_node, list, + name); + } + + return -ENOENT; + +} + +/** + * \brief Get list of supported devices + * \param list Returned list + * \param name Name of verb or modifier to query + * \return Number of list entries if success, otherwise a negative error code + */ +static int get_supported_device_list(snd_use_case_mgr_t *uc_mgr, + const char **list[], char *name) +{ + return get_supcon_device_list(uc_mgr, list, name, DEVLIST_SUPPORTED); +} + +/** + * \brief Get list of conflicting devices + * \param list Returned list + * \param name Name of verb or modifier to query + * \return Number of list entries if success, otherwise a negative error code + */ +static int get_conflicting_device_list(snd_use_case_mgr_t *uc_mgr, + const char **list[], char *name) +{ + return get_supcon_device_list(uc_mgr, list, name, DEVLIST_CONFLICTING); +} + +#ifndef DOC_HIDDEN +struct myvalue { + struct list_head list; + char *value; +}; +#endif + +static int add_values(struct list_head *list, + const char *identifier, + struct list_head *source) +{ + 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; + val->value = v->data; + list_add_tail(&val->list, list); + } + } + } + return 0; +} + +/** + * \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 + */ +static int get_value_list(snd_use_case_mgr_t *uc_mgr, + const char *identifier, + const char **list[], + char *verbname) +{ + 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, &uc_mgr->value_list); + if (err < 0) + goto __fail; + 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); + if (err >= 0) { + *list = (const char **)res; + 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 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 uc_mgr_scan_master_configs(list); + pthread_mutex_lock(&uc_mgr->mutex); + if (strcmp(identifier, "_verbs") == 0) + err = get_verb_list(uc_mgr, list); + else if (strcmp(identifier, "_enadevs") == 0) + err = get_enabled_device_list(uc_mgr, list); + else if (strcmp(identifier, "_enamods") == 0) + 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 if (check_identifier(identifier, "_supporteddevs")) + err = get_supported_device_list(uc_mgr, list, str); + else if (check_identifier(identifier, "_conflictingdevs")) + err = get_conflicting_device_list(uc_mgr, list, str); + else if (identifier[0] == '_') + err = -ENOENT; + 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(snd_use_case_mgr_t *uc_mgr, char **value, + struct list_head *value_list, const char *identifier) +{ + 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)) { + if (uc_mgr->conf_format < 2) { + *value = strdup(val->data); + if (*value == NULL) + return -ENOMEM; + return 0; + } + return uc_mgr_get_substituted_value(uc_mgr, value, val->data); + } + } + return -ENOENT; +} + +static int get_value3(snd_use_case_mgr_t *uc_mgr, + 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(uc_mgr, value, value_list1, identifier); + if (err >= 0 || err != -ENOENT) + return err; + err = get_value1(uc_mgr, value, value_list2, identifier); + if (err >= 0 || err != -ENOENT) + return err; + err = get_value1(uc_mgr, value, value_list3, identifier); + if (err >= 0 || err != -ENOENT) + return err; + return -ENOENT; +} + +/** + * \brief Get value + * \param uc_mgr Use case manager + * \param identifier Value identifier (string) + * \param value Returned value string + * \param item Modifier or Device 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, + char **value, + const char *mod_dev_name, + const char *verb_name, + int exact) +{ + struct use_case_verb *verb; + struct use_case_modifier *mod; + struct use_case_device *dev; + int err; + + if (mod_dev_name || verb_name || !exact) { + if (verb_name && strlen(verb_name)) { + verb = find_verb(uc_mgr, verb_name); + } else { + verb = uc_mgr->active_verb; + } + if (verb) { + if (mod_dev_name) { + mod = find_modifier(uc_mgr, verb, + mod_dev_name, 0); + if (mod) { + err = get_value1(uc_mgr, value, + &mod->value_list, + identifier); + if (err >= 0 || err != -ENOENT) + return err; + } + + dev = find_device(uc_mgr, verb, + mod_dev_name, 0); + if (dev) { + err = get_value1(uc_mgr, value, + &dev->value_list, + identifier); + if (err >= 0 || err != -ENOENT) + return err; + } + + if (exact) + return -ENOENT; + } + + err = get_value1(uc_mgr, value, &verb->value_list, identifier); + if (err >= 0 || err != -ENOENT) + return err; + } + + if (exact) + return -ENOENT; + } + + err = get_value1(uc_mgr, value, &uc_mgr->value_list, identifier); + if (err >= 0 || err != -ENOENT) + return err; + + return -ENOENT; +} + +/** + * \brief Get current - string + * \param uc_mgr Use case manager + * \param identifier + * \param value Value pointer + * \return Zero if success, otherwise a negative error code + * + * 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) +{ + const char *slash1, *slash2, *mod_dev_after; + const char *ident, *mod_dev, *verb; + int exact = 0; + int err; + + pthread_mutex_lock(&uc_mgr->mutex); + 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) { + err = -ENOENT; + goto __end; + } + *value = strdup(uc_mgr->active_verb->name); + if (*value == NULL) { + err = -ENOMEM; + goto __end; + } + err = 0; + } else if (strcmp(identifier, "_file") == 0) { + /* get the conf file name of the opened card */ + if ((uc_mgr->card_name == NULL) + || (uc_mgr->conf_file_name[0] == '\0')) { + err = -ENOENT; + goto __end; + } + *value = strndup(uc_mgr->conf_file_name, MAX_FILE); + if (*value == NULL) { + err = -ENOMEM; + goto __end; + } + err = 0; + + } else if (identifier[0] == '_') { + err = -ENOENT; + goto __end; + } else { + if (identifier[0] == '=') { + exact = 1; + identifier++; + } + + slash1 = strchr(identifier, '/'); + if (slash1) { + ident = strndup(identifier, slash1 - identifier); + + slash2 = strchr(slash1 + 1, '/'); + if (slash2) { + mod_dev_after = slash2; + verb = slash2 + 1; + } + else { + mod_dev_after = slash1 + strlen(slash1); + verb = NULL; + } + + if (mod_dev_after == slash1 + 1) + mod_dev = NULL; + else + mod_dev = strndup(slash1 + 1, + mod_dev_after - (slash1 + 1)); + } + else { + ident = identifier; + mod_dev = NULL; + verb = NULL; + } + + err = get_value(uc_mgr, ident, (char **)value, mod_dev, verb, + exact); + if (ident != identifier) + free((void *)ident); + if (mod_dev) + free((void *)mod_dev); + } + __end: + pthread_mutex_unlock(&uc_mgr->mutex); + return err; +} + + +/** + * \brief Get current - integer + * \param uc_mgr Use case manager + * \param identifier + * \return Value if success, otherwise a negative error code + */ +int snd_use_case_geti(snd_use_case_mgr_t *uc_mgr, + const char *identifier, + long *value) +{ + 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")) { + if (!str) { + err = -EINVAL; + goto __end; + } + err = device_status(uc_mgr, str); + if (err >= 0) { + *value = err; + err = 0; + } + } else if (check_identifier(identifier, "_modstatus")) { + if (!str) { + err = -EINVAL; + goto __end; + } + err = modifier_status(uc_mgr, str); + if (err >= 0) { + *value = err; + err = 0; + } +#if 0 + /* + * enable this block if the else clause below is expanded to query + * user-supplied values + */ + } else if (identifier[0] == '_') + err = -ENOENT; +#endif + } else + err = -ENOENT; + if (str) + free(str); + } + __end: + pthread_mutex_unlock(&uc_mgr->mutex); + 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, + &uc_mgr->active_verb->value_list, + &uc_mgr->value_list, + NULL); + 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 = 0; + + 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, uc_mgr->active_verb, device_name, 1); + 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, uc_mgr->active_verb, modifier_name, 1); + 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, uc_mgr->active_verb, old_device, 1); + if (xold == NULL) + return -ENOENT; + list_del(&xold->active_list); + xnew = find_device(uc_mgr, uc_mgr->active_verb, new_device, 1); + list_add_tail(&xold->active_list, &uc_mgr->active_devices); + if (xnew == 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, + &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); + } + 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, 1); + 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, uc_mgr->active_verb, old_modifier, 1); + if (xold == NULL) + return -ENOENT; + xnew = find_modifier(uc_mgr, uc_mgr->active_verb, new_modifier, 1); + if (xnew == 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, + &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); + } + 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, 1); + if (err < 0) + return err; + } + return err; +} + +/** + * \brief Set new + * \param uc_mgr Use case manager + * \param identifier + * \param value Value + * \return Zero if success, otherwise a negative error code + */ +int snd_use_case_set(snd_use_case_mgr_t *uc_mgr, + const char *identifier, + const char *value) +{ + char *str, *str1; + int err = 0; + + pthread_mutex_lock(&uc_mgr->mutex); + 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 { + err = -EINVAL; + goto __end; + } + 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 err; +} + +/** + * \brief Parse control element identifier + * \param elem_id Element identifier + * \param ucm_id Use case identifier + * \param value String value to be parsed + * \return Zero if success, otherwise a negative error code + */ +int snd_use_case_parse_ctl_elem_id(snd_ctl_elem_id_t *dst, + const char *ucm_id, + const char *value) +{ + snd_ctl_elem_iface_t iface; + int jack_control; + + jack_control = strcmp(ucm_id, "JackControl") == 0; + if (!jack_control && + strcmp(ucm_id, "PlaybackVolume") && + strcmp(ucm_id, "PlaybackSwitch") && + strcmp(ucm_id, "CaptureVolume") && + strcmp(ucm_id, "CaptureSwitch")) + return -EINVAL; + snd_ctl_elem_id_clear(dst); + if (strcasestr(ucm_id, "name=")) + return __snd_ctl_ascii_elem_id_parse(dst, value, NULL); + iface = SND_CTL_ELEM_IFACE_MIXER; + if (jack_control) + iface = SND_CTL_ELEM_IFACE_CARD; + snd_ctl_elem_id_set_interface(dst, iface); + snd_ctl_elem_id_set_name(dst, value); + return 0; +} + +/** + * \brief Parse mixer element identifier + * \param dst Simple mixer element identifier + * \param ucm_id Use case identifier + * \param value String value to be parsed + * \return Zero if success, otherwise a negative error code + */ +int snd_use_case_parse_selem_id(snd_mixer_selem_id_t *dst, + const char *ucm_id, + const char *value) +{ + if (strcmp(ucm_id, "PlaybackMixerId") == 0 || + strcmp(ucm_id, "CaptureMixerId") == 0) + return snd_mixer_selem_id_parse(dst, value); + return -EINVAL; +} diff --git a/src/ucm/ucm_subs.c b/src/ucm/ucm_subs.c index 00afa9e..90e395f 100644 --- a/src/ucm/ucm_subs.c +++ b/src/ucm/ucm_subs.c @@ -25,6 +25,7 @@ */ #include "ucm_local.h" +#include #include #include @@ -145,10 +146,11 @@ static char *rval_sysfs(snd_use_case_mgr_t *uc_mgr ATTRIBUTE_UNUSED, const char return strdup(path); } -#define MATCH_VARIABLE(name, id, fcn) \ +#define MATCH_VARIABLE(name, id, fcn, empty_ok) \ if (strncmp((name), (id), sizeof(id) - 1) == 0) { \ rval = fcn(uc_mgr); \ idsize = sizeof(id) - 1; \ + allow_empty = (empty_ok); \ goto __rval; \ } @@ -189,12 +191,14 @@ int uc_mgr_get_substituted_value(snd_use_case_mgr_t *uc_mgr, while (*value) { if (*value == '$' && *(value+1) == '{') { - MATCH_VARIABLE(value, "${ConfName}", rval_conf_name); - MATCH_VARIABLE(value, "${CardId}", rval_card_id); - MATCH_VARIABLE(value, "${CardDriver}", rval_card_driver); - MATCH_VARIABLE(value, "${CardName}", rval_card_name); - MATCH_VARIABLE(value, "${CardLongName}", rval_card_longname); - MATCH_VARIABLE(value, "${CardComponents}", rval_card_components); + bool allow_empty = false; + + MATCH_VARIABLE(value, "${ConfName}", rval_conf_name, false); + MATCH_VARIABLE(value, "${CardId}", rval_card_id, false); + MATCH_VARIABLE(value, "${CardDriver}", rval_card_driver, false); + MATCH_VARIABLE(value, "${CardName}", rval_card_name, false); + MATCH_VARIABLE(value, "${CardLongName}", rval_card_longname, false); + MATCH_VARIABLE(value, "${CardComponents}", rval_card_components, true); MATCH_VARIABLE2(value, "${env:", rval_env); MATCH_VARIABLE2(value, "${sys:", rval_sysfs); err = -EINVAL; @@ -208,7 +212,7 @@ int uc_mgr_get_substituted_value(snd_use_case_mgr_t *uc_mgr, } goto __error; __rval: - if (rval == NULL || rval[0] == '\0') { + if (rval == NULL || (!allow_empty && rval[0] == '\0')) { free(rval); strncpy(r, value, idsize); r[idsize] = '\0'; diff --git a/src/ucm/ucm_subs.c.alsa-git b/src/ucm/ucm_subs.c.alsa-git new file mode 100644 index 0000000..00afa9e --- /dev/null +++ b/src/ucm/ucm_subs.c.alsa-git @@ -0,0 +1,248 @@ +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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) 2019 Red Hat Inc. + * Authors: Jaroslav Kysela + */ + +#include "ucm_local.h" +#include +#include + +static char *rval_conf_name(snd_use_case_mgr_t *uc_mgr) +{ + if (uc_mgr->conf_file_name[0]) + return strdup(uc_mgr->conf_file_name); + return NULL; +} + +static char *rval_card_id(snd_use_case_mgr_t *uc_mgr) +{ + struct ctl_list *ctl_list; + + ctl_list = uc_mgr_get_one_ctl(uc_mgr); + if (ctl_list == NULL) + return NULL; + return strdup(snd_ctl_card_info_get_id(ctl_list->ctl_info)); +} + +static char *rval_card_driver(snd_use_case_mgr_t *uc_mgr) +{ + struct ctl_list *ctl_list; + + ctl_list = uc_mgr_get_one_ctl(uc_mgr); + if (ctl_list == NULL) + return NULL; + return strdup(snd_ctl_card_info_get_driver(ctl_list->ctl_info)); +} + +static char *rval_card_name(snd_use_case_mgr_t *uc_mgr) +{ + struct ctl_list *ctl_list; + + ctl_list = uc_mgr_get_one_ctl(uc_mgr); + if (ctl_list == NULL) + return NULL; + return strdup(snd_ctl_card_info_get_name(ctl_list->ctl_info)); +} + +static char *rval_card_longname(snd_use_case_mgr_t *uc_mgr) +{ + struct ctl_list *ctl_list; + + ctl_list = uc_mgr_get_one_ctl(uc_mgr); + if (ctl_list == NULL) + return NULL; + return strdup(snd_ctl_card_info_get_longname(ctl_list->ctl_info)); +} + +static char *rval_card_components(snd_use_case_mgr_t *uc_mgr) +{ + struct ctl_list *ctl_list; + + ctl_list = uc_mgr_get_one_ctl(uc_mgr); + if (ctl_list == NULL) + return NULL; + return strdup(snd_ctl_card_info_get_components(ctl_list->ctl_info)); +} + +static char *rval_env(snd_use_case_mgr_t *uc_mgr ATTRIBUTE_UNUSED, const char *id) +{ + char *e; + + e = getenv(id); + if (e) + return strdup(e); + return NULL; +} + +static char *rval_sysfs(snd_use_case_mgr_t *uc_mgr ATTRIBUTE_UNUSED, const char *id) +{ + char path[PATH_MAX], link[PATH_MAX + 1]; + struct stat sb; + ssize_t len; + char *e; + int fd; + + e = getenv("SYSFS_PATH"); + if (e == NULL) + e = "/sys"; + if (id[0] == '/') + id++; + snprintf(path, sizeof(path), "%s/%s", e, id); + if (lstat(path, &sb) != 0) + return NULL; + if (S_ISLNK(sb.st_mode)) { + len = readlink(path, link, sizeof(link) - 1); + if (len <= 0) { + uc_error("sysfs: cannot read link '%s' (%d)", path, errno); + return NULL; + } + link[len] = '\0'; + e = strrchr(link, '/'); + if (e) + return strdup(e + 1); + return NULL; + } + if (S_ISDIR(sb.st_mode)) + return NULL; + if ((sb.st_mode & S_IRUSR) == 0) + return NULL; + + fd = open(path, O_RDONLY); + if (fd < 0) { + uc_error("sysfs open failed for '%s' (%d)", path, errno); + return NULL; + } + len = read(fd, path, sizeof(path)-1); + close(fd); + if (len < 0) { + uc_error("sysfs unable to read value '%s' (%d)", path, errno); + return NULL; + } + while (len > 0 && path[len-1] == '\n') + len--; + path[len] = '\0'; + return strdup(path); +} + +#define MATCH_VARIABLE(name, id, fcn) \ + if (strncmp((name), (id), sizeof(id) - 1) == 0) { \ + rval = fcn(uc_mgr); \ + idsize = sizeof(id) - 1; \ + goto __rval; \ + } + +#define MATCH_VARIABLE2(name, id, fcn) \ + if (strncmp((name), (id), sizeof(id) - 1) == 0) { \ + idsize = sizeof(id) - 1; \ + tmp = strchr(value + idsize, '}'); \ + if (tmp) { \ + rvalsize = tmp - (value + idsize); \ + if (rvalsize > sizeof(v2)) { \ + err = -ENOMEM; \ + goto __error; \ + } \ + strncpy(v2, value + idsize, rvalsize); \ + v2[rvalsize] = '\0'; \ + idsize += rvalsize + 1; \ + rval = fcn(uc_mgr, v2); \ + goto __rval; \ + } \ + } + +int uc_mgr_get_substituted_value(snd_use_case_mgr_t *uc_mgr, + char **_rvalue, + const char *value) +{ + size_t size, nsize, idsize, rvalsize, dpos = 0; + const char *tmp; + char *r, *nr, *rval, v2[32]; + int err; + + if (value == NULL) + return -ENOENT; + + size = strlen(value) + 1; + r = malloc(size); + if (r == NULL) + return -ENOMEM; + + while (*value) { + if (*value == '$' && *(value+1) == '{') { + MATCH_VARIABLE(value, "${ConfName}", rval_conf_name); + MATCH_VARIABLE(value, "${CardId}", rval_card_id); + MATCH_VARIABLE(value, "${CardDriver}", rval_card_driver); + MATCH_VARIABLE(value, "${CardName}", rval_card_name); + MATCH_VARIABLE(value, "${CardLongName}", rval_card_longname); + MATCH_VARIABLE(value, "${CardComponents}", rval_card_components); + MATCH_VARIABLE2(value, "${env:", rval_env); + MATCH_VARIABLE2(value, "${sys:", rval_sysfs); + err = -EINVAL; + tmp = strchr(value, '}'); + if (tmp) { + strncpy(r, value, tmp + 1 - value); + r[tmp + 1 - value] = '\0'; + uc_error("variable '%s' is not known!", r); + } else { + uc_error("variable reference '%s' is not complete", value); + } + goto __error; +__rval: + if (rval == NULL || rval[0] == '\0') { + free(rval); + strncpy(r, value, idsize); + r[idsize] = '\0'; + uc_error("variable '%s' is not defined in this context!", r); + err = -EINVAL; + goto __error; + } + value += idsize; + rvalsize = strlen(rval); + nsize = size + rvalsize - idsize; + if (nsize > size) { + nr = realloc(r, nsize); + if (nr == NULL) { + free(rval); + err = -ENOMEM; + goto __error; + } + size = nsize; + r = nr; + } + strcpy(r + dpos, rval); + dpos += rvalsize; + free(rval); + } else { + r[dpos++] = *value; + value++; + } + } + r[dpos] = '\0'; + + *_rvalue = r; + return 0; + +__error: + free(r); + return err; +} diff --git a/utils/alsa.m4 b/utils/alsa.m4 index 4c457f0..320e433 100644 --- a/utils/alsa.m4 +++ b/utils/alsa.m4 @@ -22,6 +22,7 @@ alsa_save_CFLAGS="$CFLAGS" alsa_save_LDFLAGS="$LDFLAGS" alsa_save_LIBS="$LIBS" alsa_found=yes +alsa_topology_found=no dnl dnl Get the cflags and libraries for alsa @@ -158,11 +159,17 @@ AC_CHECK_LIB([asound], [snd_ctl_open],, alsa_found=no] ) if test "x$enable_atopology" = "xyes"; then +alsa_topology_found=yes AC_CHECK_LIB([atopology], [snd_tplg_new],, [ifelse([$3], , [AC_MSG_ERROR(No linkable libatopology was found.)]) - alsa_found=no] + alsa_topology_found=no, +] ) fi +else +if test "x$enable_atopology" = "xyes"; then + alsa_topology_found=yes +fi fi if test "x$alsa_found" = "xyes" ; then @@ -183,7 +190,7 @@ fi dnl add the alsa topology library; must be at the end AC_MSG_CHECKING(for ALSA topology LDFLAGS) -if test "x$enable_atopology" = "xyes"; then +if test "x$alsa_topology_found" = "xyes"; then ALSA_TOPOLOGY_LIBS="$ALSA_TOPOLOGY_LIBS -latopology" fi AC_MSG_RESULT($ALSA_TOPOLOGY_LIBS) diff --git a/utils/alsa.m4.alsa-git b/utils/alsa.m4.alsa-git new file mode 100644 index 0000000..4c457f0 --- /dev/null +++ b/utils/alsa.m4.alsa-git @@ -0,0 +1,195 @@ +dnl Configure Paths for Alsa +dnl Some modifications by Richard Boulton +dnl Christopher Lansdown +dnl Jaroslav Kysela +dnl Last modification: $Id: alsa.m4,v 1.24 2004/09/15 18:48:07 tiwai Exp $ +dnl +dnl AM_PATH_ALSA([MINIMUM-VERSION [, ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND]]]) +dnl Test for libasound, and define ALSA_CFLAGS, ALSA_LIBS and +dnl ALSA_TOPOLOGY_LIBS as appropriate. +dnl +dnl enables arguments --with-alsa-prefix= +dnl --with-alsa-inc-prefix= +dnl --disable-alsatest +dnl +dnl For backwards compatibility, if ACTION_IF_NOT_FOUND is not specified, +dnl and the alsa libraries are not found, a fatal AC_MSG_ERROR() will result. +dnl + +AC_DEFUN([AM_PATH_ALSA], +[dnl Save the original CFLAGS, LDFLAGS, and LIBS +alsa_save_CFLAGS="$CFLAGS" +alsa_save_LDFLAGS="$LDFLAGS" +alsa_save_LIBS="$LIBS" +alsa_found=yes + +dnl +dnl Get the cflags and libraries for alsa +dnl +AC_ARG_WITH(alsa-prefix, + AS_HELP_STRING([--with-alsa-prefix=PFX], [Prefix where Alsa library is installed(optional)]), + [alsa_prefix="$withval"], [alsa_prefix=""]) + +AC_ARG_WITH(alsa-inc-prefix, + AS_HELP_STRING([--with-alsa-inc-prefix=PFX], [Prefix where include libraries are (optional)]), + [alsa_inc_prefix="$withval"], [alsa_inc_prefix=""]) + +AC_ARG_ENABLE(alsa-topology, + AS_HELP_STRING([--enable-alsatopology], [Force to use the Alsa topology library]), + [enable_atopology="$enableval"], + [enable_atopology=no]) + +AC_ARG_ENABLE(alsatest, + AS_HELP_STRING([--disable-alsatest], [Do not try to compile and run a test Alsa program]), + [enable_alsatest="$enableval"], + [enable_alsatest=yes]) + +dnl Add any special include directories +AC_MSG_CHECKING(for ALSA CFLAGS) +if test "$alsa_inc_prefix" != "" ; then + ALSA_CFLAGS="$ALSA_CFLAGS -I$alsa_inc_prefix" + CFLAGS="$CFLAGS -I$alsa_inc_prefix" +fi +AC_MSG_RESULT($ALSA_CFLAGS) + +AC_CHECK_LIB(c, dlopen, LIBDL="", [AC_CHECK_LIB(dl, dlopen, LIBDL="-ldl")]) + +dnl add any special lib dirs +AC_MSG_CHECKING(for ALSA LDFLAGS) +if test "$alsa_prefix" != "" ; then + ALSA_LIBS="$ALSA_LIBS -L$alsa_prefix" + LDFLAGS="$LDFLAGS $ALSA_LIBS" +fi + +dnl add the alsa library +ALSA_LIBS="$ALSA_LIBS -lasound -lm $LIBDL -lpthread" +LIBS="$ALSA_LIBS $LIBS" +AC_MSG_RESULT($ALSA_LIBS) + +dnl Check for a working version of libasound that is of the right version. +if test "x$enable_alsatest" = "xyes"; then + +AC_MSG_CHECKING([required libasound headers version]) +min_alsa_version=ifelse([$1], , 0.1.1, $1) +no_alsa="" + alsa_min_major_version=`echo $min_alsa_version | \ + sed 's/\([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\)/\1/'` + alsa_min_minor_version=`echo $min_alsa_version | \ + sed 's/\([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\)/\2/'` + alsa_min_micro_version=`echo $min_alsa_version | \ + sed 's/\([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\)/\3/'` +AC_MSG_RESULT($alsa_min_major_version.$alsa_min_minor_version.$alsa_min_micro_version) + +AC_LANG_SAVE +AC_LANG_C +AC_MSG_CHECKING([for libasound headers version >= $alsa_min_major_version.$alsa_min_minor_version.$alsa_min_micro_version ($min_alsa_version)]) +AC_TRY_COMPILE([ +#include +], [ +/* ensure backward compatibility */ +#if !defined(SND_LIB_MAJOR) && defined(SOUNDLIB_VERSION_MAJOR) +#define SND_LIB_MAJOR SOUNDLIB_VERSION_MAJOR +#endif +#if !defined(SND_LIB_MINOR) && defined(SOUNDLIB_VERSION_MINOR) +#define SND_LIB_MINOR SOUNDLIB_VERSION_MINOR +#endif +#if !defined(SND_LIB_SUBMINOR) && defined(SOUNDLIB_VERSION_SUBMINOR) +#define SND_LIB_SUBMINOR SOUNDLIB_VERSION_SUBMINOR +#endif + +# if(SND_LIB_MAJOR > $alsa_min_major_version) + exit(0); +# else +# if(SND_LIB_MAJOR < $alsa_min_major_version) +# error not present +# endif + +# if(SND_LIB_MINOR > $alsa_min_minor_version) + exit(0); +# else +# if(SND_LIB_MINOR < $alsa_min_minor_version) +# error not present +# endif + +# if(SND_LIB_SUBMINOR < $alsa_min_micro_version) +# error not present +# endif +# endif +# endif +exit(0); +], + [AC_MSG_RESULT(found.)], + [AC_MSG_RESULT(not present.) + ifelse([$3], , [AC_MSG_ERROR(Sufficiently new version of libasound not found.)]) + alsa_found=no] +) +AC_LANG_RESTORE + +AC_LANG_SAVE +AC_LANG_C +AC_MSG_CHECKING([for libatopology (sound headers version > 1.1.9)]) +AC_TRY_COMPILE([ +#include +#include +], [ +/* ensure backward compatibility */ +#if !defined(SND_LIB_VERSION) +#define SND_LIB_VERSION 0 +#endif +#if SND_LIB_VERSION > 0x00010109 + exit(0); +#else +# error not present +#endif +exit(0); +], + [AC_MSG_RESULT(yes) + enable_atopology="yes"], + [AC_MSG_RESULT(no)] +) +AC_LANG_RESTORE + +fi + +dnl Now that we know that we have the right version, let's see if we have the library and not just the headers. +if test "x$enable_alsatest" = "xyes"; then +AC_CHECK_LIB([asound], [snd_ctl_open],, + [ifelse([$3], , [AC_MSG_ERROR(No linkable libasound was found.)]) + alsa_found=no] +) +if test "x$enable_atopology" = "xyes"; then +AC_CHECK_LIB([atopology], [snd_tplg_new],, + [ifelse([$3], , [AC_MSG_ERROR(No linkable libatopology was found.)]) + alsa_found=no] +) +fi +fi + +if test "x$alsa_found" = "xyes" ; then + ifelse([$2], , :, [$2]) + LIBS=`echo $LIBS | sed 's/-lasound//g'` + LIBS=`echo $LIBS | sed 's/ //'` + LIBS="-lasound $LIBS" +fi +if test "x$alsa_found" = "xno" ; then + ifelse([$3], , :, [$3]) + CFLAGS="$alsa_save_CFLAGS" + LDFLAGS="$alsa_save_LDFLAGS" + LIBS="$alsa_save_LIBS" + ALSA_CFLAGS="" + ALSA_LIBS="" + ALSA_TOPOLOGY_LIBS="" +fi + +dnl add the alsa topology library; must be at the end +AC_MSG_CHECKING(for ALSA topology LDFLAGS) +if test "x$enable_atopology" = "xyes"; then + ALSA_TOPOLOGY_LIBS="$ALSA_TOPOLOGY_LIBS -latopology" +fi +AC_MSG_RESULT($ALSA_TOPOLOGY_LIBS) + +dnl That should be it. Now just export out symbols: +AC_SUBST(ALSA_CFLAGS) +AC_SUBST(ALSA_LIBS) +AC_SUBST(ALSA_TOPOLOGY_LIBS) +])