diff --git a/configure.in b/configure.in index c353759d..264b604c 100644 --- a/configure.in +++ b/configure.in @@ -373,6 +373,9 @@ AC_ARG_ENABLE(hwdep, AC_ARG_ENABLE(seq, AS_HELP_STRING([--disable-seq], [disable the sequencer component]), [build_seq="$enableval"], [build_seq="yes"]) +AC_ARG_ENABLE(ucm, + AS_HELP_STRING([--disable-ucm], [disable the use-case-manager component]), + [build_ucm="$enableval"], [build_ucm="yes"]) AC_ARG_ENABLE(alisp, AS_HELP_STRING([--disable-alisp], [disable the alisp component]), [build_alisp="$enableval"], [build_alisp="yes"]) @@ -414,6 +417,7 @@ AM_CONDITIONAL(BUILD_PCM, test x$build_pcm = xyes) AM_CONDITIONAL(BUILD_RAWMIDI, test x$build_rawmidi = xyes) AM_CONDITIONAL(BUILD_HWDEP, test x$build_hwdep = xyes) AM_CONDITIONAL(BUILD_SEQ, test x$build_seq = xyes) +AM_CONDITIONAL(BUILD_UCM, test x$build_ucm = xyes) AM_CONDITIONAL(BUILD_ALISP, test x$build_alisp = xyes) AM_CONDITIONAL(BUILD_PYTHON, test x$build_python = xyes) @@ -598,7 +602,7 @@ AC_OUTPUT(Makefile doc/Makefile doc/pictures/Makefile doc/doxygen.cfg \ src/control/Makefile src/mixer/Makefile \ src/pcm/Makefile src/pcm/scopes/Makefile \ src/rawmidi/Makefile src/timer/Makefile \ - src/hwdep/Makefile src/seq/Makefile \ + src/hwdep/Makefile src/seq/Makefile src/ucm/Makefile \ src/compat/Makefile src/alisp/Makefile src/conf/Makefile \ src/conf/cards/Makefile \ src/conf/pcm/Makefile \ diff --git a/doc/doxygen.cfg.in b/doc/doxygen.cfg.in index 8606c485..f4499d61 100644 --- a/doc/doxygen.cfg.in +++ b/doc/doxygen.cfg.in @@ -28,6 +28,7 @@ INPUT = @top_srcdir@/doc/index.doxygen \ @top_srcdir@/include/pcm_ioplug.h \ @top_srcdir@/include/control_external.h \ @top_srcdir@/include/mixer.h \ + @top_srcdir@/include/use-case.h \ @top_srcdir@/src/error.c \ @top_srcdir@/src/dlmisc.c \ @top_srcdir@/src/async.c \ @@ -76,7 +77,8 @@ INPUT = @top_srcdir@/doc/index.doxygen \ @top_srcdir@/src/rawmidi \ @top_srcdir@/src/timer \ @top_srcdir@/src/hwdep \ - @top_srcdir@/src/seq + @top_srcdir@/src/seq \ + @top_srcdir@/src/ucm EXCLUDE = @top_srcdir@/src/control/control_local.h \ @top_srcdir@/src/pcm/atomic.h \ @top_srcdir@/src/pcm/interval.h \ @@ -91,7 +93,8 @@ EXCLUDE = @top_srcdir@/src/control/control_local.h \ @top_srcdir@/src/hwdep/hwdep_local.h \ @top_srcdir@/src/mixer/mixer_local.h \ @top_srcdir@/src/rawmidi/rawmidi_local.h \ - @top_srcdir@/src/seq/seq_local.h + @top_srcdir@/src/seq/seq_local.h \ + @top_srcdir@/src/seq/ucm_local.h RECURSIVE = YES FILE_PATTERNS = *.c *.h EXAMPLE_PATH = @top_srcdir@/test diff --git a/include/Makefile.am b/include/Makefile.am index a2915034..de37f2cc 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -5,7 +5,7 @@ alsaincludedir = ${includedir}/alsa alsainclude_HEADERS = asoundlib.h asoundef.h \ version.h global.h input.h output.h error.h \ - conf.h control.h iatomic.h + conf.h control.h iatomic.h use-case.h if BUILD_CTL_PLUGIN_EXT alsainclude_HEADERS += control_external.h diff --git a/include/control.h b/include/control.h index 3d6b0a5f..e8f38bb4 100644 --- a/include/control.h +++ b/include/control.h @@ -284,6 +284,13 @@ unsigned int snd_ctl_event_elem_get_index(const snd_ctl_event_t *obj); int snd_ctl_elem_list_alloc_space(snd_ctl_elem_list_t *obj, unsigned int entries); void snd_ctl_elem_list_free_space(snd_ctl_elem_list_t *obj); +char *snd_ctl_ascii_elem_id_get(snd_ctl_elem_id_t *id); +int snd_ctl_ascii_elem_id_parse(snd_ctl_elem_id_t *dst, const char *str); +int snd_ctl_ascii_value_parse(snd_ctl_t *handle, + snd_ctl_elem_value_t *dst, + snd_ctl_elem_info_t *info, + const char *value); + size_t snd_ctl_elem_id_sizeof(void); /** \hideinitializer * \brief allocate an invalid #snd_ctl_elem_id_t using standard alloca diff --git a/include/list.h b/include/list.h index 5b3f1bf0..4d9895fe 100644 --- a/include/list.h +++ b/include/list.h @@ -162,5 +162,13 @@ static __inline__ void list_splice(struct list_head *list, struct list_head *hea #define list_entry(ptr, type, member) \ ((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member))) +/** + * list_entry - get the struct for this entry + * @ptr: the &struct list_head pointer. + * @type: the type of the struct this is embedded in. + * @offset: offset of entry inside a struct + */ +#define list_entry_offset(ptr, type, offset) \ + ((type *)((char *)(ptr)-(offset))) #endif /* _LIST_H */ diff --git a/include/use-case.h b/include/use-case.h new file mode 100644 index 00000000..f5776284 --- /dev/null +++ b/include/use-case.h @@ -0,0 +1,349 @@ +/** + * \file include/use-case.h + * \brief use case interface for the ALSA driver + * \author Liam Girdwood + * \author Stefan Schmidt + * \author Jaroslav Kysela + * \author Justin Xu + * \date 2008-2010 + */ +/* + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Copyright (C) 2008-2010 SlimLogic Ltd + * Copyright (C) 2010 Wolfson Microelectronics PLC + * Copyright (C) 2010 Texas Instruments Inc. + * + * Support for the verb/device/modifier core logic and API, + * command line tool and file parser was kindly sponsored by + * Texas Instruments Inc. + * Support for multiple active modifiers and devices, + * transition sequences, multiple client access and user defined use + * cases was kindly sponsored by Wolfson Microelectronics PLC. + */ + +#ifndef __ALSA_USE_CASE_H +#define __ALSA_USE_CASE_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * \defgroup Use Case Interface + * The ALSA Use Case manager interface. + * See \ref Usecase page for more details. + * \{ + */ + +/** + * ALSA Use Case Interface + * + * The use case manager works by configuring the sound card ALSA kcontrols to + * change the hardware digital and analog audio routing to match the requested + * device use case. The use case manager kcontrol configurations are stored in + * easy to modify text files. + * + * An audio use case can be defined by a verb and device parameter. The verb + * describes the use case action i.e. a phone call, listening to music, recording + * a conversation etc. The device describes the physical audio capture and playback + * hardware i.e. headphones, phone handset, bluetooth headset, etc. + * + * It's intended clients will mostly only need to set the use case verb and + * device for each system use case change (as the verb and device parameters + * cover most audio use cases). + * + * However there are times when a use case has to be modified at runtime. e.g. + * + * o Incoming phone call when the device is playing music + * o Recording sections of a phone call + * o Playing tones during a call. + * + * In order to allow asynchronous runtime use case adaptations, we have a third + * optional modifier parameter that can be used to further configure + * the use case during live audio runtime. + * + * This interface allows clients to :- + * + * o Query the supported use case verbs, devices and modifiers for the machine. + * o Set and Get use case verbs, devices and modifiers for the machine. + * o Get the ALSA PCM playback and capture device PCMs for use case verb and + * modifier. + * o Get the TQ parameter for each use case verb and modifier. + * o Get the ALSA master playback and capture volume/switch kcontrols + * for each use case. + */ + + +/* + * Use Case Verb. + * + * The use case verb is the main device audio action. e.g. the "HiFi" use + * case verb will configure the audio hardware for HiFi Music playback + * and capture. + */ +#define SND_USE_CASE_VERB_INACTIVE "Inactive" +#define SND_USE_CASE_VERB_HIFI "HiFi" +#define SND_USE_CASE_VERB_HIFI_LOW_POWER "HiFi Low Power" +#define SND_USE_CASE_VERB_VOICE "Voice" +#define SND_USE_CASE_VERB_VOICE_LOW_POWER "Voice Low Power" +#define SND_USE_CASE_VERB_VOICECALL "Voice Call" +#define SND_USE_CASE_VERB_IP_VOICECALL "Voice Call IP" +#define SND_USE_CASE_VERB_ANALOG_RADIO "FM Analog Radio" +#define SND_USE_CASE_VERB_DIGITAL_RADIO "FM Digital Radio" +/* add new verbs to end of list */ + + +/* + * Use Case Device. + * + * Physical system devices the render and capture audio. Devices can be OR'ed + * together to support audio on similtanious devices. + */ +#define SND_USE_CASE_DEV_NONE "None" +#define SND_USE_CASE_DEV_SPEAKER "Speaker" +#define SND_USE_CASE_DEV_LINE "Line" +#define SND_USE_CASE_DEV_HEADPHONES "Headphones" +#define SND_USE_CASE_DEV_HEADSET "Headset" +#define SND_USE_CASE_DEV_HANDSET "Handset" +#define SND_USE_CASE_DEV_BLUETOOTH "Bluetooth" +#define SND_USE_CASE_DEV_EARPIECE "Earpiece" +#define SND_USE_CASE_DEV_SPDIF "SPDIF" +#define SND_USE_CASE_DEV_HDMI "HDMI" +/* add new devices to end of list */ + + +/* + * Use Case Modifiers. + * + * The use case modifier allows runtime configuration changes to deal with + * asynchronous events. + * + * e.g. to record a voice call :- + * 1. Set verb to SND_USE_CASE_VERB_VOICECALL (for voice call) + * 2. Set modifier SND_USE_CASE_MOD_CAPTURE_VOICE when capture required. + * 3. Call snd_use_case_get("_pcm_/_cdevice") to get ALSA source PCM name + * with captured voice pcm data. + * + * e.g. to play a ring tone when listenin to MP3 Music :- + * 1. Set verb to SND_USE_CASE_VERB_HIFI (for MP3 playback) + * 2. Set modifier to SND_USE_CASE_MOD_PLAY_TONE when incoming call happens. + * 3. Call snd_use_case_get("_pcm_/_pdevice") to get ALSA PCM sink name for + * ringtone pcm data. + */ +#define SND_USE_CASE_MOD_CAPTURE_VOICE "Capture Voice" +#define SND_USE_CASE_MOD_CAPTURE_MUSIC "Capture Music" +#define SND_USE_CASE_MOD_PLAY_MUSIC "Play Music" +#define SND_USE_CASE_MOD_PLAY_VOICE "Play Voice" +#define SND_USE_CASE_MOD_PLAY_TONE "Play Tone" +#define SND_USE_CASE_MOD_ECHO_REF "Echo Reference" +/* add new modifiers to end of list */ + + +/** + * 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" +#define SND_USE_CASE_TQ_VOICE "Voice" +#define SND_USE_CASE_TQ_TONES "Tones" + +/** use case container */ +typedef struct snd_use_case_mgr snd_use_case_mgr_t; + +/** + * \brief 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[/] - get list of supported devices + * (in pair device+comment) + * _modifiers[/]- get list of supported modifiers + * (in pair modifier+comment) + * TQ[/] - get list of TQ identifiers + * _enadevs - get list of enabled devices + * _enamods - get list of enabled modifiers + * + */ +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: String is dynamically allocated, use free() to + * deallocate this string. + * + * Known identifiers: + * NULL - return current card + * _verb - return current verb + * TQ[/] - Tone Quality [for given modifier] + * PlaybackPCM[/] - full PCM playback device name + * CapturePCM[/] - full PCM capture device name + * PlaybackCTL[/] - playback control device name + * PlaybackVolume[/] - playback control volume ID string + * PlaybackSwitch[/] - playback control switch ID string + * CaptureCTL[/] - capture control device name + * CaptureVolume[/] - capture control volume ID string + * CaptureSwitch[/] - capture control switch ID string + * PlaybackMixer[/] - name of playback mixer + * PlaybackMixerID[/] - mixer playback ID + * CaptureMixer[/] - name of capture mixer + * CaptureMixerID[/] - mixer capture ID + */ +int snd_use_case_get(snd_use_case_mgr_t *uc_mgr, + const char *identifier, + 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/ - return status for given device + * _modstatus/ - return status for given modifier + */ +int snd_use_case_geti(snd_use_case_mgr_t *uc_mgr, + const char *identifier, + long *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/ - new_device = value + * - disable old_device and then enable new_device + * - if old_device is not enabled just return + * - check transmit sequence firstly + * _enamod - enable given modifier = value + * _dismod - disable given modifier = value + * _swmod/ - new_modifier = value + * - disable old_modifier and then enable new_modifier + * - if old_modifier is not enabled just return + * - check transmit sequence firstly + */ +int snd_use_case_set(snd_use_case_mgr_t *uc_mgr, + const char *identifier, + const char *value); + +/** + * \brief Open and initialise use case core for sound card + * \param uc_mgr Returned use case manager pointer + * \param card_name Sound card name. + * \return zero if success, otherwise a negative error code + */ +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); +} + +/** + * \} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* __ALSA_USE_CASE_H */ diff --git a/src/Makefile.am b/src/Makefile.am index 3204fe46..9a00dca3 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -38,6 +38,10 @@ if BUILD_SEQ SUBDIRS += seq libasound_la_LIBADD += seq/libseq.la endif +if BUILD_UCM +SUBDIRS += ucm +libasound_la_LIBADD += ucm/libucm.la +endif if BUILD_ALISP SUBDIRS += alisp libasound_la_LIBADD += alisp/libalisp.la diff --git a/src/control/Makefile.am b/src/control/Makefile.am index d4c50c01..8076c732 100644 --- a/src/control/Makefile.am +++ b/src/control/Makefile.am @@ -1,7 +1,8 @@ EXTRA_LTLIBRARIES = libcontrol.la libcontrol_la_SOURCES = cards.c tlv.c namehint.c hcontrol.c \ - control.c control_hw.c setup.c control_symbols.c + control.c control_hw.c setup.c ctlparse.c \ + control_symbols.c if BUILD_CTL_PLUGIN_SHM libcontrol_la_SOURCES += control_shm.c endif diff --git a/src/control/ctlparse.c b/src/control/ctlparse.c new file mode 100644 index 00000000..a9298167 --- /dev/null +++ b/src/control/ctlparse.c @@ -0,0 +1,351 @@ +/** + * \file control/control.c + * \brief CTL interface - parse ASCII identifiers and values + * \author Jaroslav Kysela + * \date 2010 + */ +/* + * Control Interface - ASCII parser + * Copyright (c) 2010 by Jaroslav Kysela + * + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include "control_local.h" + +/* Function to convert from percentage to volume. val = percentage */ + +#define convert_prange1(val, min, max) \ + ceil((val) * ((max) - (min)) * 0.01 + (min)) + +#define check_range(val, min, max) \ + ((val < min) ? (min) : ((val > max) ? (max) : (val))) + +static long get_integer(const char **ptr, long min, long max) +{ + long val = min; + char *p = (char *)*ptr, *s; + + if (*p == ':') + p++; + if (*p == '\0' || (!isdigit(*p) && *p != '-')) + goto out; + + s = p; + val = strtol(s, &p, 10); + if (*p == '.') { + p++; + strtol(p, &p, 10); + } + if (*p == '%') { + val = (long)convert_prange1(strtod(s, NULL), min, max); + p++; + } + val = check_range(val, min, max); + if (*p == ',') + p++; + out: + *ptr = p; + return val; +} + +static long long get_integer64(const char **ptr, long long min, long long max) +{ + long long val = min; + char *p = (char *)*ptr, *s; + + if (*p == ':') + p++; + if (*p == '\0' || (!isdigit(*p) && *p != '-')) + goto out; + + s = p; + val = strtol(s, &p, 10); + if (*p == '.') { + p++; + strtol(p, &p, 10); + } + if (*p == '%') { + val = (long long)convert_prange1(strtod(s, NULL), min, max); + p++; + } + val = check_range(val, min, max); + if (*p == ',') + p++; + out: + *ptr = p; + return val; +} + +/** + * \brief return ASCII CTL element identifier name + * \param id CTL identifier + * \return ascii identifier of CTL element + * + * The string is allocated using strdup(). + */ +char *snd_ctl_ascii_elem_id_get(snd_ctl_elem_id_t *id) +{ + unsigned int index, device, subdevice; + char buf[256], buf1[32]; + + snprintf(buf, sizeof(buf), "numid=%u,iface=%s,name='%s'", + snd_ctl_elem_id_get_numid(id), + snd_ctl_elem_iface_name( + snd_ctl_elem_id_get_interface(id)), + snd_ctl_elem_id_get_name(id)); + buf[sizeof(buf)-1] = '\0'; + index = snd_ctl_elem_id_get_index(id); + device = snd_ctl_elem_id_get_device(id); + subdevice = snd_ctl_elem_id_get_subdevice(id); + if (index) { + snprintf(buf1, sizeof(buf1), ",index=%i", index); + if (strlen(buf) + strlen(buf1) < sizeof(buf)) + strcat(buf, buf1); + } + if (device) { + snprintf(buf1, sizeof(buf1), ",device=%i", device); + if (strlen(buf) + strlen(buf1) < sizeof(buf)) + strcat(buf, buf1); + } + if (subdevice) { + snprintf(buf1, sizeof(buf1), ",subdevice=%i", subdevice); + if (strlen(buf) + strlen(buf1) < sizeof(buf)) + strcat(buf, buf1); + } + return strdup(buf); +} + +/** + * \brief parse ASCII string as CTL element identifier + * \param dst destination CTL identifier + * \param str source ASCII string + * \return zero on success, otherwise a negative error code + */ +int snd_ctl_ascii_elem_id_parse(snd_ctl_elem_id_t *dst, const char *str) +{ + int c, size, numid; + char *ptr; + + while (*str == ' ' || *str == '\t') + str++; + if (!(*str)) + return -EINVAL; + snd_ctl_elem_id_set_interface(dst, SND_CTL_ELEM_IFACE_MIXER); /* default */ + while (*str) { + if (!strncasecmp(str, "numid=", 6)) { + str += 6; + numid = atoi(str); + if (numid <= 0) { + fprintf(stderr, "amixer: Invalid numid %d\n", numid); + return -EINVAL; + } + snd_ctl_elem_id_set_numid(dst, atoi(str)); + while (isdigit(*str)) + str++; + } else if (!strncasecmp(str, "iface=", 6)) { + str += 6; + if (!strncasecmp(str, "card", 4)) { + snd_ctl_elem_id_set_interface(dst, SND_CTL_ELEM_IFACE_CARD); + str += 4; + } else if (!strncasecmp(str, "mixer", 5)) { + snd_ctl_elem_id_set_interface(dst, SND_CTL_ELEM_IFACE_MIXER); + str += 5; + } else if (!strncasecmp(str, "pcm", 3)) { + snd_ctl_elem_id_set_interface(dst, SND_CTL_ELEM_IFACE_PCM); + str += 3; + } else if (!strncasecmp(str, "rawmidi", 7)) { + snd_ctl_elem_id_set_interface(dst, SND_CTL_ELEM_IFACE_RAWMIDI); + str += 7; + } else if (!strncasecmp(str, "timer", 5)) { + snd_ctl_elem_id_set_interface(dst, SND_CTL_ELEM_IFACE_TIMER); + str += 5; + } else if (!strncasecmp(str, "sequencer", 9)) { + snd_ctl_elem_id_set_interface(dst, SND_CTL_ELEM_IFACE_SEQUENCER); + str += 9; + } else { + return -EINVAL; + } + } else if (!strncasecmp(str, "name=", 5)) { + char buf[64]; + str += 5; + ptr = buf; + size = 0; + if (*str == '\'' || *str == '\"') { + c = *str++; + while (*str && *str != c) { + if (size < (int)sizeof(buf)) { + *ptr++ = *str; + size++; + } + str++; + } + if (*str == c) + str++; + } else { + while (*str && *str != ',') { + if (size < (int)sizeof(buf)) { + *ptr++ = *str; + size++; + } + str++; + } + } + *ptr = '\0'; + snd_ctl_elem_id_set_name(dst, buf); + } else if (!strncasecmp(str, "index=", 6)) { + str += 6; + snd_ctl_elem_id_set_index(dst, atoi(str)); + while (isdigit(*str)) + str++; + } else if (!strncasecmp(str, "device=", 7)) { + str += 7; + snd_ctl_elem_id_set_device(dst, atoi(str)); + while (isdigit(*str)) + str++; + } else if (!strncasecmp(str, "subdevice=", 10)) { + str += 10; + snd_ctl_elem_id_set_subdevice(dst, atoi(str)); + while (isdigit(*str)) + str++; + } + if (*str == ',') { + str++; + } else { + if (*str) + return -EINVAL; + } + } + return 0; +} + +static int get_ctl_enum_item_index(snd_ctl_t *handle, + snd_ctl_elem_info_t *info, + const char **ptrp) +{ + char *ptr = (char *)*ptrp; + int items, i, len; + const char *name; + + items = snd_ctl_elem_info_get_items(info); + if (items <= 0) + return -1; + + for (i = 0; i < items; i++) { + snd_ctl_elem_info_set_item(info, i); + if (snd_ctl_elem_info(handle, info) < 0) + return -1; + name = snd_ctl_elem_info_get_item_name(info); + len = strlen(name); + if (! strncmp(name, ptr, len)) { + if (! ptr[len] || ptr[len] == ',' || ptr[len] == '\n') { + ptr += len; + *ptrp = ptr; + return i; + } + } + } + return -1; +} + +/** + * \brief parse ASCII string as CTL element value + * \param dst destination CTL element value + * \param info CTL element info structure + * \param value source ASCII string + * \return zero on success, otherwise a negative error code + */ +int snd_ctl_ascii_value_parse(snd_ctl_t *handle, + snd_ctl_elem_value_t *dst, + snd_ctl_elem_info_t *info, + const char *value) +{ + const char *ptr = value; + snd_ctl_elem_id_t *myid; + snd_ctl_elem_type_t type; + unsigned int idx, count; + long tmp; + long long tmp64; + + snd_ctl_elem_id_alloca(&myid); + snd_ctl_elem_info_get_id(info, myid); + type = snd_ctl_elem_info_get_type(info); + count = snd_ctl_elem_info_get_count(info); + snd_ctl_elem_value_set_id(dst, myid); + + for (idx = 0; idx < count && idx < 128 && ptr && *ptr; idx++) { + switch (type) { + case SND_CTL_ELEM_TYPE_BOOLEAN: + tmp = 0; + if (!strncasecmp(ptr, "on", 2) || + !strncasecmp(ptr, "up", 2)) { + tmp = 1; + ptr += 2; + } else if (!strncasecmp(ptr, "yes", 3)) { + tmp = 1; + ptr += 3; + } else if (!strncasecmp(ptr, "toggle", 6)) { + tmp = snd_ctl_elem_value_get_boolean(dst, idx); + tmp = tmp > 0 ? 0 : 1; + ptr += 6; + } else if (isdigit(*ptr)) { + tmp = atoi(ptr) > 0 ? 1 : 0; + while (isdigit(*ptr)) + ptr++; + } else { + while (*ptr && *ptr != ',') + ptr++; + } + snd_ctl_elem_value_set_boolean(dst, idx, tmp); + break; + case SND_CTL_ELEM_TYPE_INTEGER: + tmp = get_integer(&ptr, + snd_ctl_elem_info_get_min(info), + snd_ctl_elem_info_get_max(info)); + snd_ctl_elem_value_set_integer(dst, idx, tmp); + break; + case SND_CTL_ELEM_TYPE_INTEGER64: + tmp64 = get_integer64(&ptr, + snd_ctl_elem_info_get_min64(info), + snd_ctl_elem_info_get_max64(info)); + snd_ctl_elem_value_set_integer64(dst, idx, tmp64); + break; + case SND_CTL_ELEM_TYPE_ENUMERATED: + tmp = get_ctl_enum_item_index(handle, info, &ptr); + if (tmp < 0) + tmp = get_integer(&ptr, 0, + snd_ctl_elem_info_get_items(info) - 1); + snd_ctl_elem_value_set_enumerated(dst, idx, tmp); + break; + case SND_CTL_ELEM_TYPE_BYTES: + tmp = get_integer(&ptr, 0, 255); + snd_ctl_elem_value_set_byte(dst, idx, tmp); + break; + default: + break; + } + if (!strchr(value, ',')) + ptr = value; + else if (*ptr == ',') + ptr++; + } + return 0; +} diff --git a/src/ucm/Makefile.am b/src/ucm/Makefile.am new file mode 100644 index 00000000..7435d903 --- /dev/null +++ b/src/ucm/Makefile.am @@ -0,0 +1,10 @@ +EXTRA_LTLIBRARIES = libucm.la + +libucm_la_SOURCES = utils.c parser.c main.c + +noinst_HEADERS = ucm_local.h + +all: libucm.la + + +INCLUDES=-I$(top_srcdir)/include diff --git a/src/ucm/main.c b/src/ucm/main.c new file mode 100644 index 00000000..030c9b1c --- /dev/null +++ b/src/ucm/main.c @@ -0,0 +1,1437 @@ +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * Support for the verb/device/modifier core logic and API, + * command line tool and file parser was kindly sponsored by + * Texas Instruments Inc. + * Support for multiple active modifiers and devices, + * transition sequences, multiple client access and user defined use + * cases was kindly sponsored by Wolfson Microelectronics PLC. + * + * Copyright (C) 2008-2010 SlimLogic Ltd + * Copyright (C) 2010 Wolfson Microelectronics PLC + * Copyright (C) 2010 Texas Instruments Inc. + * Copyright (C) 2010 Red Hat Inc. + * Authors: Liam Girdwood + * Stefan Schmidt + * Justin Xu + * Jaroslav Kysela + */ + +#include "ucm_local.h" +#include +#include + +/* + * misc + */ + +static int get_value1(const char **value, struct list_head *value_list, + const char *identifier); +static int get_value3(const char **value, + const char *identifier, + struct list_head *value_list1, + struct list_head *value_list2, + struct list_head *value_list3); + +static int check_identifier(const char *identifier, const char *prefix) +{ + int len; + + if (strcmp(identifier, prefix) == 0) + return 1; + len = strlen(prefix); + if (memcmp(identifier, prefix, len) == 0 && identifier[len] == '/') + return 1; + return 0; +} + +static int list_count(struct list_head *list) +{ + struct list_head *pos; + int count = 0; + + list_for_each(pos, list) { + count += 1; + } + return count; +} + +static int alloc_str_list(struct list_head *list, int mult, char **result[]) +{ + char **res; + int cnt; + + cnt = list_count(list) * mult; + if (cnt == 0) + return cnt; + res = calloc(mult, cnt * sizeof(char *)); + if (res == NULL) + return -ENOMEM; + *result = res; + return cnt; +} + +/** + * \brief Create an identifier + * \param fmt Format (sprintf like) + * \param ... Optional arguments for sprintf like format + * \return Allocated string identifier or NULL on error + */ +char *snd_use_case_identifier(const char *fmt, ...) +{ + char *str, *res; + int size = strlen(fmt) + 512; + va_list args; + + str = malloc(size); + if (str == NULL) + return NULL; + va_start(args, fmt); + vsnprintf(str, size, fmt, args); + va_end(args); + str[size-1] = '\0'; + res = realloc(str, strlen(str) + 1); + if (res) + return res; + return str; +} + +/** + * \brief Free a string list + * \param list The string list to free + * \param items Count of strings + * \return Zero if success, otherwise a negative error code + */ +int snd_use_case_free_list(const char *list[], int items) +{ + int i; + if (list == NULL) + return 0; + for (i = 0; i < items; i++) + free((void *)list[i]); + free(list); + return 0; +} + +static int open_ctl(snd_use_case_mgr_t *uc_mgr, + snd_ctl_t **ctl, + const char *ctl_dev) +{ + int err; + + /* FIXME: add a list of ctl devices to uc_mgr structure and + cache accesses for multiple opened ctl devices */ + if (uc_mgr->ctl_dev != NULL && strcmp(ctl_dev, uc_mgr->ctl_dev) == 0) { + *ctl = uc_mgr->ctl; + return 0; + } + if (uc_mgr->ctl_dev) { + free(uc_mgr->ctl_dev); + uc_mgr->ctl_dev = NULL; + snd_ctl_close(uc_mgr->ctl); + + } + err = snd_ctl_open(ctl, ctl_dev, 0); + if (err < 0) + return err; + uc_mgr->ctl_dev = strdup(ctl_dev); + if (uc_mgr->ctl_dev == NULL) { + snd_ctl_close(*ctl); + return -ENOMEM; + } + uc_mgr->ctl = *ctl; + return 0; +} + +static int execute_cset(snd_ctl_t *ctl, char *cset) +{ + char *pos; + int err; + snd_ctl_elem_id_t *id; + snd_ctl_elem_value_t *value; + snd_ctl_elem_info_t *info; + + snd_ctl_elem_id_malloc(&id); + snd_ctl_elem_value_malloc(&value); + snd_ctl_elem_info_malloc(&info); + + pos = strrchr(cset, ' '); + if (pos == NULL) { + uc_error("undefined value for cset >%s<", cset); + return -EINVAL; + } + *pos = '\0'; + err = snd_ctl_ascii_elem_id_parse(id, cset); + if (err < 0) + goto __fail; + snd_ctl_elem_value_set_id(value, id); + snd_ctl_elem_info_set_id(info, id); + err = snd_ctl_elem_read(ctl, value); + if (err < 0) + goto __fail; + err = snd_ctl_elem_info(ctl, info); + if (err < 0) + goto __fail; + err = snd_ctl_ascii_value_parse(ctl, value, info, pos + 1); + if (err < 0) + goto __fail; + err = snd_ctl_elem_write(ctl, value); + if (err < 0) + goto __fail; + err = 0; + __fail: + *pos = ' '; + return err; +} + +/** + * \brief Execute the sequence + * \param uc_mgr Use case manager + * \param seq Sequence + * \return zero on success, otherwise a negative error code + */ +static int execute_sequence(snd_use_case_mgr_t *uc_mgr, + 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: + if (cdev == NULL) { + const char *cdev1 = NULL, *cdev2 = NULL; + err = get_value3(&cdev1, "PlaybackCTL", + value_list1, + value_list2, + value_list3); + if (err < 0 && err != ENOENT) { + uc_error("cdev is not defined!"); + return err; + } + err = get_value3(&cdev1, "CaptureCTL", + value_list1, + value_list2, + value_list3); + if (err < 0 && err != ENOENT) { + free((char *)cdev1); + uc_error("cdev is not defined!"); + return err; + } + if (cdev1 == NULL || cdev2 == NULL || + strcmp(cdev1, cdev2) == 0) { + cdev = (char *)cdev1; + free((char *)cdev2); + } else { + free((char *)cdev1); + free((char *)cdev2); + } + } + if (ctl == NULL) { + err = open_ctl(uc_mgr, &ctl, cdev); + if (err < 0) + goto __fail; + } + err = execute_cset(ctl, s->data.cset); + if (err < 0) + goto __fail; + break; + case SEQUENCE_ELEMENT_TYPE_SLEEP: + usleep(s->data.sleep); + break; + case SEQUENCE_ELEMENT_TYPE_EXEC: + err = system(s->data.exec); + 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; + +} + +/** + * \brief Import master config and execute the default sequence + * \param uc_mgr Use case manager + * \return zero on success, otherwise a negative error code + */ +static int import_master_config(snd_use_case_mgr_t *uc_mgr) +{ + int err; + + err = uc_mgr_import_master_config(uc_mgr); + if (err < 0) + return err; + err = execute_sequence(uc_mgr, &uc_mgr->default_list, + &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) + return cnt; + *result = (const char **)res; + list_for_each(pos, list) { + ptr = list_entry_offset(pos, char, offset); + str1 = *((char **)(ptr + s1offset)); + if (str1 != NULL) { + *res = strdup(str1); + if (*res == NULL) + goto __fail; + } else { + *res = NULL; + } + res++; + } + return cnt; + __fail: + snd_use_case_free_list((const char **)res, cnt); + return -ENOMEM; +} + +#define get_list(list, result, type, member, s1) \ + get_list0(list, result, \ + (unsigned long)(&((type *)0)->member), \ + (unsigned long)(&((type *)0)->s1)) + +/** + * \brief Universal string list - pair of strings + * \param list List of structures + * \param result Result list + * \param offset Offset of list structure + * \param s1offset Offset of string structure + * \param s1offset Offset of string structure + * \return count of items on success, otherwise a negative error code + */ +static int get_list20(struct list_head *list, + const char **result[], + unsigned long offset, + unsigned long s1offset, + unsigned long s2offset) +{ + char **res; + int cnt; + struct list_head *pos; + char *ptr, *str1, *str2; + + cnt = alloc_str_list(list, 2, &res); + if (cnt <= 0) + return cnt; + *result = (const char **)res; + list_for_each(pos, list) { + ptr = list_entry_offset(pos, char, offset); + str1 = *((char **)(ptr + s1offset)); + if (str1 != NULL) { + *res = strdup(str1); + if (*res == NULL) + goto __fail; + } else { + *res = NULL; + } + res++; + str2 = *((char **)(ptr + s2offset)); + if (str2 != NULL) { + *res = strdup(str2); + if (*res == NULL) + goto __fail; + } else { + *res = NULL; + } + res++; + } + return cnt; + __fail: + snd_use_case_free_list((const char **)res, cnt); + return -ENOMEM; +} + +#define get_list2(list, result, type, member, s1, s2) \ + get_list20(list, result, \ + (unsigned long)(&((type *)0)->member), \ + (unsigned long)(&((type *)0)->s1), \ + (unsigned long)(&((type *)0)->s2)) + +/** + * \brief Find verb + * \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); +} + +/** + * \brief Find device + * \param verb Use case verb + * \param device_name device to find + * \return structure on success, otherwise a NULL (not found) + */ +static inline struct use_case_device * + find_device(struct use_case_verb *verb, + const char *device_name) +{ + return find(&verb->device_list, + struct use_case_device, list, name, + device_name); +} + +static int is_modifier_supported(snd_use_case_mgr_t *uc_mgr, + struct use_case_modifier *modifier) +{ + struct dev_list *device; + struct list_head *pos; + + list_for_each(pos, &modifier->dev_list) { + device = list_entry(pos, struct dev_list, list); + if (find(&uc_mgr->active_devices, + struct use_case_device, active_list, name, device->name)) + return 1; + + } + return 0; +} + +/** + * \brief Find modifier + * \param verb Use case verb + * \param modifier_name modifier to find + * \return structure on success, otherwise a NULL (not found) + */ +static struct use_case_modifier * + find_modifier(snd_use_case_mgr_t *uc_mgr, const char *modifier_name) +{ + struct use_case_modifier *modifier; + struct use_case_verb *verb = uc_mgr->active_verb; + struct list_head *pos; + char name[64], *cpos; + + list_for_each(pos, &verb->modifier_list) { + modifier = list_entry(pos, struct use_case_modifier, list); + + strncpy(name, modifier->name, sizeof(name)); + cpos = strchr(name, '.'); + if (!cpos) + continue; + *cpos= '\0'; + + if (strcmp(name, modifier_name)) + continue; + + if (is_modifier_supported(uc_mgr, modifier)) + return modifier; + } + return NULL; +} + +/** + * \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 (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 (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 **mgr, + const char *card_name) +{ + snd_use_case_mgr_t *uc_mgr; + int err; + + /* create a new UCM */ + uc_mgr = calloc(1, sizeof(snd_use_case_mgr_t)); + if (uc_mgr == NULL) + return -ENOMEM; + INIT_LIST_HEAD(&uc_mgr->verb_list); + INIT_LIST_HEAD(&uc_mgr->default_list); + INIT_LIST_HEAD(&uc_mgr->value_list); + INIT_LIST_HEAD(&uc_mgr->active_modifiers); + INIT_LIST_HEAD(&uc_mgr->active_devices); + pthread_mutex_init(&uc_mgr->mutex, NULL); + + uc_mgr->card_name = strdup(card_name); + if (uc_mgr->card_name == NULL) { + free(uc_mgr); + return -ENOMEM; + } + + /* get info on use_cases and verify against card */ + err = import_master_config(uc_mgr); + if (err < 0) { + uc_error("error: failed to import %s use case configuration %d", + card_name, err); + goto err; + } + + *mgr = uc_mgr; + return 0; + +err: + uc_mgr_free(uc_mgr); + return err; +} + +/** + * \brief Reload and reparse all use case files. + * \param uc_mgr Use case manager + * \return zero on success, otherwise a negative error code + */ +int snd_use_case_mgr_reload(snd_use_case_mgr_t *uc_mgr) +{ + int err; + + pthread_mutex_lock(&uc_mgr->mutex); + + uc_mgr_free_verb(uc_mgr); + + /* reload all use cases */ + err = import_master_config(uc_mgr); + if (err < 0) { + uc_error("error: failed to reload use cases\n"); + pthread_mutex_unlock(&uc_mgr->mutex); + return -EINVAL; + } + + pthread_mutex_unlock(&uc_mgr->mutex); + return err; +} + +/** + * \brief Close use case manager. + * \param uc_mgr Use case manager + * \return zero on success, otherwise a negative error code + */ +int snd_use_case_mgr_close(snd_use_case_mgr_t *uc_mgr) +{ + uc_mgr_free(uc_mgr); + + return 0; +} + +/* + * 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); +} + +struct myvalue { + struct list_head list; + char *value; +}; + +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; + 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); + *list = (const char **)res; + if (err >= 0) { + list_for_each(pos, &mylist) { + val = list_entry(pos, struct myvalue, list); + *res = strdup(val->value); + if (*res == NULL) { + snd_use_case_free_list((const char **)res, err); + err = -ENOMEM; + goto __fail; + } + res++; + } + } + __fail: + list_for_each_safe(pos, npos, &mylist) { + val = list_entry(pos, struct myvalue, list); + list_del(&val->list); + free(val); + } + return err; +} + +/** + * \brief 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 + err = get_value_list(uc_mgr, identifier, list, str); + if (str) + free(str); + } + __end: + pthread_mutex_unlock(&uc_mgr->mutex); + return err; +} + +static int get_value1(const char **value, struct list_head *value_list, + const char *identifier) +{ + struct ucm_value *val; + struct list_head *pos; + + 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)) { + *value = strdup(val->data); + if (*value == NULL) + return -ENOMEM; + return 0; + } + } + return -ENOENT; +} + +static int get_value3(const char **value, + const char *identifier, + struct list_head *value_list1, + struct list_head *value_list2, + struct list_head *value_list3) +{ + int err; + + err = get_value1(value, value_list1, identifier); + if (err >= 0 || err != -ENOENT) + return err; + err = get_value1(value, value_list2, identifier); + if (err >= 0 || err != -ENOENT) + return err; + err = get_value1(value, value_list3, identifier); + if (err >= 0 || err != -ENOENT) + return err; + return -ENOENT; +} + +/** + * \brief Get value + * \param uc_mgr Use case manager + * \param identifier Value identifier (string) + * \param value Returned value string + * \param modifier modifier name (string) + * \return Zero on success (value is filled), otherwise a negative error code + */ +static int get_value(snd_use_case_mgr_t *uc_mgr, + const char *identifier, + const char **value, + const char *modifier) +{ + struct use_case_modifier *mod; + int err; + + if (modifier != NULL) { + mod = find_modifier(uc_mgr, modifier); + if (mod != NULL) { + err = get_value1(value, &mod->value_list, identifier); + if (err >= 0 || err != -ENOENT) + return err; + } + } + err = get_value1(value, &uc_mgr->active_verb->value_list, identifier); + if (err >= 0 || err != -ENOENT) + return err; + err = get_value1(value, &uc_mgr->value_list, identifier); + if (err >= 0 || err != -ENOENT) + return err; + return -ENOENT; +} + +/** + * \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) +{ + char *str, *str1; + 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) + return -ENOENT; + *value = strdup(uc_mgr->active_verb->name); + if (*value == NULL) { + err = -ENOMEM; + goto __end; + } + err = 0; + } else { + str1 = strchr(identifier, '/'); + if (str1) { + str = strdup(str1 + 1); + if (str == NULL) { + err = -ENOMEM; + goto __end; + } + } else { + str = NULL; + } + err = get_value(uc_mgr, identifier, value, str); + if (str) + free(str); + } + __end: + pthread_mutex_unlock(&uc_mgr->mutex); + return err; +} + +long device_status(snd_use_case_mgr_t *uc_mgr, + const char *device_name) +{ + struct use_case_device *dev; + struct list_head *pos; + + list_for_each(pos, &uc_mgr->active_devices) { + dev = list_entry(pos, struct use_case_device, active_list); + if (strcmp(dev->name, device_name) == 0) + return 1; + } + return 0; +} + +long modifier_status(snd_use_case_mgr_t *uc_mgr, + const char *modifier_name) +{ + struct use_case_modifier *mod; + struct list_head *pos; + + list_for_each(pos, &uc_mgr->active_modifiers) { + mod = list_entry(pos, struct use_case_modifier, active_list); + if (strcmp(mod->name, modifier_name) == 0) + return 1; + } + return 0; +} + + +/** + * \brief Get current - integer + * \param uc_mgr Use case manager + * \param identifier + * \return Value if success, otherwise a negative error code + */ +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")) { + err = device_status(uc_mgr, str); + if (err >= 0) { + *value = err; + err = 0; + } + } else if (check_identifier(identifier, "_modstatus")) { + err = modifier_status(uc_mgr, str); + if (err >= 0) { + *value = err; + err = 0; + } + } else + err = -EINVAL; + 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; + + if (uc_mgr->active_verb && + strcmp(uc_mgr->active_verb->name, verb_name) == 0) + return 0; + if (strcmp(verb_name, SND_USE_CASE_VERB_INACTIVE) != 0) { + verb = find_verb(uc_mgr, verb_name); + if (verb == NULL) + return -ENOENT; + } else { + verb = NULL; + } + if (uc_mgr->active_verb) { + err = handle_transition_verb(uc_mgr, verb); + if (err == 0) { + err = dismantle_use_case(uc_mgr); + if (err < 0) + return err; + } else if (err == 1) { + uc_mgr->active_verb = verb; + verb = NULL; + } else { + verb = NULL; /* show error */ + } + } + if (verb) { + err = set_verb(uc_mgr, verb, 1); + if (err < 0) + uc_error("error: failed to initialize new use case: %s", + verb_name); + } + return err; +} + + +static int set_device_user(snd_use_case_mgr_t *uc_mgr, + const char *device_name, + int enable) +{ + struct use_case_device *device; + + if (uc_mgr->active_verb == NULL) + return -ENOENT; + device = find_device(uc_mgr->active_verb, device_name); + if (device == NULL) + return -ENOENT; + return set_device(uc_mgr, device, enable); +} + +static int set_modifier_user(snd_use_case_mgr_t *uc_mgr, + const char *modifier_name, + int enable) +{ + struct use_case_modifier *modifier; + + if (uc_mgr->active_verb == NULL) + return -ENOENT; + + modifier = find_modifier(uc_mgr, modifier_name); + if (modifier == NULL) + return -ENOENT; + return set_modifier(uc_mgr, modifier, enable); +} + +static int switch_device(snd_use_case_mgr_t *uc_mgr, + const char *old_device, + const char *new_device) +{ + struct use_case_device *xold, *xnew; + struct transition_sequence *trans; + struct list_head *pos; + int err, seq_found = 0; + + if (uc_mgr->active_verb == NULL) + return -ENOENT; + if (device_status(uc_mgr, old_device) == 0) { + uc_error("error: device %s not enabled", old_device); + return -EINVAL; + } + if (device_status(uc_mgr, new_device) != 0) { + uc_error("error: device %s already enabled", new_device); + return -EINVAL; + } + xold = find_device(uc_mgr->active_verb, old_device); + if (xold == NULL) + return -ENOENT; + xnew = find_device(uc_mgr->active_verb, new_device); + if (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, old_modifier); + if (xold == NULL) + return -ENOENT; + xnew = find_modifier(uc_mgr, new_modifier); + 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; + + 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 { + str = NULL; + } + if (check_identifier(identifier, "_swdev")) + err = switch_device(uc_mgr, str, value); + else if (check_identifier(identifier, "_swmod")) + err = switch_modifier(uc_mgr, str, value); + else + err = -EINVAL; + if (str) + free(str); + } + __end: + pthread_mutex_unlock(&uc_mgr->mutex); + return err; +} diff --git a/src/ucm/parser.c b/src/ucm/parser.c new file mode 100644 index 00000000..f3a75e6d --- /dev/null +++ b/src/ucm/parser.c @@ -0,0 +1,1180 @@ +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * Support for the verb/device/modifier core logic and API, + * command line tool and file parser was kindly sponsored by + * Texas Instruments Inc. + * Support for multiple active modifiers and devices, + * transition sequences, multiple client access and user defined use + * cases was kindly sponsored by Wolfson Microelectronics PLC. + * + * Copyright (C) 2008-2010 SlimLogic Ltd + * Copyright (C) 2010 Wolfson Microelectronics PLC + * Copyright (C) 2010 Texas Instruments Inc. + * Copyright (C) 2010 Red Hat Inc. + * Authors: Liam Girdwood + * Stefan Schmidt + * Justin Xu + * Jaroslav Kysela + */ + +#include "ucm_local.h" +#include + +/** The name of the environment variable containing the UCM directory */ +#define ALSA_CONFIG_UCM_VAR "ALSA_CONFIG_UCM" + +static int parse_sequence(snd_use_case_mgr_t *uc_mgr, + struct list_head *base, + snd_config_t *cfg); + +/* + * Parse string + */ +int parse_string(snd_config_t *n, char **res) +{ + int err; + + err = snd_config_get_string(n, (const char **)res); + if (err < 0) + return err; + *res = strdup(*res); + if (*res == NULL) + return -ENOMEM; + return 0; +} + + +/* + * Parse transition + */ +static int parse_transition(snd_use_case_mgr_t *uc_mgr, + struct list_head *tlist, + snd_config_t *cfg) +{ + struct transition_sequence *tseq; + const char *id; + snd_config_iterator_t i, next; + snd_config_t *n; + int err; + + if (snd_config_get_id(cfg, &id) < 0) + return -EINVAL; + + if (snd_config_get_type(cfg) != SND_CONFIG_TYPE_COMPOUND) { + uc_error("compound type expected for %s", id); + return -EINVAL; + } + + snd_config_for_each(i, next, cfg) { + n = snd_config_iterator_entry(i); + + if (snd_config_get_id(n, &id) < 0) + return -EINVAL; + + tseq = calloc(1, sizeof(*tseq)); + if (tseq == NULL) + return -ENOMEM; + INIT_LIST_HEAD(&tseq->transition_list); + + tseq->name = strdup(id); + if (tseq->name == NULL) { + free(tseq); + return -ENOMEM; + } + + err = parse_sequence(uc_mgr, &tseq->transition_list, n); + if (err < 0) { + uc_mgr_free_transition_element(tseq); + return err; + } + + list_add(&tseq->list, tlist); + } + return 0; +} + +/* + * Parse compound + */ +static int parse_compound(snd_use_case_mgr_t *uc_mgr, snd_config_t *cfg, + int (*fcn)(snd_use_case_mgr_t *, snd_config_t *, void *, void *), + void *data1, void *data2) +{ + const char *id; + snd_config_iterator_t i, next; + snd_config_t *n; + int err; + + if (snd_config_get_id(cfg, &id) < 0) + return -EINVAL; + + if (snd_config_get_type(cfg) != SND_CONFIG_TYPE_COMPOUND) { + uc_error("compound type expected for %s", id); + return -EINVAL; + } + /* parse compound */ + snd_config_for_each(i, next, cfg) { + n = snd_config_iterator_entry(i); + + if (snd_config_get_type(cfg) != SND_CONFIG_TYPE_COMPOUND) { + uc_error("compound type expected for %s", id); + return -EINVAL; + } + + err = fcn(uc_mgr, n, data1, data2); + if (err < 0) + return err; + } + + return 0; +} + + +/* + * Parse transition + */ +static int parse_supported_device(snd_use_case_mgr_t *uc_mgr ATTRIBUTE_UNUSED, + struct list_head *dlist, + snd_config_t *cfg) +{ + struct dev_list *sdev; + const char *id; + snd_config_iterator_t i, next; + snd_config_t *n; + int err; + + if (snd_config_get_id(cfg, &id) < 0) + return -EINVAL; + + if (snd_config_get_type(cfg) != SND_CONFIG_TYPE_COMPOUND) { + uc_error("compound type expected for %s", id); + return -EINVAL; + } + + snd_config_for_each(i, next, cfg) { + n = snd_config_iterator_entry(i); + + if (snd_config_get_id(n, &id) < 0) + return -EINVAL; + + sdev = calloc(1, sizeof(struct dev_list)); + if (sdev == NULL) + return -ENOMEM; + err = parse_string(n, &sdev->name); + if (err < 0) { + free(sdev); + return err; + } + list_add(&sdev->list, dlist); + } + return 0; +} + +/* + * Parse sequences. + * + * Sequence controls elements are in the following form:- + * + * cdev "hw:0" + * cset "element_id_syntax value_syntax" + * usleep time + * exec "any unix command with arguments" + * + * e.g. + * cset "name='Master Playback Switch' 0,0" + * cset "iface=PCM,name='Disable HDMI',index=1 0" + */ +static int parse_sequence(snd_use_case_mgr_t *uc_mgr ATTRIBUTE_UNUSED, + struct list_head *base, + snd_config_t *cfg) +{ + struct sequence_element *curr; + snd_config_iterator_t i, next; + snd_config_t *n; + int err, idx = 0; + const char *cmd = NULL; + + if (snd_config_get_type(cfg) != SND_CONFIG_TYPE_COMPOUND) { + uc_error("error: compound is expected for sequence definition"); + return -EINVAL; + } + + snd_config_for_each(i, next, cfg) { + const char *id; + idx ^= 1; + n = snd_config_iterator_entry(i); + err = snd_config_get_id(n, &id); + if (err < 0) + continue; + if (idx == 1) { + if (snd_config_get_type(n) != SND_CONFIG_TYPE_STRING) { + uc_error("error: string type is expected for sequence command"); + return -EINVAL; + } + snd_config_get_string(n, &cmd); + continue; + } + + /* alloc new sequence element */ + curr = calloc(1, sizeof(struct sequence_element)); + if (curr == NULL) + return -ENOMEM; + list_add_tail(&curr->list, base); + + if (strcmp(cmd, "cdev") == 0) { + curr->type = SEQUENCE_ELEMENT_TYPE_CDEV; + err = parse_string(n, &curr->data.cdev); + if (err < 0) { + uc_error("error: cdev requires a string!"); + return err; + } + continue; + } + + if (strcmp(cmd, "cset") == 0) { + curr->type = SEQUENCE_ELEMENT_TYPE_CSET; + err = parse_string(n, &curr->data.cset); + if (err < 0) { + uc_error("error: cset requires a string!"); + return err; + } + continue; + } + + if (strcmp(cmd, "usleep") == 0) { + curr->type = SEQUENCE_ELEMENT_TYPE_SLEEP; + err = snd_config_get_integer(n, &curr->data.sleep); + if (err < 0) { + uc_error("error: usleep requires integer!"); + return err; + } + continue; + } + + if (strcmp(cmd, "exec") == 0) { + curr->type = SEQUENCE_ELEMENT_TYPE_EXEC; + err = parse_string(n, &curr->data.exec); + if (err < 0) { + uc_error("error: exec requires a string!"); + return err; + } + continue; + } + + list_del(&curr->list); + uc_mgr_free_sequence_element(curr); + } + + return 0; +} + +/* + * Parse values. + * + * Parse values describing PCM, control/mixer settings and stream parameters. + * + * Value { + * TQ Voice + * CapturePCM "hw:1" + * PlaybackVolume "name='Master Playback Volume',index=2" + * PlaybackSwitch "name='Master Playback Switch',index=2" + * } + */ +static int parse_value(snd_use_case_mgr_t *uc_mgr ATTRIBUTE_UNUSED, + struct list_head *base, + snd_config_t *cfg) +{ + struct ucm_value *curr; + snd_config_iterator_t i, next; + snd_config_t *n; + long l; + long long ll; + double d; + snd_config_type_t type; + int err; + + if (snd_config_get_type(cfg) != SND_CONFIG_TYPE_COMPOUND) { + uc_error("error: compound is expected for value definition"); + return -EINVAL; + } + snd_config_for_each(i, next, cfg) { + const char *id; + n = snd_config_iterator_entry(i); + err = snd_config_get_id(n, &id); + if (err < 0) + continue; + + /* alloc new value */ + curr = calloc(1, sizeof(struct ucm_value)); + if (curr == NULL) + return -ENOMEM; + list_add_tail(&curr->list, base); + curr->name = strdup(id); + if (curr->name == NULL) + return -ENOMEM; + type = snd_config_get_type(n); + switch (type) { + case SND_CONFIG_TYPE_INTEGER: + curr->data = malloc(16); + if (curr->data == NULL) + return -ENOMEM; + snd_config_get_integer(n, &l); + sprintf(curr->data, "%li", l); + break; + case SND_CONFIG_TYPE_INTEGER64: + curr->data = malloc(32); + if (curr->data == NULL) + return -ENOMEM; + snd_config_get_integer64(n, &ll); + sprintf(curr->data, "%lli", ll); + break; + case SND_CONFIG_TYPE_REAL: + curr->data = malloc(64); + if (curr->data == NULL) + return -ENOMEM; + snd_config_get_real(n, &d); + sprintf(curr->data, "%-16g", d); + break; + case SND_CONFIG_TYPE_STRING: + err = parse_string(n, &curr->data); + if (err < 0) { + uc_error("error: unable to parse a string for id '%s'!", id); + return err; + } + break; + default: + uc_error("error: invalid type %i in Value compound", type); + return -EINVAL; + } + } + + return 0; +} + +/* + * Parse Modifier Use cases + * + * # Each modifier is described in new section. N modifier are allowed + * SectionModifier."Capture Voice" { + * + * Comment "Record voice call" + * SupportedDevice [ + * "x" + * "y" + * ] + * + * EnableSequence [ + * .... + * ] + * + * DisableSequence [ + * ... + * ] + * + * TransitionSequence."ToModifierName" [ + * ... + * ] + * + * # Optional TQ and ALSA PCMs + * Value { + * TQ Voice + * CapturePCM "hw:1" + * PlaybackVolume "name='Master Playback Volume',index=2" + * PlaybackSwitch "name='Master Playback Switch',index=2" + * } + * + * } + */ +static int parse_modifier(snd_use_case_mgr_t *uc_mgr, + snd_config_t *cfg, + void *data1, + void *data2) +{ + struct use_case_verb *verb = data1; + struct use_case_modifier *modifier; + char *name = data2; + const char *id; + snd_config_iterator_t i, next; + snd_config_t *n; + int err; + + /* allocate modifier */ + modifier = calloc(1, sizeof(*modifier)); + if (modifier == NULL) + return -ENOMEM; + INIT_LIST_HEAD(&modifier->enable_list); + INIT_LIST_HEAD(&modifier->disable_list); + INIT_LIST_HEAD(&modifier->transition_list); + INIT_LIST_HEAD(&modifier->dev_list); + INIT_LIST_HEAD(&modifier->value_list); + list_add_tail(&modifier->list, &verb->modifier_list); + err = snd_config_get_id(cfg, &id); + if (err < 0) + return err; + modifier->name = malloc(strlen(name) + strlen(id) + 2); + if (modifier->name == NULL) + return -ENOMEM; + strcpy(modifier->name, name); + strcat(modifier->name, "."); + strcat(modifier->name, id); + + snd_config_for_each(i, next, cfg) { + const char *id; + n = snd_config_iterator_entry(i); + if (snd_config_get_id(n, &id) < 0) + continue; + + if (strcmp(id, "Comment") == 0) { + err = parse_string(n, &modifier->comment); + if (err < 0) { + uc_error("error: failed to get modifier comment"); + return err; + } + continue; + } + + if (strcmp(id, "SupportedDevice") == 0) { + err = parse_supported_device(uc_mgr, &modifier->dev_list, n); + if (err < 0) { + uc_error("error: failed to parse supported" + " device list"); + return err; + } + } + + if (strcmp(id, "EnableSequence") == 0) { + err = parse_sequence(uc_mgr, &modifier->enable_list, n); + if (err < 0) { + uc_error("error: failed to parse modifier" + " enable sequence"); + return err; + } + continue; + } + + if (strcmp(id, "DisableSequence") == 0) { + err = parse_sequence(uc_mgr, &modifier->disable_list, n); + if (err < 0) { + uc_error("error: failed to parse modifier" + " disable sequence"); + return err; + } + continue; + } + + if (strcmp(id, "TransitionSequence") == 0) { + err = parse_transition(uc_mgr, &modifier->transition_list, n); + if (err < 0) { + uc_error("error: failed to parse transition" + " modifier"); + return err; + } + continue; + } + + if (strcmp(id, "Value") == 0) { + err = parse_value(uc_mgr, &modifier->value_list, n); + if (err < 0) { + uc_error("error: failed to parse Value"); + return err; + } + continue; + } + } + + if (list_empty(&modifier->dev_list)) { + uc_error("error: %s: modifier missing supported device sequence"); + return -EINVAL; + } + + return 0; +} + +/* + * Parse Device Use Cases + * + *# Each device is described in new section. N devices are allowed + *SectionDevice."Headphones".0 { + * Comment "Headphones connected to 3.5mm jack" + * + * EnableSequence [ + * .... + * ] + * + * DisableSequence [ + * ... + * ] + * + * TransitionSequence."ToDevice" [ + * ... + * ] + * + * Value { + * PlaybackVolume "name='Master Playback Volume',index=2" + * PlaybackSwitch "name='Master Playback Switch',index=2" + * } + * } + */ +static int parse_device_index(snd_use_case_mgr_t *uc_mgr, + snd_config_t *cfg, + void *data1, + void *data2) +{ + struct use_case_verb *verb = data1; + char *name = data2; + struct use_case_device *device; + const char *id; + snd_config_iterator_t i, next; + snd_config_t *n; + int err; + + device = calloc(1, sizeof(*device)); + if (device == NULL) + return -ENOMEM; + INIT_LIST_HEAD(&device->enable_list); + INIT_LIST_HEAD(&device->disable_list); + INIT_LIST_HEAD(&device->transition_list); + INIT_LIST_HEAD(&device->value_list); + list_add_tail(&device->list, &verb->device_list); + if (snd_config_get_id(cfg, &id) < 0) + return -EINVAL; + device->name = malloc(strlen(name) + strlen(id) + 2); + if (device->name == NULL) + return -ENOMEM; + strcpy(device->name, name); + strcat(device->name, "."); + strcat(device->name, id); + + snd_config_for_each(i, next, cfg) { + const char *id; + n = snd_config_iterator_entry(i); + if (snd_config_get_id(n, &id) < 0) + continue; + + if (strcmp(id, "Comment") == 0) { + err = parse_string(n, &device->comment); + if (err < 0) { + uc_error("error: failed to get device comment"); + return err; + } + continue; + } + + if (strcmp(id, "EnableSequence") == 0) { + uc_dbg("EnableSequence"); + err = parse_sequence(uc_mgr, &device->enable_list, n); + if (err < 0) { + uc_error("error: failed to parse device enable" + " sequence"); + return err; + } + continue; + } + + if (strcmp(id, "DisableSequence") == 0) { + uc_dbg("DisableSequence"); + err = parse_sequence(uc_mgr, &device->disable_list, n); + if (err < 0) { + uc_error("error: failed to parse device disable" + " sequence"); + return err; + } + continue; + } + + if (strcmp(id, "TransitionSequence") == 0) { + uc_dbg("TransitionSequence"); + err = parse_transition(uc_mgr, &device->transition_list, n); + if (err < 0) { + uc_error("error: failed to parse transition" + " device"); + return err; + } + continue; + } + + if (strcmp(id, "Value") == 0) { + err = parse_value(uc_mgr, &device->value_list, n); + if (err < 0) { + uc_error("error: failed to parse Value"); + return err; + } + continue; + } + } + return 0; +} + +static int parse_device_name(snd_use_case_mgr_t *uc_mgr, + snd_config_t *cfg, + void *data1, + void *data2 ATTRIBUTE_UNUSED) +{ + const char *id; + int err; + + err = snd_config_get_id(cfg, &id); + if (err < 0) + return err; + return parse_compound(uc_mgr, cfg, parse_device_index, + data1, (void *)id); +} + +static int parse_modifier_name(snd_use_case_mgr_t *uc_mgr, + snd_config_t *cfg, + void *data1, + void *data2 ATTRIBUTE_UNUSED) +{ + const char *id; + int err; + + err = snd_config_get_id(cfg, &id); + if (err < 0) + return err; + return parse_compound(uc_mgr, cfg, parse_modifier, + data1, (void *)id); +} + +/* + * Parse Verb Section + * + * # Example Use case verb section for Voice call blah + * # By Joe Blogs + * + * SectionVerb { + * # enable and disable sequences are compulsory + * EnableSequence [ + * cset "name='Master Playback Switch',index=2 0,0" + * cset "name='Master Playback Volume',index=2 25,25" + * msleep 50 + * cset "name='Master Playback Switch',index=2 1,1" + * cset "name='Master Playback Volume',index=2 50,50" + * ] + * + * DisableSequence [ + * cset "name='Master Playback Switch',index=2 0,0" + * cset "name='Master Playback Volume',index=2 25,25" + * msleep 50 + * cset "name='Master Playback Switch',index=2 1,1" + * cset "name='Master Playback Volume',index=2 50,50" + * ] + * + * # Optional transition verb + * TransitionSequence."ToCaseName" [ + * msleep 1 + * ] + * + * # Optional TQ and ALSA PCMs + * Value { + * TQ HiFi + * CapturePCM "hw:0" + * PlaybackPCM "hw:0" + * } + * } + */ +static int parse_verb(snd_use_case_mgr_t *uc_mgr, + struct use_case_verb *verb, + snd_config_t *cfg) +{ + snd_config_iterator_t i, next; + snd_config_t *n; + int err; + + /* parse verb section */ + snd_config_for_each(i, next, cfg) { + const char *id; + n = snd_config_iterator_entry(i); + if (snd_config_get_id(n, &id) < 0) + continue; + + if (strcmp(id, "EnableSequence") == 0) { + uc_dbg("Parse EnableSequence"); + err = parse_sequence(uc_mgr, &verb->enable_list, n); + if (err < 0) { + uc_error("error: failed to parse verb enable sequence"); + return err; + } + continue; + } + + if (strcmp(id, "DisableSequence") == 0) { + uc_dbg("Parse DisableSequence"); + err = parse_sequence(uc_mgr, &verb->disable_list, n); + if (err < 0) { + uc_error("error: failed to parse verb disable sequence"); + return err; + } + continue; + } + + if (strcmp(id, "TransitionSequence") == 0) { + uc_dbg("Parse TransitionSequence"); + err = parse_transition(uc_mgr, &verb->transition_list, n); + if (err < 0) { + uc_error("error: failed to parse transition sequence"); + return err; + } + continue; + } + + if (strcmp(id, "Value") == 0) { + uc_dbg("Parse Value"); + err = parse_value(uc_mgr, &verb->value_list, n); + if (err < 0) + return err; + continue; + } + } + + return 0; +} + +/* + * Parse a Use case verb file. + * + * This file contains the following :- + * o Verb enable and disable sequences. + * o Supported Device enable and disable sequences for verb. + * o Supported Modifier enable and disable sequences for verb + * o Optional QoS for the verb and modifiers. + * o Optional PCM device ID for verb and modifiers + * o Alias kcontrols IDs for master and volumes and mutes. + */ +static int parse_verb_file(snd_use_case_mgr_t *uc_mgr, + const char *use_case_name, + const char *comment, + const char *file) +{ + snd_config_iterator_t i, next; + snd_config_t *n; + struct use_case_verb *verb; + snd_config_t *cfg; + char filename[MAX_FILE]; + char *env = getenv(ALSA_CONFIG_UCM_VAR); + int err; + + /* allocate verb */ + verb = calloc(1, sizeof(struct use_case_verb)); + if (verb == NULL) + return -ENOMEM; + INIT_LIST_HEAD(&verb->enable_list); + INIT_LIST_HEAD(&verb->disable_list); + INIT_LIST_HEAD(&verb->transition_list); + INIT_LIST_HEAD(&verb->device_list); + INIT_LIST_HEAD(&verb->modifier_list); + INIT_LIST_HEAD(&verb->value_list); + list_add_tail(&verb->list, &uc_mgr->verb_list); + verb->name = strdup(use_case_name); + if (verb->name == NULL) + return -ENOMEM; + verb->comment = strdup(comment); + if (verb->comment == NULL) + return -ENOMEM; + + /* open Verb file for reading */ + snprintf(filename, sizeof(filename), "%s/%s/%s", + env ? env : ALSA_USE_CASE_DIR, + uc_mgr->card_name, file); + filename[sizeof(filename)-1] = '\0'; + + err = uc_mgr_config_load(filename, &cfg); + if (err < 0) { + uc_error("error: failed to open verb file %s : %d", + filename, -errno); + return err; + } + + /* parse master config sections */ + snd_config_for_each(i, next, cfg) { + const char *id; + n = snd_config_iterator_entry(i); + if (snd_config_get_id(n, &id) < 0) + continue; + + /* find verb section and parse it */ + if (strcmp(id, "SectionVerb") == 0) { + err = parse_verb(uc_mgr, verb, n); + if (err < 0) { + uc_error("error: %s failed to parse verb", + file); + return err; + } + continue; + } + + /* find device sections and parse them */ + if (strcmp(id, "SectionDevice") == 0) { + err = parse_compound(uc_mgr, n, + parse_device_name, verb, NULL); + if (err < 0) { + uc_error("error: %s failed to parse device", + file); + return err; + } + continue; + } + + /* find modifier sections and parse them */ + if (strcmp(id, "SectionModifier") == 0) { + err = parse_compound(uc_mgr, n, + parse_modifier_name, verb, NULL); + if (err < 0) { + uc_error("error: %s failed to parse modifier", + file); + return err; + } + continue; + } + } + + /* use case verb must have at least 1 device */ + if (list_empty(&verb->device_list)) { + uc_error("error: no use case device defined", file); + return -EINVAL; + } + return 0; +} + +/* + * Parse master section for "Use Case" and "File" tags. + */ +static int parse_master_section(snd_use_case_mgr_t *uc_mgr, snd_config_t *cfg, + void *data1 ATTRIBUTE_UNUSED, + void *data2 ATTRIBUTE_UNUSED) +{ + snd_config_iterator_t i, next; + snd_config_t *n; + const char *use_case_name, *file = NULL, *comment = NULL; + int err; + + if (snd_config_get_id(cfg, &use_case_name) < 0) { + uc_error("unable to get name for use case section"); + return -EINVAL; + } + + if (snd_config_get_type(cfg) != SND_CONFIG_TYPE_COMPOUND) { + uc_error("compound type expected for use case section"); + return -EINVAL; + } + /* parse master config sections */ + snd_config_for_each(i, next, cfg) { + const char *id; + n = snd_config_iterator_entry(i); + if (snd_config_get_id(n, &id) < 0) + continue; + + /* get use case verb file name */ + if (strcmp(id, "File") == 0) { + err = snd_config_get_string(n, &file); + if (err < 0) { + uc_error("failed to get File"); + return err; + } + continue; + } + + /* get optional use case comment */ + if (strncmp(id, "Comment", 7) == 0) { + err = snd_config_get_string(n, &comment); + if (err < 0) { + uc_error("error: failed to get Comment"); + return err; + } + continue; + } + + uc_error("unknown field %s in master section"); + } + + uc_dbg("use_case_name %s file %s end %d", use_case_name, file, end); + + /* do we have both use case name and file ? */ + if (!file) { + uc_error("error: use case missing file"); + return -EINVAL; + } + + /* parse verb file */ + return parse_verb_file(uc_mgr, use_case_name, comment, file); +} + +/* + * parse controls + */ +static int parse_controls(snd_use_case_mgr_t *uc_mgr, snd_config_t *cfg) +{ + int err; + + if (!list_empty(&uc_mgr->default_list)) { + uc_error("Default list is not empty"); + return -EINVAL; + } + err = parse_sequence(uc_mgr, &uc_mgr->default_list, cfg); + if (err < 0) { + uc_error("Unable to parse SectionDefaults"); + return err; + } + + return 0; +} + +/* + * Each sound card has a master sound card file that lists all the supported + * use case verbs for that sound card. i.e. + * + * #Example master file for blah sound card + * #By Joe Blogs + * + * Comment "Nice Abstracted Soundcard" + * + * # The file is divided into Use case sections. One section per use case verb. + * + * SectionUseCase."Voice Call" { + * File "voice_call_blah" + * Comment "Make a voice phone call." + * } + * + * SectionUseCase."HiFi" { + * File "hifi_blah" + * Comment "Play and record HiFi quality Music." + * } + * + * # Define Value defaults + * + * ValueDefaults { + * PlaybackCTL "hw:CARD=0" + * CaptureCTL "hw:CARD=0" + * } + * + * # This file also stores the default sound card state. + * + * SectionDefaults [ + * cset "name='Master Playback Switch',index=2 1,1" + * cset "name='Master Playback Volume',index=2 25,25" + * cset "name='Master Mono Playback',index=1 0" + * cset "name='Master Mono Playback Volume',index=1 0" + * cset "name='PCM Switch',index=2 1,1" + * exec "some binary here" + * msleep 50 + * ........ + * ] + * + * # End of example file. + */ +static int parse_master_file(snd_use_case_mgr_t *uc_mgr, snd_config_t *cfg) +{ + snd_config_iterator_t i, next; + snd_config_t *n; + const char *id; + int err; + + if (snd_config_get_type(cfg) != SND_CONFIG_TYPE_COMPOUND) { + uc_error("compound type expected for master file"); + return -EINVAL; + } + + /* parse master config sections */ + snd_config_for_each(i, next, cfg) { + + n = snd_config_iterator_entry(i); + if (snd_config_get_id(n, &id) < 0) + continue; + + if (strcmp(id, "Comment") == 0) { + err = parse_string(n, &uc_mgr->comment); + if (err < 0) { + uc_error("error: failed to get master comment"); + return err; + } + continue; + } + + /* find use case section and parse it */ + if (strcmp(id, "SectionUseCase") == 0) { + err = parse_compound(uc_mgr, n, + parse_master_section, + NULL, NULL); + if (err < 0) + return err; + continue; + } + + /* find default control values section and parse it */ + if (strcmp(id, "SectionDefaults") == 0) { + err = parse_controls(uc_mgr, n); + if (err < 0) + return err; + continue; + } + + /* get the default values */ + if (strcmp(id, "ValueDefaults") == 0) { + err = parse_value(uc_mgr, &uc_mgr->value_list, n); + if (err < 0) { + uc_error("error: failed to parse ValueDefaults"); + return err; + } + continue; + } + + uc_error("uknown master file field %s", id); + } + return 0; +} + +static int load_master_config(const char *card_name, snd_config_t **cfg) +{ + char filename[MAX_FILE]; + char *env = getenv(ALSA_CONFIG_UCM_VAR); + int err; + + snprintf(filename, sizeof(filename)-1, + "%s/%s/%s.conf", env ? env : ALSA_USE_CASE_DIR, + card_name, card_name); + filename[MAX_FILE-1] = '\0'; + + err = uc_mgr_config_load(filename, cfg); + if (err < 0) { + uc_error("error: could not parse configuration for card %s", + card_name); + return err; + } + + return 0; +} + +/* load master use case file for sound card */ +int uc_mgr_import_master_config(snd_use_case_mgr_t *uc_mgr) +{ + snd_config_t *cfg; + int err; + + err = load_master_config(uc_mgr->card_name, &cfg); + if (err < 0) + return err; + err = parse_master_file(uc_mgr, cfg); + snd_config_delete(cfg); + if (err < 0) + uc_mgr_free_verb(uc_mgr); + + return err; +} + +static int filename_filter(const struct dirent *dirent) +{ + if (dirent == NULL) + return 0; + if (dirent->d_type == DT_DIR) { + if (dirent->d_name[0] == '.') { + if (dirent->d_name[1] == '\0') + return 0; + if (dirent->d_name[1] == '.' && + dirent->d_name[2] == '\0') + return 0; + } + return 1; + } + return 0; +} + +/* scan all cards and comments */ +int uc_mgr_scan_master_configs(const char **_list[]) +{ + char filename[MAX_FILE], dfl[MAX_FILE]; + char *env = getenv(ALSA_CONFIG_UCM_VAR); + const char **list; + snd_config_t *cfg, *c; + int i, cnt, err; + ssize_t ss; + struct dirent **namelist; + + snprintf(filename, sizeof(filename)-1, + "%s", env ? env : ALSA_USE_CASE_DIR); + filename[MAX_FILE-1] = '\0'; + + err = scandir(filename, &namelist, filename_filter, versionsort); + if (err < 0) { + err = -errno; + uc_error("error: could not scan directory %s: %s", + filename, strerror(-err)); + return err; + } + cnt = err; + + dfl[0] = '\0'; + if (strlen(filename) + 8 < sizeof(filename)) { + strcat(filename, "/default"); + ss = readlink(filename, dfl, sizeof(dfl)-1); + if (ss >= 0) { + dfl[ss] = '\0'; + dfl[sizeof(dfl)-1] = '\0'; + if (dfl[0] && dfl[strlen(dfl)-1] == '/') + dfl[strlen(dfl)-1] = '\0'; + } else { + dfl[0] = '\0'; + } + } + + list = calloc(1, cnt * 2 * sizeof(char *)); + if (list == NULL) { + err = -ENOMEM; + goto __err; + } + + for (i = 0; i < cnt; i++) { + err = load_master_config(namelist[i]->d_name, &cfg); + if (err < 0) + goto __err; + err = snd_config_search(cfg, "Comment", &c); + if (err >= 0) { + err = parse_string(c, (char **)&list[i*2+1]); + if (err < 0) { + snd_config_delete(cfg); + goto __err; + } + } + snd_config_delete(cfg); + list[i * 2] = strdup(namelist[i]->d_name); + if (list[i * 2] == NULL) { + err = -ENOMEM; + goto __err; + } + if (strcmp(dfl, list[i * 2]) == 0) { + /* default to top */ + const char *save1 = list[i * 2]; + const char *save2 = list[i * 2 + 1]; + memmove(list + 2, list, i * 2 * sizeof(char *)); + list[0] = save1; + list[1] = save2; + } + } + err = cnt * 2; + + __err: + for (i = 0; i < cnt; i++) { + free(namelist[i]); + if (err < 0) { + free((void *)list[i * 2]); + free((void *)list[i * 2 + 1]); + } + } + free(namelist); + + if (err >= 0) + *_list = list; + + return err; +} diff --git a/src/ucm/ucm_local.h b/src/ucm/ucm_local.h new file mode 100644 index 00000000..13e82da9 --- /dev/null +++ b/src/ucm/ucm_local.h @@ -0,0 +1,208 @@ +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * Support for the verb/device/modifier core logic and API, + * command line tool and file parser was kindly sponsored by + * Texas Instruments Inc. + * Support for multiple active modifiers and devices, + * transition sequences, multiple client access and user defined use + * cases was kindly sponsored by Wolfson Microelectronics PLC. + * + * Copyright (C) 2008-2010 SlimLogic Ltd + * Copyright (C) 2010 Wolfson Microelectronics PLC + * Copyright (C) 2010 Texas Instruments Inc. + * Copyright (C) 2010 Red Hat Inc. + * Authors: Liam Girdwood + * Stefan Schmidt + * Justin Xu + * Jaroslav Kysela + */ + + + +#if 0 +#define UC_MGR_DEBUG +#endif + +#include "local.h" +#include "use-case.h" + +#define MAX_FILE 256 +#define ALSA_USE_CASE_DIR ALSA_CONFIG_DIR "/ucm" + +#define SEQUENCE_ELEMENT_TYPE_CDEV 1 +#define SEQUENCE_ELEMENT_TYPE_CSET 2 +#define SEQUENCE_ELEMENT_TYPE_SLEEP 3 +#define SEQUENCE_ELEMENT_TYPE_EXEC 4 + +struct ucm_value { + struct list_head list; + char *name; + char *data; +}; + +struct sequence_element { + struct list_head list; + unsigned int type; + union { + long sleep; /* Sleep time in msecs if sleep element, else 0 */ + char *cdev; + char *cset; + char *exec; + } data; +}; + +/* + * Transition sequences. i.e. transition between one verb, device, mod to another + */ +struct transition_sequence { + struct list_head list; + char *name; + struct list_head transition_list; +}; + +/* + * Modifier Supported Devices. + */ +struct dev_list { + struct list_head list; + char *name; +}; + + +/* + * Describes a Use Case Modifier and it's enable and disable sequences. + * A use case verb can have N modifiers. + */ +struct use_case_modifier { + struct list_head list; + struct list_head active_list; + + char *name; + char *comment; + + /* modifier enable and disable sequences */ + struct list_head enable_list; + struct list_head disable_list; + + /* modifier transition list */ + struct list_head transition_list; + + /* list of supported devices per modifier */ + struct list_head dev_list; + + /* values */ + struct list_head value_list; +}; + +/* + * Describes a Use Case Device and it's enable and disable sequences. + * A use case verb can have N devices. + */ +struct use_case_device { + struct list_head list; + struct list_head active_list; + + char *name; + char *comment; + + /* device enable and disable sequences */ + struct list_head enable_list; + struct list_head disable_list; + + /* device transition list */ + struct list_head transition_list; + + /* value list */ + struct list_head value_list; +}; + +/* + * Describes a Use Case Verb and it's enable and disable sequences. + * A use case verb can have N devices and N modifiers. + */ +struct use_case_verb { + struct list_head list; + + unsigned int active: 1; + + char *name; + char *comment; + + /* verb enable and disable sequences */ + struct list_head enable_list; + struct list_head disable_list; + + /* verb transition list */ + struct list_head transition_list; + + /* hardware devices that can be used with this use case */ + struct list_head device_list; + + /* modifiers that can be used with this use case */ + struct list_head modifier_list; + + /* value list */ + struct list_head value_list; +}; + +/* + * Manages a sound card and all its use cases. + */ +struct snd_use_case_mgr { + char *card_name; + char *comment; + + /* use case verb, devices and modifier configs parsed from files */ + struct list_head verb_list; + + /* default settings - sequence */ + struct list_head default_list; + + /* default settings - value list */ + struct list_head value_list; + + /* current status */ + struct use_case_verb *active_verb; + struct list_head active_devices; + struct list_head active_modifiers; + + /* locking */ + pthread_mutex_t mutex; + + /* change to list of ctl handles */ + snd_ctl_t *ctl; + char *ctl_dev; +}; + +#define uc_error SNDERR + +#ifdef UC_MGR_DEBUG +#define uc_dbg SNDERR +#else +#define uc_dbg(fmt, arg...) +#endif + +void uc_mgr_error(const char *fmt, ...); +void uc_mgr_stdout(const char *fmt, ...); + +int uc_mgr_config_load(const char *file, snd_config_t **cfg); +int uc_mgr_import_master_config(snd_use_case_mgr_t *uc_mgr); +int uc_mgr_scan_master_configs(const char **_list[]); + +void uc_mgr_free_sequence_element(struct sequence_element *seq); +void uc_mgr_free_transition_element(struct transition_sequence *seq); +void uc_mgr_free_verb(snd_use_case_mgr_t *uc_mgr); +void uc_mgr_free(snd_use_case_mgr_t *uc_mgr); diff --git a/src/ucm/utils.c b/src/ucm/utils.c new file mode 100644 index 00000000..2def0b8b --- /dev/null +++ b/src/ucm/utils.c @@ -0,0 +1,236 @@ +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * Support for the verb/device/modifier core logic and API, + * command line tool and file parser was kindly sponsored by + * Texas Instruments Inc. + * Support for multiple active modifiers and devices, + * transition sequences, multiple client access and user defined use + * cases was kindly sponsored by Wolfson Microelectronics PLC. + * + * Copyright (C) 2008-2010 SlimLogic Ltd + * Copyright (C) 2010 Wolfson Microelectronics PLC + * Copyright (C) 2010 Texas Instruments Inc. + * Copyright (C) 2010 Red Hat Inc. + * Authors: Liam Girdwood + * Stefan Schmidt + * Justin Xu + * Jaroslav Kysela + */ + +#include "ucm_local.h" + +void uc_mgr_error(const char *fmt,...) +{ + va_list va; + va_start(va, fmt); + fprintf(stderr, "ucm: "); + vfprintf(stderr, fmt, va); + va_end(va); +} + +void uc_mgr_stdout(const char *fmt,...) +{ + va_list va; + va_start(va, fmt); + vfprintf(stdout, fmt, va); + va_end(va); +} + +int uc_mgr_config_load(const char *file, snd_config_t **cfg) +{ + FILE *fp; + snd_input_t *in; + snd_config_t *top; + int err; + + fp = fopen(file, "r"); + if (fp == NULL) { + err = -errno; + goto __err; + } + err = snd_input_stdio_attach(&in, fp, 1); + if (err < 0) { + __err: + uc_error("could not open configuration file %s", file); + return err; + } + err = snd_config_top(&top); + if (err < 0) + return err; + err = snd_config_load(top, in); + if (err < 0) { + uc_error("could not load configuration file %s", file); + snd_config_delete(top); + return err; + } + err = snd_input_close(in); + if (err < 0) { + snd_config_delete(top); + return err; + } + *cfg = top; + return 0; +} + +void uc_mgr_free_value(struct list_head *base) +{ + struct list_head *pos, *npos; + struct ucm_value *val; + + list_for_each_safe(pos, npos, base) { + val = list_entry(pos, struct ucm_value, list); + free(val->name); + free(val->data); + list_del(&val->list); + free(val); + } +} + +void uc_mgr_free_dev_list(struct list_head *base) +{ + struct list_head *pos, *npos; + struct dev_list *dlist; + + list_for_each_safe(pos, npos, base) { + dlist = list_entry(pos, struct dev_list, list); + free(dlist->name); + list_del(&dlist->list); + free(dlist); + } +} + +void uc_mgr_free_sequence_element(struct sequence_element *seq) +{ + if (seq == NULL) + return; + switch (seq->type) { + case SEQUENCE_ELEMENT_TYPE_CSET: + case SEQUENCE_ELEMENT_TYPE_EXEC: + free(seq->data.exec); + break; + default: + break; + } + free(seq); +} + +void uc_mgr_free_sequence(struct list_head *base) +{ + struct list_head *pos, *npos; + struct sequence_element *seq; + + list_for_each_safe(pos, npos, base) { + seq = list_entry(pos, struct sequence_element, list); + list_del(&seq->list); + uc_mgr_free_sequence_element(seq); + } +} + +void uc_mgr_free_transition_element(struct transition_sequence *tseq) +{ + free(tseq->name); + uc_mgr_free_sequence(&tseq->transition_list); + free(tseq); +} + +void uc_mgr_free_transition(struct list_head *base) +{ + struct list_head *pos, *npos; + struct transition_sequence *tseq; + + list_for_each_safe(pos, npos, base) { + tseq = list_entry(pos, struct transition_sequence, list); + list_del(&tseq->list); + uc_mgr_free_transition_element(tseq); + } +} + +void uc_mgr_free_modifier(struct list_head *base) +{ + struct list_head *pos, *npos; + struct use_case_modifier *mod; + + list_for_each_safe(pos, npos, base) { + mod = list_entry(pos, struct use_case_modifier, list); + free(mod->name); + free(mod->comment); + uc_mgr_free_sequence(&mod->enable_list); + uc_mgr_free_sequence(&mod->disable_list); + uc_mgr_free_transition(&mod->transition_list); + uc_mgr_free_dev_list(&mod->dev_list); + uc_mgr_free_value(&mod->value_list); + list_del(&mod->list); + free(mod); + } +} + +void uc_mgr_free_device(struct list_head *base) +{ + struct list_head *pos, *npos; + struct use_case_device *dev; + + list_for_each_safe(pos, npos, base) { + dev = list_entry(pos, struct use_case_device, list); + free(dev->name); + free(dev->comment); + uc_mgr_free_sequence(&dev->enable_list); + uc_mgr_free_sequence(&dev->disable_list); + uc_mgr_free_transition(&dev->transition_list); + uc_mgr_free_value(&dev->value_list); + list_del(&dev->list); + free(dev); + } +} + +void uc_mgr_free_verb(snd_use_case_mgr_t *uc_mgr) +{ + struct list_head *pos, *npos; + struct use_case_verb *verb; + + list_for_each_safe(pos, npos, &uc_mgr->verb_list) { + verb = list_entry(pos, struct use_case_verb, list); + free(verb->name); + free(verb->comment); + uc_mgr_free_sequence(&verb->enable_list); + uc_mgr_free_sequence(&verb->disable_list); + uc_mgr_free_transition(&verb->transition_list); + uc_mgr_free_value(&verb->value_list); + uc_mgr_free_device(&verb->device_list); + uc_mgr_free_modifier(&verb->modifier_list); + list_del(&verb->list); + free(verb); + } + uc_mgr_free_sequence(&uc_mgr->default_list); + uc_mgr_free_value(&uc_mgr->value_list); + free(uc_mgr->comment); + uc_mgr->comment = NULL; + uc_mgr->active_verb = NULL; + INIT_LIST_HEAD(&uc_mgr->active_devices); + INIT_LIST_HEAD(&uc_mgr->active_modifiers); + if (uc_mgr->ctl != NULL) { + snd_ctl_close(uc_mgr->ctl); + uc_mgr->ctl = NULL; + } + free(uc_mgr->ctl_dev); + uc_mgr->ctl_dev = NULL; +} + +void uc_mgr_free(snd_use_case_mgr_t *uc_mgr) +{ + uc_mgr_free_verb(uc_mgr); + free(uc_mgr->card_name); + free(uc_mgr); +} diff --git a/test/ucm/TestHDA/Case1.conf b/test/ucm/TestHDA/Case1.conf new file mode 100644 index 00000000..3d8cddf8 --- /dev/null +++ b/test/ucm/TestHDA/Case1.conf @@ -0,0 +1,25 @@ +SectionVerb { + EnableSequence [ + exec "Case1 enable seq" + exec "Case1 enable seq 2" + ] + DisableSequence [ + exec "Case2 disable seq" + ] + TransitionVerb."Case2" [ + exec "Case1->Case2 transition seq" + ] + Value { + TestValue1 "123" + } +} + +SectionDevice."Device1".0 { + +} + +SectionModifier."Modifier1" { + SupportedDevice [ + "Device1" + ] +} diff --git a/test/ucm/TestHDA/TestHDA.conf b/test/ucm/TestHDA/TestHDA.conf new file mode 100644 index 00000000..9b93b96e --- /dev/null +++ b/test/ucm/TestHDA/TestHDA.conf @@ -0,0 +1,11 @@ +Comment "A test HDA card" + +SectionUseCase."Case1" { + File "Case1.conf" + Comment "Case1 Comment" +} + +SectionDefaults [ + exec "my prg" + msleep 1 +] diff --git a/test/ucm/anothercard/anothercard.conf b/test/ucm/anothercard/anothercard.conf new file mode 100644 index 00000000..3d9ed7de --- /dev/null +++ b/test/ucm/anothercard/anothercard.conf @@ -0,0 +1 @@ +Comment "Another Card" diff --git a/test/ucm/testcard1/testcard1.conf b/test/ucm/testcard1/testcard1.conf new file mode 100644 index 00000000..e69de29b