diff --git a/alsa-info/alsa-info.sh b/alsa-info/alsa-info.sh index 3871b97a2..01a910a43 100755 --- a/alsa-info/alsa-info.sh +++ b/alsa-info/alsa-info.sh @@ -1,7 +1,7 @@ #!/bin/bash -SCRIPT_VERSION=0.4.65 -CHANGELOG="http://www.alsa-project.org/alsa-info.sh.changelog" +SCRIPT_VERSION=0.5.0 +CHANGELOG="https://www.alsa-project.org/alsa-info.sh.changelog" ################################################################################# #Copyright (C) 2007 Free Software Foundation. @@ -45,7 +45,7 @@ update() { test -z "$WGET" -o ! -x "$WGET" && return SHFILE=$(mktemp -t alsa-info.XXXXXXXXXX) || exit 1 - wget -O $SHFILE "http://www.alsa-project.org/alsa-info.sh" >/dev/null 2>&1 + wget -O $SHFILE "https://www.alsa-project.org/alsa-info.sh" >/dev/null 2>&1 REMOTE_VERSION=$(grep SCRIPT_VERSION $SHFILE | head -n1 | sed 's/.*=//') if [ -s "$SHFILE" -a "$REMOTE_VERSION" != "$SCRIPT_VERSION" ]; then if [[ -n $DIALOG ]] @@ -233,7 +233,7 @@ withdmesg() { echo "!!ALSA/HDA dmesg" >> $FILE echo "!!--------------" >> $FILE echo "" >> $FILE - dmesg | grep -C1 -E 'ALSA|HDA|HDMI|snd[_-]|sound|hda.codec|hda.intel' >> $FILE + dmesg | grep -C1 -E 'ALSA|HDA|HDMI|snd[_-]|sound|audio|hda.codec|hda.intel' >> $FILE echo "" >> $FILE echo "" >> $FILE } @@ -425,9 +425,11 @@ get_alsa_library_version ALSA_UTILS_VERSION=$(amixer -v | awk '{ print $3 }') ESDINST=$(command -v esd) +PWINST=$(command -v pipewire) PAINST=$(command -v pulseaudio) ARTSINST=$(command -v artsd) JACKINST=$(command -v jackd) +JACK2INST=$(command -v jackdbus) ROARINST=$(command -v roard) DMIDECODE=$(command -v dmidecode) @@ -461,7 +463,7 @@ if [ -d /sys/bus/acpi/devices ]; then done fi -cat /proc/asound/modules 2>/dev/null | awk '{ print $2 }' > $TEMPDIR/alsamodules.tmp +awk '{ print $2 " (card " $1 ")" }' < /proc/asound/modules > $TEMPDIR/alsamodules.tmp 2> /dev/null cat /proc/asound/cards > $TEMPDIR/alsacards.tmp if [[ ! -z "$LSPCI" ]]; then for class in 0401 0402 0403; do @@ -555,6 +557,13 @@ echo "" >> $FILE echo "!!Sound Servers on this system" >> $FILE echo "!!----------------------------" >> $FILE echo "" >> $FILE +if [[ -n $PWINST ]];then +[[ $(pgrep '^(.*/)?pipewire$') ]] && PWRUNNING="Yes" || PWRUNNING="No" +echo "PipeWire:" >> $FILE +echo " Installed - Yes ($PWINST)" >> $FILE +echo " Running - $PWRUNNING" >> $FILE +echo "" >> $FILE +fi if [[ -n $PAINST ]];then [[ $(pgrep '^(.*/)?pulseaudio$') ]] && PARUNNING="Yes" || PARUNNING="No" echo "Pulseaudio:" >> $FILE @@ -583,6 +592,13 @@ echo " Installed - Yes ($JACKINST)" >> $FILE echo " Running - $JACKRUNNING" >> $FILE echo "" >> $FILE fi +if [[ -n $JACK2INST ]];then +[[ $(pgrep '^(.*/)?jackdbus$') ]] && JACK2RUNNING="Yes" || JACK2RUNNING="No" +echo "Jack2:" >> $FILE +echo " Installed - Yes ($JACK2INST)" >> $FILE +echo " Running - $JACK2RUNNING" >> $FILE +echo "" >> $FILE +fi if [[ -n $ROARINST ]];then [[ $(pgrep '^(.*/)?roard$') ]] && ROARRUNNING="Yes" || ROARRUNNING="No" echo "RoarAudio:" >> $FILE @@ -611,8 +627,7 @@ echo "" >> $FILE echo "" >> $FILE fi -if [ "$SNDOPTIONS" ] -then +if [ "$SNDOPTIONS" ]; then echo "!!Modprobe options (Sound related)" >> $FILE echo "!!--------------------------------" >> $FILE echo "" >> $FILE @@ -635,6 +650,18 @@ if [ -d "$SYSFS" ]; then echo "" >> $FILE done echo "" >> $FILE + echo "!!Sysfs card info" >> $FILE + echo "!!---------------" >> $FILE + echo "" >> $FILE + for cdir in $(echo $SYSFS/class/sound/card*); do + echo "!!Card: $cdir" >> $FILE + driver=$(readlink -f "$cdir/device/driver") + echo "Driver: $driver" >> $FILE + echo "Tree:" >> $FILE + tree --noreport $cdir -L 2 | sed -e 's/^/\t/g' >> $FILE + echo "" >> $FILE + done + echo "" >> $FILE fi if [ -s "$TEMPDIR/alsa-hda-intel.tmp" ]; then @@ -796,7 +823,7 @@ if [ -n "$1" ]; then echo " --update (check server for script updates)" echo " --upload (upload contents to remote server)" echo " --no-upload (do not upload contents to remote server)" - echo " --pastebin (use http://pastebin.ca) as remote server" + echo " --pastebin (use https://pastebin.ca) as remote server" echo " instead www.alsa-project.org" echo " --stdout (print alsa information to standard output" echo " instead of a file)" @@ -825,28 +852,28 @@ if ! wget --help 2>/dev/null | grep -q post-file; then : elif [ -n "$DIALOG" ]; then if [ -z "$PASTEBIN" ]; then - dialog --backtitle "$BGTITLE" --msgbox "Could not automatically upload output to http://www.alsa-project.org.\nPossible reasons are:\n\n 1. Couldn't find 'wget' in your PATH\n 2. Your version of wget is less than 1.8.2\n\nPlease manually upload $NFILE to http://www.alsa-project.org/cardinfo-db/ and submit your post." 25 100 + dialog --backtitle "$BGTITLE" --msgbox "Could not automatically upload output to https://www.alsa-project.org.\nPossible reasons are:\n\n 1. Couldn't find 'wget' in your PATH\n 2. Your version of wget is less than 1.8.2\n\nPlease manually upload $NFILE to https://www.alsa-project.org/cardinfo-db/ and submit your post." 25 100 else - dialog --backtitle "$BGTITLE" --msgbox "Could not automatically upload output to http://www.pastebin.ca.\nPossible reasons are:\n\n 1. Couldn't find 'wget' in your PATH\n 2. Your version of wget is less than 1.8.2\n\nPlease manually upload $NFILE to http://www.pastebin.ca/upload.php and submit your post." 25 100 + dialog --backtitle "$BGTITLE" --msgbox "Could not automatically upload output to https://www.pastebin.ca.\nPossible reasons are:\n\n 1. Couldn't find 'wget' in your PATH\n 2. Your version of wget is less than 1.8.2\n\nPlease manually upload $NFILE to https://www.pastebin.ca/upload.php and submit your post." 25 100 fi else if [ -z "$PASTEBIN" ]; then echo "" - echo "Could not automatically upload output to http://www.alsa-project.org" + echo "Could not automatically upload output to https://www.alsa-project.org" echo "Possible reasons are:" echo " 1. Couldn't find 'wget' in your PATH" echo " 2. Your version of wget is less than 1.8.2" echo "" - echo "Please manually upload $NFILE to http://www.alsa-project.org/cardinfo-db/ and submit your post." + echo "Please manually upload $NFILE to https://www.alsa-project.org/cardinfo-db/ and submit your post." echo "" else echo "" - echo "Could not automatically upload output to http://www.pastebin.ca" + echo "Could not automatically upload output to https://www.pastebin.ca" echo "Possible reasons are:" echo " 1. Couldn't find 'wget' in your PATH" echo " 2. Your version of wget is less than 1.8.2" echo "" - echo "Please manually upload $NFILE to http://www.pastebin.ca/upload.php and submit your post." + echo "Please manually upload $NFILE to https://www.pastebin.ca/upload.php and submit your post." echo "" fi fi @@ -900,9 +927,9 @@ else fi if [[ -z $PASTEBIN ]]; then - wget -O - --tries=5 --timeout=60 --post-file=$FILE "http://www.alsa-project.org/cardinfo-db/" &>$TEMPDIR/wget.tmp + wget -O - --tries=5 --timeout=60 --post-file=$FILE "https://www.alsa-project.org/cardinfo-db/" &>$TEMPDIR/wget.tmp else - wget -O - --tries=5 --timeout=60 --post-file=$FILE "http://pastebin.ca/quiet-paste.php?api=$PASTEBINKEY&encrypt=t&encryptpw=blahblah" &>$TEMPDIR/wget.tmp + wget -O - --tries=5 --timeout=60 --post-file=$FILE "https://pastebin.ca/quiet-paste.php?api=$PASTEBINKEY&encrypt=t&encryptpw=blahblah" &>$TEMPDIR/wget.tmp fi if [ $? -ne 0 ]; then @@ -945,7 +972,7 @@ fi # dialog if [ -z "$PASTEBIN" ]; then FINAL_URL=$(grep "SUCCESS:" $TEMPDIR/wget.tmp | cut -d ' ' -f 2) else - FINAL_URL=$(grep "SUCCESS:" $TEMPDIR/wget.tmp | sed -n 's/.*\:\([0-9]\+\).*/http:\/\/pastebin.ca\/\1/p') + FINAL_URL=$(grep "SUCCESS:" $TEMPDIR/wget.tmp | sed -n 's/.*\:\([0-9]\+\).*/https:\/\/pastebin.ca\/\1/p') fi # See if tput is available, and use it if it is. diff --git a/alsactl/Makefile.am b/alsactl/Makefile.am index c1031ac81..deff2cd1e 100644 --- a/alsactl/Makefile.am +++ b/alsactl/Makefile.am @@ -10,7 +10,7 @@ EXTRA_DIST=alsactl.1 alsactl_init.xml AM_CFLAGS = -D_GNU_SOURCE alsactl_SOURCES=alsactl.c state.c lock.c utils.c init_parse.c init_ucm.c \ - daemon.c monitor.c + daemon.c monitor.c clean.c alsactl_CFLAGS=$(AM_CFLAGS) -D__USE_GNU \ -DSYS_ASOUNDRC=\"$(ASOUND_STATE_DIR)/asound.state\" \ diff --git a/alsactl/alsactl.1 b/alsactl/alsactl.1 index 615491a74..8296663a7 100644 --- a/alsactl/alsactl.1 +++ b/alsactl/alsactl.1 @@ -8,6 +8,8 @@ alsactl \- advanced controls for ALSA soundcard driver \fBalsactl\fP \fImonitor\fP +\fBalsactl\fP [\fIclean\fP] [[control identifiers]] + .SH DESCRIPTION \fBalsactl\fP is used to control advanced settings for the ALSA soundcard drivers. It supports multiple soundcards. If your card has @@ -16,31 +18,67 @@ you have come to the right place. .SH COMMANDS -\fIstore\fP saves the current driver state for the selected soundcard +.SS Introduction + +The \fI\fP argument is optional. If no soundcards are specified, +setup for all cards will be saved, loaded or monitored. + +.SS store + +This command saves the current driver state for the selected soundcard to the configuration file. -\fIrestore\fP loads driver state for the selected soundcard from the +.SS restore + +This command loads driver state for the selected soundcard from the configuration file. If restoring fails (eventually partly), the init action is called. -\fInrestore\fP is like \fIrestore\fP, but it notifies also the daemon +.SS nrestore + +This command is like \fIrestore\fP, but it notifies also the daemon to do new rescan for available soundcards. -\fIinit\fP tries to initialize all devices to a default state. If device +.SS init + +This command tries to initialize all devices to a default state. If device is not known, error code 99 is returned. -\fIdaemon\fP manages to save periodically the sound state. +.SS daemon + +This command manages to save periodically the sound state. + +.SS rdaemon + +This command is like \fIdaemon\fP but restore the sound state at first. -\fIrdaemon\fP like \fIdaemon\fP but restore the sound state at first. +.SS kill -\fIkill\fP notifies the daemon to do the specified operation (quit, +This command notifies the daemon to do the specified operation (quit, rescan, save_and_quit). -\fImonitor\fP is for monitoring the events received from the given +.SS monitor + +This command is for monitoring the events received from the given control device. -If no soundcards are specified, setup for all cards will be saved, -loaded or monitored. +.SS clean [filter] + +This command cleans the controls created by applications. + +The optional element identifiers are accepted as a filter. One extra +argument is parsed as an element identifiers. + +\fIExample:\fP alsactl clean 0 "name='PCM'" "name='Mic Phantom'" + +.SS dump-state + +This command dumps the current state (all cards) to stdout. + +.SS dump-cfg + +This command dumps the current configuration (all cards) to stdout. +Note that the configuration hooks are evaluated. .SH OPTIONS @@ -60,6 +98,10 @@ Print alsactl version number. \fI\-f, \-\-file\fP Select the configuration file to use. The default is /var/lib/alsa/asound.state. +.TP +\fI\-a, \-\-config-dir\fP +Select the boot / hotplug ALSA configuration directory to use. The default is /var/lib/alsa. + .TP \fI\-l, \-\-lock\fP Use the file locking to serialize the concurrent access to the state file (this @@ -81,7 +123,7 @@ as much as possible. This option is set as default now. .TP \fI\-g, \-\-ignore\fP -Used with store and restore commands. Do not show 'No soundcards found' +Used with store, restore and init commands. Do not show 'No soundcards found' and do not set an error exit code when soundcards are not installed. .TP diff --git a/alsactl/alsactl.c b/alsactl/alsactl.c index 20ebac156..a0112844c 100644 --- a/alsactl/alsactl.c +++ b/alsactl/alsactl.c @@ -29,11 +29,13 @@ #include #include #include -#include #include "alsactl.h" +#ifndef SYS_ASOUND_DIR +#define SYS_ASOUND_DIR "/var/lib/alsa" +#endif #ifndef SYS_ASOUNDRC -#define SYS_ASOUNDRC "/var/lib/alsa/asound.state" +#define SYS_ASOUNDRC SYS_ASOUND_DIR "/asound.state" #endif #ifndef SYS_PIDFILE #define SYS_PIDFILE "/var/run/alsactl.pid" @@ -74,6 +76,7 @@ static struct arg args[] = { { 'v', "version", "print version of this program" }, { HEADER, NULL, "Available state options:" }, { FILEARG | 'f', "file", "configuration file (default " SYS_ASOUNDRC ")" }, +{ FILEARG | 'a', "config-dir", "boot / hotplug configuration directory (default " SYS_ASOUND_DIR ")" }, { 'l', "lock", "use file locking to serialize concurrent access" }, { 'L', "no-lock", "do not use file locking to serialize concurrent access" }, { FILEARG | 'O', "lock-state-file", "state lock file path (default " SYS_LOCKFILE ")" }, @@ -111,6 +114,9 @@ static struct arg args[] = { { CARDCMD, "rdaemon", "like daemon but do the state restore at first" }, { KILLCMD, "kill", "notify daemon to quit, rescan or save_and_quit" }, { CARDCMD, "monitor", "monitor control events" }, +{ CARDCMD, "clean", "clean application controls" }, +{ EMPCMD, "dump-state", "dump the state (for all cards)" }, +{ EMPCMD, "dump-cfg", "dump the configuration (expanded, for all cards)" }, { 0, NULL, NULL } }; @@ -139,7 +145,7 @@ static void help(void) strcat(buf, ""); else if (sarg & KILLCMD) strcat(buf, ""); - printf(" %-8s %-6s %s\n", larg ? larg : "", + printf(" %-10s %-6s %s\n", larg ? larg : "", buf, a->comment); continue; } @@ -154,6 +160,49 @@ static void help(void) } } +static int dump_config_tree(snd_config_t *top) +{ + snd_output_t *out; + int err; + + err = snd_output_stdio_attach(&out, stdout, 0); + if (err < 0) + return err; + err = snd_config_save(top, out); + snd_output_close(out); + return 0; +} + +static int dump_state(const char *file) +{ + snd_config_t *top; + int err; + + err = load_configuration(file, &top, NULL); + if (err < 0) + return err; + err = dump_config_tree(top); + snd_config_delete(top); + return err; +} + +static int dump_configuration(void) +{ + snd_config_t *top, *cfg2; + int err; + + err = snd_config_update_ref(&top); + if (err < 0) + return err; + /* expand cards.* tree */ + err = snd_config_search_definition(top, "cards", "_dummy_", &cfg2); + if (err >= 0) + snd_config_delete(cfg2); + err = dump_config_tree(top); + snd_config_unref(top); + return err; +} + #define NO_NICE (-100000) static void do_nice(int use_nice, int sched_idle) @@ -182,11 +231,13 @@ int main(int argc, char *argv[]) "/dev/snd/hwC", NULL }; + char *cfgdir = SYS_ASOUND_DIR; char *cfgfile = SYS_ASOUNDRC; char *initfile = DATADIR "/init/00main"; char *pidfile = SYS_PIDFILE; char *cardname, ncardname[16]; char *cmd; + char *const *extra_args; const char *const *tmp; int removestate = 0; int init_fallback = 1; /* new default behavior */ @@ -240,6 +291,9 @@ int main(int argc, char *argv[]) case 'f': cfgfile = optarg; break; + case 'a': + cfgdir = optarg; + break; case 'l': do_lock = 1; break; @@ -346,6 +400,8 @@ int main(int argc, char *argv[]) } } + extra_args = argc - optind > 2 ? argv + optind + 2 : NULL; + /* the global system file should be always locked */ if (strcmp(cfgfile, SYS_ASOUNDRC) == 0 && do_lock >= 0) do_lock = 1; @@ -353,7 +409,11 @@ int main(int argc, char *argv[]) /* when running in background, use syslog for reports */ if (background) { use_syslog = 1; - daemon(0, 0); + if (daemon(0, 0)) { + syslog(LOG_INFO, "alsactl " SND_UTIL_VERSION_STR " daemon cannot be started: %s", strerror(errno)); + res = EXIT_FAILURE; + goto out; + } } cmd = argv[optind]; @@ -368,7 +428,7 @@ int main(int argc, char *argv[]) snd_lib_error_set_handler(error_handler); if (!strcmp(cmd, "init")) { - res = init(initfile, initflags, cardname); + res = init(cfgdir, initfile, initflags | FLAG_UCM_FBOOT | FLAG_UCM_BOOT, cardname); snd_config_update_free_global(); } else if (!strcmp(cmd, "store")) { res = save_state(cfgfile, cardname); @@ -377,7 +437,7 @@ int main(int argc, char *argv[]) !strcmp(cmd, "nrestore")) { if (removestate) remove(statefile); - res = load_state(cfgfile, initfile, initflags, cardname, init_fallback); + res = load_state(cfgdir, cfgfile, initfile, initflags, cardname, init_fallback); if (!strcmp(cmd, "rdaemon")) { do_nice(use_nice, sched_idle); res = state_daemon(cfgfile, cardname, period, pidfile); @@ -391,6 +451,12 @@ int main(int argc, char *argv[]) res = state_daemon_kill(pidfile, cardname); } else if (!strcmp(cmd, "monitor")) { res = monitor(cardname); + } else if (!strcmp(cmd, "clean")) { + res = clean(cardname, extra_args); + } else if (!strcmp(cmd, "dump-state")) { + res = dump_state(cfgfile); + } else if (!strcmp(cmd, "dump-cfg")) { + res = dump_configuration(); } else { fprintf(stderr, "alsactl: Unknown command '%s'...\n", cmd); res = -ENODEV; diff --git a/alsactl/alsactl.h b/alsactl/alsactl.h index c47869570..bbdf6c88b 100644 --- a/alsactl/alsactl.h +++ b/alsactl/alsactl.h @@ -1,3 +1,6 @@ +#include +#include + extern int debugflag; extern int force_restore; extern int ignore_nocards; @@ -7,6 +10,13 @@ extern char *command; extern char *statefile; extern char *lockfile; +struct snd_card_iterator { + int card; + char name[16]; + bool single; + bool first; +}; + void info_(const char *fcn, long line, const char *fmt, ...); void error_(const char *fcn, long line, const char *fmt, ...); void cerror_(const char *fcn, long line, int cond, const char *fmt, ...); @@ -26,20 +36,31 @@ void error_handler(const char *file, int line, const char *function, int err, co #endif #define FLAG_UCM_DISABLED (1<<0) -#define FLAG_UCM_DEFAULTS (1<<1) +#define FLAG_UCM_FBOOT (1<<1) +#define FLAG_UCM_BOOT (1<<2) +#define FLAG_UCM_DEFAULTS (1<<3) + +void snd_card_iterator_init(struct snd_card_iterator *iter, int cardno); +int snd_card_iterator_sinit(struct snd_card_iterator *iter, const char *cardname); +const char *snd_card_iterator_next(struct snd_card_iterator *iter); +int snd_card_iterator_error(struct snd_card_iterator *iter); -int init(const char *file, int flags, const char *cardname); +int load_configuration(const char *file, snd_config_t **top, int *open_failed); +int init(const char *cfgdir, const char *file, int flags, const char *cardname); int init_ucm(int flags, int cardno); int state_lock(const char *file, int timeout); int state_unlock(int fd, const char *file); int save_state(const char *file, const char *cardname); -int load_state(const char *file, const char *initfile, int initflags, +int load_state(const char *cfgdir, const char *file, + const char *initfile, int initflags, const char *cardname, int do_init); int power(const char *argv[], int argc); int monitor(const char *name); int state_daemon(const char *file, const char *cardname, int period, const char *pidfile); int state_daemon_kill(const char *pidfile, const char *cmd); +int clean(const char *cardname, char *const *extra_args); +int snd_card_clean_cfgdir(const char *cfgdir, int cardno); /* utils */ diff --git a/alsactl/clean.c b/alsactl/clean.c new file mode 100644 index 000000000..1eb82c0f5 --- /dev/null +++ b/alsactl/clean.c @@ -0,0 +1,196 @@ +/* + * Advanced Linux Sound Architecture Control Program + * Copyright (c) by Jaroslav Kysela + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "aconfig.h" +#include "version.h" +#include +#include +#include +#include +#include +#include "alsactl.h" + +static int clean_one_control(snd_ctl_t *handle, snd_ctl_elem_id_t *elem_id, + snd_ctl_elem_id_t **filter) +{ + snd_ctl_elem_info_t *info; + char *s; + int err; + + snd_ctl_elem_info_alloca(&info); + snd_ctl_elem_info_set_id(info, elem_id); + err = snd_ctl_elem_info(handle, info); + if (err < 0) { + s = snd_ctl_ascii_elem_id_get(elem_id); + error("Cannot read control info '%s': %s", s, snd_strerror(err)); + free(s); + return err; + } + + if (!snd_ctl_elem_info_is_user(info)) + return 0; + + s = snd_ctl_ascii_elem_id_get(elem_id); + dbg("Application control \"%s\" found.", s); + if (filter) { + for (; *filter; filter++) { + if (snd_ctl_elem_id_compare_set(elem_id, *filter) == 0) + break; + } + if (*filter == NULL) { + free(s); + return 0; + } + } + + err = snd_ctl_elem_remove(handle, elem_id); + if (err < 0) { + error("Cannot remove control '%s': %s", s, snd_strerror(err)); + free(s); + return err; + } + dbg("Application control \"%s\" removed.", s); + free(s); + return 0; +} + +static void filter_controls_free(snd_ctl_elem_id_t **_filter) +{ + snd_ctl_elem_id_t **filter; + + for (filter = _filter; filter; filter++) + free(*filter); + free(_filter); +} + +static int filter_controls_parse(char *const *controls, snd_ctl_elem_id_t ***_filter) +{ + snd_ctl_elem_id_t **filter = NULL; + char *const *c; + char *s; + unsigned int count, idx; + int err; + + if (!controls) + goto fin; + for (count = 0, c = controls; *c; c++, count++); + if (count == 0) + goto fin; + filter = calloc(count + 1, sizeof(snd_ctl_elem_id_t *)); + if (filter == NULL) { +nomem: + error("No enough memory..."); + return -ENOMEM; + } + filter[count] = NULL; + for (idx = 0; idx < count; idx++) { + err = snd_ctl_elem_id_malloc(&filter[idx]); + if (err < 0) { + filter_controls_free(filter); + goto nomem; + } + err = snd_ctl_ascii_elem_id_parse(filter[idx], controls[idx]); + if (err < 0) { + error("Cannot parse id '%s': %s", controls[idx], snd_strerror(err)); + filter_controls_free(filter); + return err; + } + s = snd_ctl_ascii_elem_id_get(filter[idx]); + dbg("Add to filter: \"%s\"", s); + free(s); + } +fin: + *_filter = filter; + return 0; +} + +static int clean_controls(int cardno, char *const *controls) +{ + snd_ctl_t *handle; + snd_ctl_elem_list_t *list; + snd_ctl_elem_id_t *elem_id; + snd_ctl_elem_id_t **filter; + char name[32]; + unsigned int idx, count; + int err; + + snd_ctl_elem_id_alloca(&elem_id); + snd_ctl_elem_list_alloca(&list); + + err = filter_controls_parse(controls, &filter); + if (err < 0) + return err; + + sprintf(name, "hw:%d", cardno); + err = snd_ctl_open(&handle, name, 0); + if (err < 0) { + error("snd_ctl_open error: %s", snd_strerror(err)); + filter_controls_free(filter); + return err; + } + dbg("Control device '%s' opened.", name); + err = snd_ctl_elem_list(handle, list); + if (err < 0) { + error("Cannot determine controls: %s", snd_strerror(err)); + goto fin_err; + } + count = snd_ctl_elem_list_get_count(list); + if (count == 0) + goto fin_ok; + snd_ctl_elem_list_set_offset(list, 0); + if ((err = snd_ctl_elem_list_alloc_space(list, count)) < 0) { + error("No enough memory..."); + goto fin_err; + } + if ((err = snd_ctl_elem_list(handle, list)) < 0) { + error("Cannot determine controls (2): %s", snd_strerror(err)); + goto fin_err; + } + for (idx = 0; idx < count; idx++) { + snd_ctl_elem_list_get_id(list, idx, elem_id); + err = clean_one_control(handle, elem_id, filter); + if (err < 0) + goto fin_err; + } +fin_ok: + filter_controls_free(filter); + snd_ctl_close(handle); + return 0; +fin_err: + filter_controls_free(filter); + snd_ctl_close(handle); + return err; +} + +int clean(const char *cardname, char *const *extra_args) +{ + struct snd_card_iterator iter; + int err; + + err = snd_card_iterator_sinit(&iter, cardname); + if (err < 0) + return err; + while (snd_card_iterator_next(&iter)) { + if ((err = clean_controls(iter.card, extra_args))) + return err; + } + return snd_card_iterator_error(&iter); +} diff --git a/alsactl/daemon.c b/alsactl/daemon.c index ee03991ef..5109015d5 100644 --- a/alsactl/daemon.c +++ b/alsactl/daemon.c @@ -29,7 +29,6 @@ #include #include #include -#include #include "alsactl.h" struct id_list { diff --git a/alsactl/init_parse.c b/alsactl/init_parse.c index 71348da37..9d0f473c1 100644 --- a/alsactl/init_parse.c +++ b/alsactl/init_parse.c @@ -37,7 +37,6 @@ #include #include #include -#include #include "aconfig.h" #include "alsactl.h" #include "list.h" @@ -1744,68 +1743,42 @@ static int parse(struct space *space, const char *filename) return err ? err : -abs(space->exit_code); } -int init(const char *filename, int flags, const char *cardname) +int init(const char *cfgdir, const char *filename, int flags, const char *cardname) { struct space *space; - int err = 0, lasterr = 0, card, first; + struct snd_card_iterator iter; + int err = 0, lasterr = 0; sysfs_init(); - if (!cardname) { - first = 1; - card = -1; - while (1) { - if (snd_card_next(&card) < 0) - break; - if (card < 0) { - if (first) { - error("No soundcards found..."); - return -ENODEV; - } - break; - } - first = 0; - if (!(flags & FLAG_UCM_DISABLED)) { - err = init_ucm(flags, card); - if (err == 0) - continue; - } - err = init_space(&space, card); - if (err == 0) { - space->rootdir = new_root_dir(filename); - if (space->rootdir != NULL) - err = parse(space, filename); - if (err <= -99) { /* non-fatal errors */ - if (lasterr == 0) - lasterr = err; - err = 0; - } - free_space(space); - } - if (err < 0) - break; - } - err = lasterr; - } else { - card = snd_card_get_index(cardname); - if (card < 0) { - error("Cannot find soundcard '%s'...", cardname); - goto error; - } - if (!(flags & FLAG_UCM_DISABLED)) { - err = init_ucm(flags, card); - if (err == 0) - return 0; + err = snd_card_iterator_sinit(&iter, cardname); + while (snd_card_iterator_next(&iter)) { + err = snd_card_clean_cfgdir(cfgdir, iter.card); + if (err < 0) { + if (lasterr == 0) + lasterr = err; + continue; } - memset(&space, 0, sizeof(space)); - err = init_space(&space, card); - if (err == 0) { - space->rootdir = new_root_dir(filename); - if (space->rootdir != NULL) - err = parse(space, filename); - free_space(space); + err = init_ucm(flags, iter.card); + if (err == 0) + continue; + err = init_space(&space, iter.card); + if (err != 0) + continue; + space->rootdir = new_root_dir(filename); + if (space->rootdir != NULL) { + err = parse(space, filename); + if (!cardname && err <= -99) { /* non-fatal errors */ + if (lasterr == 0) + lasterr = err; + err = 0; + } } + free_space(space); + if (err < 0) + goto out; } - error: + err = lasterr ? lasterr : snd_card_iterator_error(&iter); +out: sysfs_cleanup(); return err; } diff --git a/alsactl/init_ucm.c b/alsactl/init_ucm.c index 6468c9d59..b3266010f 100644 --- a/alsactl/init_ucm.c +++ b/alsactl/init_ucm.c @@ -28,7 +28,8 @@ #include /* - * Keep it as simple as possible. Execute commands from the SectionOnce only. + * Keep it as simple as possible. Execute commands from the + * FixedBootSequence and BootSequence only. */ int init_ucm(int flags, int cardno) { @@ -36,17 +37,27 @@ int init_ucm(int flags, int cardno) char id[32]; int err; + if (flags & FLAG_UCM_DISABLED) + return -ENXIO; + snprintf(id, sizeof(id), "hw:%d", cardno); err = snd_use_case_mgr_open(&uc_mgr, id); if (err < 0) return err; - err = snd_use_case_set(uc_mgr, "_boot", NULL); - if (err < 0) - goto _error; - if ((flags & FLAG_UCM_DEFAULTS) != 0) { - err = snd_use_case_set(uc_mgr, "_defaults", NULL); + if (flags & FLAG_UCM_FBOOT) { + err = snd_use_case_set(uc_mgr, "_fboot", NULL); + if (err == -ENOENT && (flags & FLAG_UCM_BOOT) != 0) { + /* nothing */ + } else if (err < 0) { + goto _error; + } + } + if (flags & FLAG_UCM_BOOT) { + err = snd_use_case_set(uc_mgr, "_boot", NULL); if (err < 0) goto _error; + if ((flags & FLAG_UCM_DEFAULTS) != 0) + err = snd_use_case_set(uc_mgr, "_defaults", NULL); } _error: snd_use_case_mgr_close(uc_mgr); @@ -57,7 +68,7 @@ int init_ucm(int flags, int cardno) int init_ucm(int flags, int cardno) { - return 0; + return -ENXIO; } #endif diff --git a/alsactl/monitor.c b/alsactl/monitor.c index fa6cd85d2..4c02557b1 100644 --- a/alsactl/monitor.c +++ b/alsactl/monitor.c @@ -28,11 +28,12 @@ #include #include #include -#include #include #include "list.h" +#include "alsactl.h" + struct src_entry { snd_ctl_t *handle; char *name; @@ -40,29 +41,6 @@ struct src_entry { struct list_head list; }; -struct snd_card_iterator { - int card; - char name[16]; -}; - -void snd_card_iterator_init(struct snd_card_iterator *iter) -{ - iter->card = -1; - memset(iter->name, 0, sizeof(iter->name)); -} - -static const char *snd_card_iterator_next(struct snd_card_iterator *iter) -{ - if (snd_card_next(&iter->card) < 0) - return NULL; - if (iter->card < 0) - return NULL; - - snprintf(iter->name, sizeof(iter->name), "hw:%d", iter->card); - - return (const char *)iter->name; -} - static void remove_source_entry(struct src_entry *entry) { list_del(&entry->list); @@ -159,7 +137,7 @@ static int prepare_source_entry(struct list_head *srcs, const char *name) struct snd_card_iterator iter; const char *cardname; - snd_card_iterator_init(&iter); + snd_card_iterator_init(&iter, -1); while ((cardname = snd_card_iterator_next(&iter))) { if (seek_entry_by_name(srcs, cardname)) continue; diff --git a/alsactl/state.c b/alsactl/state.c index ea1d3bcaa..44fda3fe7 100644 --- a/alsactl/state.c +++ b/alsactl/state.c @@ -27,7 +27,6 @@ #include #include #include -#include #include "alsactl.h" @@ -1545,6 +1544,7 @@ int save_state(const char *file, const char *cardname) int stdio; char *nfile = NULL; int lock_fd = -EINVAL; + struct snd_card_iterator iter; err = snd_config_top(&config); if (err < 0) { @@ -1578,45 +1578,18 @@ int save_state(const char *file, const char *cardname) #endif } - if (!cardname) { - int card, first = 1; - - card = -1; - /* find each installed soundcards */ - while (1) { - if (snd_card_next(&card) < 0) - break; - if (card < 0) { - if (first) { - if (ignore_nocards) { - err = 0; - goto out; - } else { - error("No soundcards found..."); - err = -ENODEV; - goto out; - } - } - break; - } - first = 0; - if ((err = get_controls(card, config))) - goto out; - } - } else { - int cardno; - - cardno = snd_card_get_index(cardname); - if (cardno < 0) { - error("Cannot find soundcard '%s'...", cardname); - err = cardno; - goto out; - } - if ((err = get_controls(cardno, config))) { + err = snd_card_iterator_sinit(&iter, cardname); + if (err < 0) + goto out; + while (snd_card_iterator_next(&iter)) { + if ((err = get_controls(iter.card, config))) goto out; - } } - + if (iter.first) { + err = snd_card_iterator_error(&iter); + goto out; + } + if (stdio) { err = snd_output_stdio_attach(&out, stdout, 0); } else { @@ -1645,140 +1618,63 @@ int save_state(const char *file, const char *cardname) return err; } -int load_state(const char *file, const char *initfile, int initflags, +int load_state(const char *cfgdir, const char *file, + const char *initfile, int initflags, const char *cardname, int do_init) { - int err, finalerr = 0; + int err, finalerr = 0, open_failed; + struct snd_card_iterator iter; snd_config_t *config; - snd_input_t *in; - int stdio, lock_fd = -EINVAL; + const char *cardname1; - err = snd_config_top(&config); - if (err < 0) { - error("snd_config_top error: %s", snd_strerror(err)); + err = load_configuration(file, &config, &open_failed); + if (err < 0 && !open_failed) return err; - } - stdio = !strcmp(file, "-"); - if (stdio) { - err = snd_input_stdio_attach(&in, stdin, 0); - } else { - lock_fd = state_lock(file, 10); - err = lock_fd >= 0 ? snd_input_stdio_open(&in, file, "r") : lock_fd; - } - if (err >= 0) { - err = snd_config_load(config, in); - snd_input_close(in); - if (lock_fd >= 0) - state_unlock(lock_fd, file); - if (err < 0) { - error("snd_config_load error: %s", snd_strerror(err)); - goto out; - } - } else { - int card, first = 1; - char cardname1[16]; - if (lock_fd >= 0) - state_unlock(lock_fd, file); + if (open_failed) { error("Cannot open %s for reading: %s", file, snd_strerror(err)); finalerr = err; - if (cardname) { - card = snd_card_get_index(cardname); - if (card < 0) { - error("Cannot find soundcard '%s'...", cardname); - err = -ENODEV; - goto out; - } - goto single; - } else { - card = -1; - } - /* find each installed soundcards */ - while (!cardname) { - if (snd_card_next(&card) < 0) - break; - if (card < 0) - break; -single: - first = 0; + + err = snd_card_iterator_sinit(&iter, cardname); + if (err < 0) + return err; + while ((cardname1 = snd_card_iterator_next(&iter)) != NULL) { if (!do_init) break; - sprintf(cardname1, "%i", card); - err = init(initfile, initflags, cardname1); + err = init(cfgdir, initfile, initflags | FLAG_UCM_FBOOT | FLAG_UCM_BOOT, cardname1); if (err < 0) { finalerr = err; - initfailed(card, "init", err); + initfailed(iter.card, "init", err); } - initfailed(card, "restore", -ENOENT); + initfailed(iter.card, "restore", -ENOENT); } - if (first) - finalerr = 0; /* no cards, no error code */ err = finalerr; + if (iter.first) + err = 0; /* no cards, no error code */ goto out; } - if (!cardname) { - int card, first = 1; - char cardname1[16]; - - card = -1; - /* find each installed soundcards */ - while (1) { - if (snd_card_next(&card) < 0) - break; - if (card < 0) { - if (first) { - if (ignore_nocards) { - err = 0; - goto out; - } else { - error("No soundcards found..."); - err = -ENODEV; - goto out; - } - } - break; - } - first = 0; - /* do a check if controls matches state file */ - if (do_init && set_controls(card, config, 0)) { - sprintf(cardname1, "%i", card); - err = init(initfile, initflags, cardname1); - if (err < 0) { - initfailed(card, "init", err); - finalerr = err; - } - } - if ((err = set_controls(card, config, 1))) { - if (!force_restore) - finalerr = err; - initfailed(card, "restore", err); - } - } - } else { - int cardno; - - cardno = snd_card_get_index(cardname); - if (cardno < 0) { - error("Cannot find soundcard '%s'...", cardname); - err = -ENODEV; - goto out; - } + err = snd_card_iterator_sinit(&iter, cardname); + if (err < 0) + goto out; + while ((cardname1 = snd_card_iterator_next(&iter)) != NULL) { + /* error is ignored */ + init_ucm(initflags | FLAG_UCM_FBOOT, iter.card); /* do a check if controls matches state file */ - if (do_init && set_controls(cardno, config, 0)) { - err = init(initfile, initflags, cardname); + if (do_init && set_controls(iter.card, config, 0)) { + err = init(cfgdir, initfile, initflags | FLAG_UCM_BOOT, cardname1); if (err < 0) { - initfailed(cardno, "init", err); + initfailed(iter.card, "init", err); finalerr = err; } } - if ((err = set_controls(cardno, config, 1))) { - initfailed(cardno, "restore", err); + if ((err = set_controls(iter.card, config, 1))) { if (!force_restore) - goto out; + finalerr = err; + initfailed(iter.card, "restore", err); } } - err = finalerr; + err = finalerr ? finalerr : snd_card_iterator_error(&iter); out: snd_config_delete(config); snd_config_update_free_global(); diff --git a/alsactl/utils.c b/alsactl/utils.c index a83aa0ab1..c79fd951b 100644 --- a/alsactl/utils.c +++ b/alsactl/utils.c @@ -30,8 +30,6 @@ #include #include #include - -#include #include "alsactl.h" int file_map(const char *filename, char **buf, size_t *bufsize) @@ -92,12 +90,12 @@ void initfailed(int cardnumber, const char *reason, int exitcode) return; sprintf(sexitcode, "%i", exitcode); fp = open(statefile, O_WRONLY|O_CREAT|O_APPEND, 0644); - write(fp, str, strlen(str)); - write(fp, ":", 1); - write(fp, reason, strlen(reason)); - write(fp, ":", 1); - write(fp, sexitcode, strlen(sexitcode)); - write(fp, "\n", 1); + (void)write(fp, str, strlen(str)); + (void)write(fp, ":", 1); + (void)write(fp, reason, strlen(reason)); + (void)write(fp, ":", 1); + (void)write(fp, sexitcode, strlen(sexitcode)); + (void)write(fp, "\n", 1); close(fp); free(str); } @@ -193,3 +191,139 @@ void error_handler(const char *file, int line, const char *function, int err, co fprintf(stderr, "alsa-lib %s:%i:(%s) %s%s%s\n", file, line, function, buf, err ? ": " : "", err ? snd_strerror(err) : ""); } + +int load_configuration(const char *file, snd_config_t **top, int *open_failed) +{ + snd_config_t *config; + snd_input_t *in; + int err, stdio_flag, lock_fd = -EINVAL; + + *top = NULL; + if (open_failed) + *open_failed = 0; + err = snd_config_top(&config); + if (err < 0) { + error("snd_config_top error: %s", snd_strerror(err)); + return err; + } + stdio_flag = !strcmp(file, "-"); + if (stdio_flag) { + err = snd_input_stdio_attach(&in, stdin, 0); + } else { + lock_fd = state_lock(file, 10); + err = lock_fd >= 0 ? snd_input_stdio_open(&in, file, "r") : lock_fd; + } + if (err < 0) { + if (open_failed) + *open_failed = 1; + goto out; + } + err = snd_config_load(config, in); + snd_input_close(in); + if (lock_fd >= 0) + state_unlock(lock_fd, file); + if (err < 0) { + error("snd_config_load error: %s", snd_strerror(err)); +out: + snd_config_delete(config); + snd_config_update_free_global(); + return err; + } else { + *top = config; + return 0; + } +} + +void snd_card_iterator_init(struct snd_card_iterator *iter, int cardno) +{ + iter->card = cardno; + iter->single = cardno >= 0; + iter->first = true; + iter->name[0] = '\0'; +} + +int snd_card_iterator_sinit(struct snd_card_iterator *iter, const char *cardname) +{ + int cardno = -1; + + if (cardname) { + cardno = snd_card_get_index(cardname); + if (cardno < 0) { + error("Cannot find soundcard '%s'...", cardname); + return cardno; + } + } + snd_card_iterator_init(iter, cardno); + return 0; +} + +const char *snd_card_iterator_next(struct snd_card_iterator *iter) +{ + if (iter->single) { + if (iter->first) { + iter->first = false; + goto retval; + } + return NULL; + } + if (snd_card_next(&iter->card) < 0) { + if (!ignore_nocards && iter->first) + error("No soundcards found..."); + return NULL; + } + iter->first = false; + if (iter->card < 0) + return NULL; +retval: + snprintf(iter->name, sizeof(iter->name), "hw:%d", iter->card); + + return (const char *)iter->name; +} + +int snd_card_iterator_error(struct snd_card_iterator *iter) +{ + return iter->first ? (ignore_nocards ? 0 : -ENODEV) : 0; +} + +static int cleanup_filename_filter(const struct dirent *dirent) +{ + size_t flen; + + if (dirent == NULL) + return 0; + if (dirent->d_type == DT_DIR) + return 0; + + flen = strlen(dirent->d_name); + if (flen <= 5) + return 0; + + if (strncmp(&dirent->d_name[flen-5], ".conf", 5) == 0) + return 1; + + return 0; +} + +int snd_card_clean_cfgdir(const char *cfgdir, int cardno) +{ + char path[PATH_MAX]; + struct dirent **list; + int lasterr = 0, n, j; + + snprintf(path, sizeof(path), "%s/card%d.conf.d", cfgdir, cardno); + n = scandir(path, &list, cleanup_filename_filter, NULL); + if (n < 0) { + if (errno == ENOENT) + return 0; + return -errno; + } + for (j = 0; j < n; j++) { + snprintf(path, sizeof(path), "%s/card%d.conf.d/%s", cfgdir, cardno, list[j]->d_name); + if (remove(path)) { + error("Unable to remove file '%s'", path); + lasterr = -errno; + } + } + + return lasterr; +} diff --git a/alsaloop/alsaloop.1 b/alsaloop/alsaloop.1 index 33fa4d1b1..6bacfd557 100644 --- a/alsaloop/alsaloop.1 +++ b/alsaloop/alsaloop.1 @@ -79,7 +79,7 @@ Default format is S16_LE. Channel count specification. Default value is 2. .TP -\fI\-c \fP | \fI\-\-rate=\fP +\fI\-r \fP | \fI\-\-rate=\fP Rate specification. Default value is 48000 (Hz). diff --git a/alsaloop/pcmjob.c b/alsaloop/pcmjob.c index 01e01994c..4efdd63f2 100644 --- a/alsaloop/pcmjob.c +++ b/alsaloop/pcmjob.c @@ -556,13 +556,13 @@ static void buf_add_src(struct loopback *loop) if (capt->format == SND_PCM_FORMAT_S32) src_int_to_float_array((int *)(capt->buf + pos1 * capt->frame_size), - (void *)loop->src_data.data_in + + (float *)loop->src_data.data_in + pos * capt->channels, count1 * capt->channels); else src_short_to_float_array((short *)(capt->buf + pos1 * capt->frame_size), - (void *)loop->src_data.data_in + + (float *)loop->src_data.data_in + pos * capt->channels, count1 * capt->channels); count -= count1; diff --git a/alsamixer/card_select.c b/alsamixer/card_select.c index a58c0370c..aac7b452a 100644 --- a/alsamixer/card_select.c +++ b/alsamixer/card_select.c @@ -108,7 +108,7 @@ static int get_cards(void) int count, number, err; snd_ctl_t *ctl; snd_ctl_card_info_t *info; - char buf[16]; + char buf[32]; struct card *card, *prev_card; first_card.indexstr = "-"; @@ -125,7 +125,11 @@ static int get_cards(void) fatal_alsa_error(_("cannot enumerate sound cards"), err); if (number < 0) break; +#if defined(SND_LIB_VER) && SND_LIB_VER(1, 2, 5) <= SND_LIB_VERSION + sprintf(buf, "sysdefault:%d", number); +#else sprintf(buf, "hw:%d", number); +#endif err = snd_ctl_open(&ctl, buf, 0); if (err < 0) continue; diff --git a/alsamixer/cli.c b/alsamixer/cli.c index 23d34ad0d..f153f280c 100644 --- a/alsamixer/cli.c +++ b/alsamixer/cli.c @@ -73,7 +73,7 @@ static void parse_options(int argc, char *argv[]) }; int option; int card_index; - static char name_buf[16]; + static char name_buf[24]; while ((option = getopt_long(argc, argv, short_options, long_options, NULL)) != -1) { @@ -88,7 +88,11 @@ static void parse_options(int argc, char *argv[]) fprintf(stderr, _("invalid card index: %s\n"), optarg); goto fail; } +#if defined(SND_LIB_VER) && SND_LIB_VER(1, 2, 5) <= SND_LIB_VERSION + sprintf(name_buf, "sysdefault:%d", card_index); +#else sprintf(name_buf, "hw:%d", card_index); +#endif selem_regopt.device = name_buf; break; case 'D': diff --git a/alsamixer/colors.c b/alsamixer/colors.c index 1a8cb8fc5..c81ebcf08 100644 --- a/alsamixer/colors.c +++ b/alsamixer/colors.c @@ -50,11 +50,11 @@ void init_colors(int use_color) start_color(); use_default_colors(); - get_color_pair(COLOR_CYAN, COLOR_BLACK); // COLOR_PAIR(1) - get_color_pair(COLOR_YELLOW, COLOR_BLACK); + get_color_pair(COLOR_CYAN, -1); // COLOR_PAIR(1) + get_color_pair(COLOR_YELLOW, -1); get_color_pair(COLOR_WHITE, COLOR_GREEN); - get_color_pair(COLOR_RED, COLOR_BLACK); - get_color_pair(COLOR_WHITE, COLOR_BLACK); + get_color_pair(COLOR_RED, -1); + get_color_pair(COLOR_WHITE, -1); get_color_pair(COLOR_WHITE, COLOR_BLUE); get_color_pair(COLOR_RED, COLOR_BLUE); get_color_pair(COLOR_GREEN, COLOR_GREEN); diff --git a/alsamixer/configparser.c b/alsamixer/configparser.c index 93aa72afd..7647987f8 100644 --- a/alsamixer/configparser.c +++ b/alsamixer/configparser.c @@ -155,7 +155,7 @@ const char *mixer_words = static unsigned int parse_words(const char *name, const char* wordlist, unsigned int itemlen, unsigned int *number) { unsigned int words = 0; unsigned int word; - unsigned int i; + int i; char buf[16]; char *endptr; @@ -181,7 +181,7 @@ static unsigned int parse_words(const char *name, const char* wordlist, unsigned word = W_NUMBER; } else if ((i = strlist_index(wordlist, itemlen, buf)) >= 0) - word = 2U << i; + word = i <= 30 ? (2U << i) : 0; else return 0; diff --git a/alsamixer/mixer_display.c b/alsamixer/mixer_display.c index 882781de5..330fdd537 100644 --- a/alsamixer/mixer_display.c +++ b/alsamixer/mixer_display.c @@ -634,7 +634,7 @@ static void display_control(unsigned int control_index) static void display_scroll_indicators(void) { - int y0, y1, y; + int y0, y1; chtype left, right; if (screen_too_small) diff --git a/alsamixer/widget.c b/alsamixer/widget.c index 17f3aceef..5fedaba97 100644 --- a/alsamixer/widget.c +++ b/alsamixer/widget.c @@ -29,10 +29,6 @@ int screen_cols; static int cursor_visibility = -1; -static void widget_handle_key(int key) -{ -} - static void update_cursor_visibility(void) { const struct widget *active_widget; @@ -87,9 +83,6 @@ void widget_init(struct widget *widget, int lines_, int cols, int y, int x, set_panel_userptr(widget->panel, widget); } - //if (!widget->handle_key) - // widget->handle_key = widget_handle_key; - if (old_window) delwin(old_window); diff --git a/alsaucm/usecase.c b/alsaucm/usecase.c index d39a15951..1dba1caa2 100644 --- a/alsaucm/usecase.c +++ b/alsaucm/usecase.c @@ -58,7 +58,9 @@ enum uc_cmd { /* set/get */ OM_SET, OM_GET, + OM_GET_VAL, OM_GETI, + OM_GETI_VAL, /* misc */ OM_HELP, @@ -82,7 +84,9 @@ static struct cmd cmds[] = { { OM_LIST2, 1, 1, "list" }, { OM_SET, 2, 1, "set" }, { OM_GET, 1, 1, "get" }, + { OM_GET_VAL, 1, 1, "getval" }, { OM_GETI, 1, 1, "geti" }, + { OM_GETI_VAL, 1, 1, "getival" }, { OM_DUMP, 1, 1, "dump" }, { OM_HELP, 0, 0, "help" }, { OM_QUIT, 0, 0, "quit" }, @@ -328,6 +332,7 @@ static int do_one(struct context *context, struct cmd *cmd, char **argv) } break; case OM_GET: + case OM_GET_VAL: err = snd_use_case_get(context->uc_mgr, argv[0], &str); if (err < 0) { fprintf(stderr, @@ -336,10 +341,14 @@ static int do_one(struct context *context, struct cmd *cmd, char **argv) snd_strerror(err)); return err; } - printf(" %s=%s\n", argv[0], str); + if (cmd->code == OM_GET) + printf(" %s=%s\n", argv[0], str); + else + printf("%s\n", str); free((void *)str); break; case OM_GETI: + case OM_GETI_VAL: err = snd_use_case_geti(context->uc_mgr, argv[0], &lval); if (err < 0) { fprintf(stderr, @@ -348,7 +357,10 @@ static int do_one(struct context *context, struct cmd *cmd, char **argv) snd_strerror(err)); return lval; } - printf(" %s=%li\n", argv[0], lval); + if (cmd->code == OM_GETI) + printf(" %s=%li\n", argv[0], lval); + else + printf("%li\n", lval); break; case OM_QUIT: context->do_exit = 1; diff --git a/amidi/amidi.c b/amidi/amidi.c index cde4697c7..90e77501a 100644 --- a/amidi/amidi.c +++ b/amidi/amidi.c @@ -688,8 +688,11 @@ int main(int argc, char *argv[]) continue; read += length; - if (receive_file != -1) - write(receive_file, buf, length); + if (receive_file != -1) { + ssize_t wlength = write(receive_file, buf, length); + if (wlength != length) + error("write error: %s", wlength < 0 ? strerror(errno) : "short"); + } if (dump) { for (i = 0; i < length; ++i) print_byte(buf[i]); diff --git a/amixer/Makefile.am b/amixer/Makefile.am index b4526cabb..46ec7369f 100644 --- a/amixer/Makefile.am +++ b/amixer/Makefile.am @@ -5,7 +5,7 @@ LDADD = -lm # CFLAGS += -g -Wall bin_PROGRAMS = amixer -amixer_SOURCES = amixer.c ../alsamixer/volume_mapping.c -noinst_HEADERS = amixer.h +amixer_SOURCES = amixer.c volume_mapping.c +noinst_HEADERS = amixer.h volume_mapping.h man_MANS = amixer.1 EXTRA_DIST = amixer.1 diff --git a/amixer/amixer.1 b/amixer/amixer.1 index 76007d2e4..0bac82b13 100644 --- a/amixer/amixer.1 +++ b/amixer/amixer.1 @@ -44,11 +44,12 @@ value, respectively. The parameters \fIcap, nocap, mute, unmute, toggle\fP are used to change capture (recording) and muting for the group specified. -The optional modifiers can be put as extra parameters to specify -the stream direction or channels to apply. +The optional modifiers can be put as extra parameters before the value to +specify the stream direction or channels to apply. The modifiers \fIplayback\fP and \fIcapture\fP specify the stream, -and the modifiers \fIfront, rear, center, woofer\fP are used to specify -channels to be changed. +and the modifiers \fIfront, frontleft, frontright, frontcenter, center, +rear, rearright, rearleft, woofer\fP are used to specify channels to be +changed. A simple mixer control must be specified. Only one device can be controlled at a time. diff --git a/amixer/amixer.c b/amixer/amixer.c index b3b9b4809..f9cbb8984 100644 --- a/amixer/amixer.c +++ b/amixer/amixer.c @@ -1602,7 +1602,8 @@ static int events(int argc ATTRIBUTE_UNUSED, char *argv[] ATTRIBUTE_UNUSED) if (res >= 0) { printf("Poll ok: %i\n", res); res = snd_hctl_handle_events(handle); - assert(res > 0); + if (res < 0) + printf("ERR: %s (%d)\n", snd_strerror(res), res); } } snd_hctl_close(handle); @@ -1771,7 +1772,7 @@ static int exec_stdin(void) int main(int argc, char *argv[]) { - int morehelp, level = 0; + int badopt, retval, level = 0; int read_stdin = 0; static const struct option long_option[] = { @@ -1790,7 +1791,7 @@ int main(int argc, char *argv[]) {NULL, 0, NULL, 0}, }; - morehelp = 0; + badopt = 0; while (1) { int c; @@ -1805,10 +1806,14 @@ int main(int argc, char *argv[]) int i; i = snd_card_get_index(optarg); if (i >= 0 && i < 32) +#if defined(SND_LIB_VER) && SND_LIB_VER(1, 2, 5) <= SND_LIB_VERSION + sprintf(card, "sysdefault:%i", i); +#else sprintf(card, "hw:%i", i); +#endif else { - fprintf(stderr, "Invalid card number.\n"); - morehelp++; + fprintf(stderr, "Invalid card number '%s'.\n", optarg); + badopt++; } } break; @@ -1830,7 +1835,7 @@ int main(int argc, char *argv[]) break; case 'v': printf("amixer version " SND_UTIL_VERSION_STR "\n"); - return 1; + return 0; case 'a': smixer_level = 1; memset(&smixer_options, 0, sizeof(smixer_options)); @@ -1841,7 +1846,7 @@ int main(int argc, char *argv[]) smixer_options.abstract = SND_MIXER_SABSTRACT_BASIC; else { fprintf(stderr, "Select correct abstraction level (none or basic)...\n"); - morehelp++; + badopt++; } break; case 's': @@ -1854,49 +1859,55 @@ int main(int argc, char *argv[]) std_vol_type = VOL_MAP; break; default: - fprintf(stderr, "Invalid switch or option needs an argument.\n"); - morehelp++; + fprintf(stderr, "Invalid switch or option -%c needs an argument.\n", c); + badopt++; } } - if (morehelp) { - help(); + if (badopt) return 1; - } + smixer_options.device = card; - if (read_stdin) - return exec_stdin(); + if (read_stdin) { + retval = exec_stdin(); + goto finish; + } if (argc - optind <= 0) { - return selems(LEVEL_BASIC | level) ? 1 : 0; + retval = selems(LEVEL_BASIC | level) ? 1 : 0; + goto finish; } if (!strcmp(argv[optind], "help")) { - return help() ? 1 : 0; + retval = help() ? 1 : 0; } else if (!strcmp(argv[optind], "info")) { - return info() ? 1 : 0; + retval = info() ? 1 : 0; } else if (!strcmp(argv[optind], "controls")) { - return controls(level) ? 1 : 0; + retval = controls(level) ? 1 : 0; } else if (!strcmp(argv[optind], "contents")) { - return controls(LEVEL_BASIC | level) ? 1 : 0; + retval = controls(LEVEL_BASIC | level) ? 1 : 0; } else if (!strcmp(argv[optind], "scontrols") || !strcmp(argv[optind], "simple")) { - return selems(level) ? 1 : 0; + retval = selems(level) ? 1 : 0; } else if (!strcmp(argv[optind], "scontents")) { - return selems(LEVEL_BASIC | level) ? 1 : 0; + retval = selems(LEVEL_BASIC | level) ? 1 : 0; } else if (!strcmp(argv[optind], "sset") || !strcmp(argv[optind], "set")) { - return sset(argc - optind - 1, argc - optind > 1 ? argv + optind + 1 : NULL, 0, 0) ? 1 : 0; + retval = sset(argc - optind - 1, argc - optind > 1 ? argv + optind + 1 : NULL, 0, 0) ? 1 : 0; } else if (!strcmp(argv[optind], "sget") || !strcmp(argv[optind], "get")) { - return sset(argc - optind - 1, argc - optind > 1 ? argv + optind + 1 : NULL, 1, 0) ? 1 : 0; + retval = sset(argc - optind - 1, argc - optind > 1 ? argv + optind + 1 : NULL, 1, 0) ? 1 : 0; } else if (!strcmp(argv[optind], "cset")) { - return cset(argc - optind - 1, argc - optind > 1 ? argv + optind + 1 : NULL, 0, 0) ? 1 : 0; + retval = cset(argc - optind - 1, argc - optind > 1 ? argv + optind + 1 : NULL, 0, 0) ? 1 : 0; } else if (!strcmp(argv[optind], "cget")) { - return cset(argc - optind - 1, argc - optind > 1 ? argv + optind + 1 : NULL, 1, 0) ? 1 : 0; + retval = cset(argc - optind - 1, argc - optind > 1 ? argv + optind + 1 : NULL, 1, 0) ? 1 : 0; } else if (!strcmp(argv[optind], "events")) { - return events(argc - optind - 1, argc - optind > 1 ? argv + optind + 1 : NULL); + retval = events(argc - optind - 1, argc - optind > 1 ? argv + optind + 1 : NULL); } else if (!strcmp(argv[optind], "sevents")) { - return sevents(argc - optind - 1, argc - optind > 1 ? argv + optind + 1 : NULL); + retval = sevents(argc - optind - 1, argc - optind > 1 ? argv + optind + 1 : NULL); } else { fprintf(stderr, "amixer: Unknown command '%s'...\n", argv[optind]); + retval = 0; } - return 0; +finish: + snd_config_update_free_global(); + + return retval; } diff --git a/amixer/volume_mapping.c b/amixer/volume_mapping.c new file mode 120000 index 000000000..922aae8c3 --- /dev/null +++ b/amixer/volume_mapping.c @@ -0,0 +1 @@ +../alsamixer/volume_mapping.c \ No newline at end of file diff --git a/amixer/volume_mapping.h b/amixer/volume_mapping.h new file mode 120000 index 000000000..89718e7eb --- /dev/null +++ b/amixer/volume_mapping.h @@ -0,0 +1 @@ +../alsamixer/volume_mapping.h \ No newline at end of file diff --git a/aplay/aplay.c b/aplay/aplay.c index b75be6c55..0b7884e80 100644 --- a/aplay/aplay.c +++ b/aplay/aplay.c @@ -1985,11 +1985,11 @@ static void do_test_position(void) fprintf(stderr, _("Suspicious status buffer position (%li total): " "avail = %li, delay = %li, buffer = %li\n"), ++counter, (long)savail, (long)sdelay, (long)buffer_frames); - } else if (avail > delay) { + } else if (stream == SND_PCM_STREAM_CAPTURE && avail > delay) { fprintf(stderr, _("Suspicious buffer position avail > delay (%li total): " "avail = %li, delay = %li\n"), ++counter, (long)avail, (long)delay); - } else if (savail > sdelay) { + } else if (stream == SND_PCM_STREAM_CAPTURE && savail > sdelay) { fprintf(stderr, _("Suspicious status buffer position avail > delay (%li total): " "avail = %li, delay = %li\n"), ++counter, (long)savail, (long)sdelay); @@ -2570,7 +2570,9 @@ static void voc_play(int fd, int ofs, char *name) } } /* while(1) */ __end: - voc_pcm_flush(); + if (!in_aborting) { + voc_pcm_flush(); + } free(buf); } /* that was a big one, perhaps somebody split it :-) */ @@ -2885,9 +2887,11 @@ static void playback_go(int fd, size_t loaded, off64_t count, int rtype, char *n written += r; l = 0; } - snd_pcm_nonblock(handle, 0); - snd_pcm_drain(handle); - snd_pcm_nonblock(handle, nonblock); + if (!in_aborting) { + snd_pcm_nonblock(handle, 0); + snd_pcm_drain(handle); + snd_pcm_nonblock(handle, nonblock); + } } static int read_header(int *loaded, int header_size) @@ -3343,7 +3347,7 @@ static void playbackv_go(int* fds, unsigned int channels, size_t loaded, off64_t do { r = safe_read(fds[0], bufs[0], expected); if (r < 0) { - perror(names[channel]); + perror(names[0]); prg_exit(EXIT_FAILURE); } for (channel = 1; channel < channels; ++channel) { @@ -3363,9 +3367,11 @@ static void playbackv_go(int* fds, unsigned int channels, size_t loaded, off64_t r = r * bits_per_frame / 8; count -= r; } - snd_pcm_nonblock(handle, 0); - snd_pcm_drain(handle); - snd_pcm_nonblock(handle, nonblock); + if (!in_aborting) { + snd_pcm_nonblock(handle, 0); + snd_pcm_drain(handle); + snd_pcm_nonblock(handle, nonblock); + } } static void capturev_go(int* fds, unsigned int channels, off64_t count, int rtype, char **names) diff --git a/axfer/container.c b/axfer/container.c index 566acd058..8c88d5c40 100644 --- a/axfer/container.c +++ b/axfer/container.c @@ -143,8 +143,8 @@ static int set_nonblock_flag(int fd) return 0; } -int container_parser_init(struct container_context *cntr, - const char *const path, unsigned int verbose) +int container_parser_init(struct container_context *cntr, int fd, + unsigned int verbose) { const struct container_parser *parsers[] = { [CONTAINER_FORMAT_RIFF_WAVE] = &container_parser_riff_wave, @@ -157,8 +157,7 @@ int container_parser_init(struct container_context *cntr, int err; assert(cntr); - assert(path); - assert(path[0] != '\0'); + assert(fd >= 0); // Detect forgotten to destruct. assert(cntr->fd == 0); @@ -166,9 +165,10 @@ int container_parser_init(struct container_context *cntr, memset(cntr, 0, sizeof(*cntr)); - // Open a target descriptor. - if (!strcmp(path, "-")) { - cntr->fd = fileno(stdin); + cntr->fd = fd; + + cntr->stdio = (cntr->fd == fileno(stdin)); + if (cntr->stdio) { if (isatty(cntr->fd)) { fprintf(stderr, "A terminal is referred for standard input. " @@ -176,16 +176,12 @@ int container_parser_init(struct container_context *cntr, "should be referred instead.\n"); return -EIO; } - err = set_nonblock_flag(cntr->fd); - if (err < 0) - return err; - cntr->stdio = true; - } else { - cntr->fd = open(path, O_RDONLY | O_NONBLOCK); - if (cntr->fd < 0) - return -errno; } + err = set_nonblock_flag(cntr->fd); + if (err < 0) + return err; + // 4 bytes are enough to detect supported containers. err = container_recursive_read(cntr, cntr->magic, sizeof(cntr->magic)); if (err < 0) @@ -225,9 +221,8 @@ int container_parser_init(struct container_context *cntr, return 0; } -int container_builder_init(struct container_context *cntr, - const char *const path, enum container_format format, - unsigned int verbose) +int container_builder_init(struct container_context *cntr, int fd, + enum container_format format, unsigned int verbose) { const struct container_builder *builders[] = { [CONTAINER_FORMAT_RIFF_WAVE] = &container_builder_riff_wave, @@ -239,8 +234,7 @@ int container_builder_init(struct container_context *cntr, int err; assert(cntr); - assert(path); - assert(path[0] != '\0'); + assert(fd >= 0); // Detect forgotten to destruct. assert(cntr->fd == 0); @@ -248,11 +242,10 @@ int container_builder_init(struct container_context *cntr, memset(cntr, 0, sizeof(*cntr)); - // Open a target descriptor. - if (path == NULL || *path == '\0') - return -EINVAL; - if (!strcmp(path, "-")) { - cntr->fd = fileno(stdout); + cntr->fd = fd; + + cntr->stdio = (cntr->fd == fileno(stdout)); + if (cntr->stdio) { if (isatty(cntr->fd)) { fprintf(stderr, "A terminal is referred for standard output. " @@ -260,17 +253,12 @@ int container_builder_init(struct container_context *cntr, "should be referred instead.\n"); return -EIO; } - err = set_nonblock_flag(cntr->fd); - if (err < 0) - return err; - cntr->stdio = true; - } else { - cntr->fd = open(path, O_RDWR | O_NONBLOCK | O_CREAT | O_TRUNC, - 0644); - if (cntr->fd < 0) - return -errno; } + err = set_nonblock_flag(cntr->fd); + if (err < 0) + return err; + builder = builders[format]; // Allocate private data for the builder. @@ -463,7 +451,6 @@ void container_context_destroy(struct container_context *cntr) { assert(cntr); - close(cntr->fd); if (cntr->private_data) free(cntr->private_data); diff --git a/axfer/container.h b/axfer/container.h index cb64816dd..71017a6e2 100644 --- a/axfer/container.h +++ b/axfer/container.h @@ -11,7 +11,6 @@ #define _LARGEFILE64_SOURCE #include -#include #include #include @@ -61,11 +60,10 @@ struct container_context { const char *const container_suffix_from_format(enum container_format format); enum container_format container_format_from_path(const char *path); -int container_parser_init(struct container_context *cntr, - const char *const path, unsigned int verbose); -int container_builder_init(struct container_context *cntr, - const char *const path, enum container_format format, - unsigned int verbose); +int container_parser_init(struct container_context *cntr, int fd, + unsigned int verbose); +int container_builder_init(struct container_context *cntr, int fd, + enum container_format format, unsigned int verbose); void container_context_destroy(struct container_context *cntr); int container_context_pre_process(struct container_context *cntr, snd_pcm_format_t *format, diff --git a/axfer/subcmd-transfer.c b/axfer/subcmd-transfer.c index 8746e6f49..27d2cc5bc 100644 --- a/axfer/subcmd-transfer.c +++ b/axfer/subcmd-transfer.c @@ -19,6 +19,8 @@ struct context { struct container_context *cntrs; unsigned int cntr_count; + int *cntr_fds; + // NOTE: To handling Unix signal. bool interrupted; int signal; @@ -146,6 +148,20 @@ static int context_init(struct context *ctx, snd_pcm_stream_t direction, return xfer_context_init(&ctx->xfer, xfer_type, direction, argc, argv); } +static int allocate_containers(struct context *ctx, unsigned int count) +{ + ctx->cntrs = calloc(count, sizeof(*ctx->cntrs)); + if (ctx->cntrs == NULL) + return -ENOMEM; + ctx->cntr_count = count; + + ctx->cntr_fds = calloc(count, sizeof(*ctx->cntrs)); + if (ctx->cntr_fds == NULL) + return -ENOMEM; + + return 0; +} + static int capture_pre_process(struct context *ctx, snd_pcm_access_t *access, snd_pcm_uframes_t *frames_per_buffer, uint64_t *total_frame_count) @@ -164,10 +180,9 @@ static int capture_pre_process(struct context *ctx, snd_pcm_access_t *access, return err; // Prepare for containers. - ctx->cntrs = calloc(ctx->xfer.path_count, sizeof(*ctx->cntrs)); - if (ctx->cntrs == NULL) - return -ENOMEM; - ctx->cntr_count = ctx->xfer.path_count; + err = allocate_containers(ctx, ctx->xfer.path_count); + if (err < 0) + return err; if (ctx->cntr_count > 1) channels = 1; @@ -176,10 +191,20 @@ static int capture_pre_process(struct context *ctx, snd_pcm_access_t *access, *total_frame_count = 0; for (i = 0; i < ctx->cntr_count; ++i) { + const char *path = ctx->xfer.paths[i]; + int fd; uint64_t frame_count; - err = container_builder_init(ctx->cntrs + i, - ctx->xfer.paths[i], + if (!strcmp(path, "-")) { + fd = fileno(stdout); + } else { + fd = open(path, O_RDWR | O_CREAT | O_TRUNC, 0644); + if (fd < 0) + return -errno; + } + ctx->cntr_fds[i] = fd; + + err = container_builder_init(ctx->cntrs + i, ctx->cntr_fds[i], ctx->xfer.cntr_format, ctx->xfer.verbose > 1); if (err < 0) @@ -212,19 +237,28 @@ static int playback_pre_process(struct context *ctx, snd_pcm_access_t *access, int err; // Prepare for containers. - ctx->cntrs = calloc(ctx->xfer.path_count, sizeof(*ctx->cntrs)); - if (ctx->cntrs == NULL) - return -ENOMEM; - ctx->cntr_count = ctx->xfer.path_count; + err = allocate_containers(ctx, ctx->xfer.path_count); + if (err < 0) + return err; for (i = 0; i < ctx->cntr_count; ++i) { + const char *path = ctx->xfer.paths[i]; + int fd; snd_pcm_format_t format; unsigned int channels; unsigned int rate; uint64_t frame_count; - err = container_parser_init(ctx->cntrs + i, - ctx->xfer.paths[i], + if (!strcmp(path, "-")) { + fd = fileno(stdin); + } else { + fd = open(path, O_RDONLY); + if (fd < 0) + return -errno; + } + ctx->cntr_fds[i] = fd; + + err = container_parser_init(ctx->cntrs + i, ctx->cntr_fds[i], ctx->xfer.verbose > 1); if (err < 0) return err; @@ -421,6 +455,12 @@ static void context_post_process(struct context *ctx, free(ctx->cntrs); } + if (ctx->cntr_fds) { + for (i = 0; i < ctx->cntr_count; ++i) + close(ctx->cntr_fds[i]); + free(ctx->cntr_fds); + } + mapper_context_post_process(&ctx->mapper); mapper_context_destroy(&ctx->mapper); } diff --git a/axfer/test/container-test.c b/axfer/test/container-test.c index 9b30ae3d3..e5b62dd5e 100644 --- a/axfer/test/container-test.c +++ b/axfer/test/container-test.c @@ -6,11 +6,20 @@ // // Licensed under the terms of the GNU General Public License, version 2. +#include +#ifdef HAVE_MEMFD_CREATE +#define _GNU_SOURCE +#endif + #include "../container.h" #include "../misc.h" #include "generator.h" +#ifdef HAVE_MEMFD_CREATE +#include +#endif + #include #include #include @@ -24,8 +33,8 @@ struct container_trial { bool verbose; }; -static void test_builder(struct container_context *cntr, - enum container_format format, const char *const name, +static void test_builder(struct container_context *cntr, int fd, + enum container_format format, snd_pcm_access_t access, snd_pcm_format_t sample_format, unsigned int samples_per_frame, @@ -41,7 +50,7 @@ static void test_builder(struct container_context *cntr, uint64_t total_frame_count; int err; - err = container_builder_init(cntr, name, format, verbose); + err = container_builder_init(cntr, fd, format, verbose); assert(err == 0); sample = sample_format; @@ -71,8 +80,8 @@ static void test_builder(struct container_context *cntr, container_context_destroy(cntr); } -static void test_parser(struct container_context *cntr, - enum container_format format, const char *const name, +static void test_parser(struct container_context *cntr, int fd, + enum container_format format, snd_pcm_access_t access, snd_pcm_format_t sample_format, unsigned int samples_per_frame, unsigned int frames_per_second, @@ -86,7 +95,7 @@ static void test_parser(struct container_context *cntr, unsigned int handled_frame_count; int err; - err = container_parser_init(cntr, name, verbose); + err = container_parser_init(cntr, fd, verbose); assert(err == 0); sample = sample_format; @@ -142,30 +151,46 @@ static int callback(struct test_generator *gen, snd_pcm_access_t access, if (buf == NULL) return -ENOMEM; - // Remove a result of a previous trial. - unlink(name); - for (i = 0; i < ARRAY_SIZE(entries); ++i) { + int fd; + off64_t pos; + frames_per_second = entries[i]; - test_builder(&trial->cntr, trial->format, name, access, +#ifdef HAVE_MEMFD_CREATE + fd = memfd_create(name, 0); +#else + fd = open(name, O_RDWR | O_CREAT | O_TRUNC, 0644); +#endif + if (fd < 0) { + err = -errno; + break; + } + + test_builder(&trial->cntr, fd, trial->format, access, sample_format, samples_per_frame, frames_per_second, frame_buffer, frame_count, trial->verbose); - test_parser(&trial->cntr, trial->format, name, access, + pos = lseek64(fd, 0, SEEK_SET); + if (pos < 0) { + err = -errno; + break; + } + + test_parser(&trial->cntr, fd, trial->format, access, sample_format, samples_per_frame, frames_per_second, buf, frame_count, trial->verbose); err = memcmp(buf, frame_buffer, size); assert(err == 0); - unlink(name); + close(fd); } free(buf); - return 0; + return err; } int main(int argc, const char *argv[]) @@ -275,7 +300,7 @@ int main(int argc, const char *argv[]) for (i = begin; i < end; ++i) { err = generator_context_init(&gen, access_mask, sample_format_masks[i], - 1, 128, 23, 4500, 1024, + 1, 32, 23, 3000, 512, sizeof(struct container_trial)); if (err >= 0) { trial = gen.private_data; diff --git a/axfer/test/mapper-test.c b/axfer/test/mapper-test.c index f0376c77d..477871d10 100644 --- a/axfer/test/mapper-test.c +++ b/axfer/test/mapper-test.c @@ -6,11 +6,20 @@ // // Licensed under the terms of the GNU General Public License, version 2. +#include +#ifdef HAVE_MEMFD_CREATE +#define _GNU_SOURCE +#endif + #include "../mapper.h" #include "../misc.h" #include "generator.h" +#ifdef HAVE_MEMFD_CREATE +#include +#endif + #include #include #include @@ -63,10 +72,9 @@ static int test_demux(struct mapper_trial *trial, snd_pcm_access_t access, unsigned int frames_per_second, unsigned int frames_per_buffer, void *frame_buffer, unsigned int frame_count, - unsigned int cntr_count) + int *cntr_fds, unsigned int cntr_count) { struct container_context *cntrs = trial->cntrs; - char **paths = trial->paths; enum container_format cntr_format = trial->cntr_format; unsigned int bytes_per_sample; uint64_t total_frame_count; @@ -78,8 +86,7 @@ static int test_demux(struct mapper_trial *trial, snd_pcm_access_t access, unsigned int channels; unsigned int rate; - err = container_builder_init(cntrs + i, paths[i], cntr_format, - 0); + err = container_builder_init(cntrs + i, cntr_fds[i], cntr_format, 0); if (err < 0) goto end; @@ -156,10 +163,9 @@ static int test_mux(struct mapper_trial *trial, snd_pcm_access_t access, unsigned int frames_per_second, unsigned int frames_per_buffer, void *frame_buffer, unsigned int frame_count, - unsigned int cntr_count) + int *cntr_fds, unsigned int cntr_count) { struct container_context *cntrs = trial->cntrs; - char **paths = trial->paths; unsigned int bytes_per_sample; uint64_t total_frame_count; int i; @@ -170,7 +176,7 @@ static int test_mux(struct mapper_trial *trial, snd_pcm_access_t access, unsigned int channels; unsigned int rate; - err = container_parser_init(cntrs + i, paths[i], 0); + err = container_parser_init(cntrs + i, cntr_fds[i], 0); if (err < 0) goto end; @@ -218,6 +224,7 @@ static int test_mapper(struct mapper_trial *trial, snd_pcm_access_t access, void *check_buffer, unsigned int frame_count, unsigned int cntr_count) { + int *cntr_fds; unsigned int frames_per_buffer; int i; int err; @@ -225,18 +232,46 @@ static int test_mapper(struct mapper_trial *trial, snd_pcm_access_t access, // Use a buffer aligned by typical size of page frame. frames_per_buffer = ((frame_count + 4096) / 4096) * 4096; + cntr_fds = calloc(cntr_count, sizeof(*cntr_fds)); + if (cntr_fds == NULL) + return -ENOMEM; + + for (i = 0; i < cntr_count; ++i) { + const char *path = trial->paths[i]; + +#ifdef HAVE_MEMFD_CREATE + cntr_fds[i] = memfd_create(path, 0); +#else + cntr_fds[i] = open(path, O_RDWR | O_CREAT | O_TRUNC, 0644); +#endif + if (cntr_fds[i] < 0) { + err = -errno; + goto end; + } + } + err = test_demux(trial, access, sample_format, samples_per_frame, frames_per_second, frames_per_buffer, frame_buffer, - frame_count, cntr_count); + frame_count, cntr_fds, cntr_count); if (err < 0) goto end; + for (i = 0; i < cntr_count; ++i) { + off64_t pos = lseek64(cntr_fds[i], 0, SEEK_SET); + if (pos != 0) { + err = -EIO; + goto end; + } + } + err = test_mux(trial, access, sample_format, samples_per_frame, frames_per_second, frames_per_buffer, check_buffer, - frame_count, cntr_count); + frame_count, cntr_fds, cntr_count); end: for (i = 0; i < cntr_count; ++i) - unlink(trial->paths[i]); + close(cntr_fds[i]); + + free(cntr_fds); return err; } diff --git a/axfer/xfer-libasound-timer-mmap.c b/axfer/xfer-libasound-timer-mmap.c index ba26e2995..5715144fd 100644 --- a/axfer/xfer-libasound-timer-mmap.c +++ b/axfer/xfer-libasound-timer-mmap.c @@ -171,7 +171,8 @@ static int timer_mmap_process_frames(struct libasound_state *state, // exactly the mechanism yet. err = xfer_libasound_wait_event(state, timeout_msec, &revents); - if (err < 0) + // MEMO: timeout is expected since the above call is just to measure time elapse. + if (err < 0 && err != -ETIMEDOUT) return err; if (revents & POLLERR) { // TODO: error reporting. diff --git a/configure.ac b/configure.ac index 1f119c5ba..ff3e1f6c7 100644 --- a/configure.ac +++ b/configure.ac @@ -19,7 +19,7 @@ AC_PROG_MKDIR_P AC_PROG_LN_S AC_PROG_SED PKG_PROG_PKG_CONFIG -AM_PATH_ALSA(1.0.27) +AM_PATH_ALSA(1.2.5) if test "x$enable_alsatest" = "xyes"; then AC_CHECK_FUNC([snd_ctl_elem_add_enumerated], , [AC_ERROR([No user enum control support in alsa-lib])]) @@ -62,6 +62,11 @@ AC_CHECK_LIB([ffado], [ffado_streaming_init], [have_ffado="yes"], [have_ffado="n AS_IF([test x"$have_ffado" = xyes], [AC_DEFINE([WITH_FFADO], [1], [Define if FFADO library is available])]) +# Test programs for axfer use shm by memfd_create(2). If not supported, open(2) is used alternatively. +AC_CHECK_FUNC([memfd_create], [have_memfd_create="yes"], [have_memfd_create="no"]) +AS_IF([test x$have_memfd_create = xyes], + [AC_DEFINE([HAVE_MEMFD_CREATE], [1], [Define if Linux kernel supports memfd_create system call])]) + AM_CONDITIONAL(HAVE_PCM, test "$have_pcm" = "yes") AM_CONDITIONAL(HAVE_MIXER, test "$have_mixer" = "yes") AM_CONDITIONAL(HAVE_RAWMIDI, test "$have_rawmidi" = "yes") diff --git a/seq/aseqnet/aseqnet.c b/seq/aseqnet/aseqnet.c index 70a1cfd6b..ebdea0b49 100644 --- a/seq/aseqnet/aseqnet.c +++ b/seq/aseqnet/aseqnet.c @@ -491,8 +491,11 @@ static void flush_writebuf(void) if (cur_wrlen) { int i; for (i = 0; i < max_connection; i++) { - if (netfd[i] >= 0) - write(netfd[i], writebuf, cur_wrlen); + if (netfd[i] >= 0) { + ssize_t wrlen = write(netfd[i], writebuf, cur_wrlen); + if (wrlen != (ssize_t)cur_wrlen) + fprintf(stderr, "write error: %s", wrlen < 0 ? strerror(errno) : "short"); + } } cur_wrlen = 0; } diff --git a/speaker-test/speaker-test.c b/speaker-test/speaker-test.c index 773af0ad6..fd13d883b 100644 --- a/speaker-test/speaker-test.c +++ b/speaker-test/speaker-test.c @@ -285,6 +285,8 @@ static const int supported_formats[] = { SND_PCM_FORMAT_FLOAT_LE, SND_PCM_FORMAT_S24_3LE, SND_PCM_FORMAT_S24_3BE, + SND_PCM_FORMAT_S24_LE, + SND_PCM_FORMAT_S24_BE, SND_PCM_FORMAT_S32_LE, SND_PCM_FORMAT_S32_BE, -1 @@ -338,6 +340,20 @@ static void do_generate(uint8_t *frames, int channel, int count, *samp8++ = BE_INT(res.i) >> 8; *samp8++ = BE_INT(res.i) >> 16; break; + case SND_PCM_FORMAT_S24_LE: + res.i >>= 8; + *samp8++ = LE_INT(res.i); + *samp8++ = LE_INT(res.i) >> 8; + *samp8++ = LE_INT(res.i) >> 16; + *samp8++ = 0; + break; + case SND_PCM_FORMAT_S24_BE: + res.i >>= 8; + *samp8++ = 0; + *samp8++ = BE_INT(res.i); + *samp8++ = BE_INT(res.i) >> 8; + *samp8++ = BE_INT(res.i) >> 16; + break; case SND_PCM_FORMAT_S32_LE: *samp32++ = LE_INT(res.i); break; diff --git a/topology/Makefile.am b/topology/Makefile.am index a56c109c1..7f77fdd27 100644 --- a/topology/Makefile.am +++ b/topology/Makefile.am @@ -8,7 +8,10 @@ endif %.1: %.rst rst2man $< > $@ -alsatplg_SOURCES = topology.c +alsatplg_SOURCES = topology.c pre-processor.c pre-process-class.c pre-process-object.c \ + pre-process-dapm.c pre-process-dai.c + +noinst_HEADERS = topology.h pre-processor.h AM_CPPFLAGS = \ -Wall -I$(top_srcdir)/include diff --git a/topology/pre-process-class.c b/topology/pre-process-class.c new file mode 100644 index 000000000..a328693d6 --- /dev/null +++ b/topology/pre-process-class.c @@ -0,0 +1,303 @@ +/* + Copyright(c) 2021 Intel Corporation + All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 of the GNU General Public License as + published by the Free Software Foundation. + + 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 + 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., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + The full GNU General Public License is included in this distribution + in the file called LICENSE.GPL. +*/ +#include +#include +#include +#include +#include +#include +#include +#include +#include "topology.h" +#include "pre-processor.h" + +bool tplg_class_is_attribute_check(const char *attr, snd_config_t *class_cfg, char *category) +{ + snd_config_iterator_t i, next; + snd_config_t *cfg, *n; + int ret; + + ret = snd_config_search(class_cfg, category, &cfg); + if (ret < 0) + return false; + + snd_config_for_each(i, next, cfg) { + const char *id, *s; + + n = snd_config_iterator_entry(i); + if (snd_config_get_id(n, &id) < 0) + continue; + + if (snd_config_get_string(n, &s) < 0) + continue; + + if (!strcmp(attr, s)) + return true; + } + + return false; +} + +/* check if attribute is mandatory */ +bool tplg_class_is_attribute_mandatory(const char *attr, snd_config_t *class_cfg) +{ + return tplg_class_is_attribute_check(attr, class_cfg, "attributes.mandatory"); +} + +/* check if attribute is immutable */ +bool tplg_class_is_attribute_immutable(const char *attr, snd_config_t *class_cfg) +{ + return tplg_class_is_attribute_check(attr, class_cfg, "attributes.immutable"); +} + +/* check if attribute is unique */ +bool tplg_class_is_attribute_unique(const char *attr, snd_config_t *class_cfg) +{ + snd_config_t *unique; + const char *s; + int ret; + + ret = snd_config_search(class_cfg, "attributes.unique", &unique); + if (ret < 0) + return false; + + if (snd_config_get_string(unique, &s) < 0) + return false; + + if (!strcmp(attr, s)) + return true; + + return false; +} + +/* + * Helper function to look up class definition from the Object config. + * ex: For an object declaration, Object.Widget.pga.0{}, return the config correspdonding to + * Class.Widget.pga{}. Note that input config , "cfg" does not include the "Object" node. + */ +snd_config_t *tplg_class_lookup(struct tplg_pre_processor *tplg_pp, snd_config_t *cfg) +{ + snd_config_iterator_t first, end; + snd_config_t *class, *class_cfg = NULL; + const char *class_type, *class_name; + char *class_config_id; + int ret; + + if (snd_config_get_id(cfg, &class_type) < 0) + return NULL; + + first = snd_config_iterator_first(cfg); + end = snd_config_iterator_end(cfg); + + if (first == end) { + SNDERR("No class name provided for object type: %s\n", class_type); + return NULL; + } + + class = snd_config_iterator_entry(first); + + if (snd_config_get_id(class, &class_name) < 0) + return NULL; + + class_config_id = tplg_snprintf("Class.%s.%s", class_type, class_name); + if (!class_config_id) + return NULL; + + ret = snd_config_search(tplg_pp->input_cfg, class_config_id, &class_cfg); + if (ret < 0) + SNDERR("No Class definition found for %s\n", class_config_id); + + free(class_config_id); + return class_cfg; +} + +/* find the attribute config by name in the class definition */ +snd_config_t *tplg_class_find_attribute_by_name(struct tplg_pre_processor *tplg_pp, + snd_config_t *class, const char *name) +{ + snd_config_t *attr = NULL; + const char *class_id; + char *attr_str; + int ret; + + if (snd_config_get_id(class, &class_id) < 0) + return NULL; + + attr_str = tplg_snprintf("DefineAttribute.%s", name); + if (!attr_str) + return NULL; + + ret = snd_config_search(class, attr_str, &attr); + if (ret < 0) + SNDERR("No definition for attribute '%s' in class '%s'\n", + name, class_id); + + free(attr_str); + return attr; +} + +/* get the name of the attribute that must have a unique value in the object instance */ +const char *tplg_class_get_unique_attribute_name(struct tplg_pre_processor *tplg_pp, + snd_config_t *class) +{ + snd_config_t *unique; + const char *unique_name, *class_id; + int ret; + + if (snd_config_get_id(class, &class_id) < 0) + return NULL; + + ret = snd_config_search(class, "attributes.unique", &unique); + if (ret < 0) { + SNDERR("No unique attribute in class '%s'\n", class_id); + return NULL; + } + + if (snd_config_get_string(unique, &unique_name) < 0) { + SNDERR("Invalid name for unique attribute in class '%s'\n", class_id); + return NULL; + } + + return unique_name; +} + +/* get attribute type from the definition */ +snd_config_type_t tplg_class_get_attribute_type(struct tplg_pre_processor *tplg_pp, + snd_config_t *attr) +{ + snd_config_t *type; + const char *s; + int ret; + + /* default to integer if no type is given */ + ret = snd_config_search(attr, "type", &type); + if (ret < 0) + return SND_CONFIG_TYPE_INTEGER; + + ret = snd_config_get_string(type, &s); + assert(ret >= 0); + + if (!strcmp(s, "string")) + return SND_CONFIG_TYPE_STRING; + + if (!strcmp(s, "compound")) + return SND_CONFIG_TYPE_COMPOUND; + + if (!strcmp(s, "real")) + return SND_CONFIG_TYPE_REAL; + + if (!strcmp(s, "integer64")) + return SND_CONFIG_TYPE_INTEGER64; + + return SND_CONFIG_TYPE_INTEGER; +} + +/* get token_ref for attribute with name attr_name in the class */ +const char *tplg_class_get_attribute_token_ref(struct tplg_pre_processor *tplg_pp, + snd_config_t *class, const char *attr_name) +{ + snd_config_t *attributes, *attr, *token_ref; + const char *token; + int ret; + + ret = snd_config_search(class, "DefineAttribute", &attributes); + if (ret < 0) + return NULL; + + ret = snd_config_search(attributes, attr_name, &attr); + if (ret < 0) + return NULL; + + ret = snd_config_search(attr, "token_ref", &token_ref); + if (ret < 0) + return NULL; + + ret = snd_config_get_string(token_ref, &token); + if (ret < 0) + return NULL; + + return token; +} + +/* convert a valid attribute string value to the corresponding tuple value */ +long tplg_class_attribute_valid_tuple_value(struct tplg_pre_processor *tplg_pp, + snd_config_t *class, snd_config_t *attr) +{ + + snd_config_t *attributes, *cfg, *valid, *tuples, *n; + snd_config_iterator_t i, next; + const char *attr_name, *attr_value; + int ret; + + ret = snd_config_get_id(attr, &attr_name); + if (ret < 0) + return -EINVAL; + + ret = snd_config_get_string(attr, &attr_value); + if (ret < 0) + return -EINVAL; + + /* find attribute definition in class */ + ret = snd_config_search(class, "DefineAttribute", &attributes); + if (ret < 0) + return -EINVAL; + + + ret = snd_config_search(attributes, attr_name, &cfg); + if (ret < 0) + return -EINVAL; + + /* check if it has valid values */ + ret = snd_config_search(cfg, "constraints.valid_values", &valid); + if (ret < 0) + return -EINVAL; + + ret = snd_config_search(cfg, "constraints.tuple_values", &tuples); + if (ret < 0) + return -EINVAL; + + /* find and return the tuple value matching the attribute value id */ + snd_config_for_each(i, next, valid) { + const char *s, *id; + + n = snd_config_iterator_entry(i); + if (snd_config_get_string(n, &s) < 0) + continue; + if (snd_config_get_id(n, &id) < 0) + continue; + + if (!strcmp(attr_value, s)) { + snd_config_t *tuple; + long tuple_value; + + ret = snd_config_search(tuples, id, &tuple); + if (ret < 0) + return -EINVAL; + + ret = snd_config_get_integer(tuple, &tuple_value); + if (ret < 0) + return ret; + + return tuple_value; + } + } + + return -EINVAL; +} diff --git a/topology/pre-process-dai.c b/topology/pre-process-dai.c new file mode 100644 index 000000000..72aeefa72 --- /dev/null +++ b/topology/pre-process-dai.c @@ -0,0 +1,140 @@ +/* + Copyright(c) 2021 Intel Corporation + All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 of the GNU General Public License as + published by the Free Software Foundation. + + 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 + 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., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + The full GNU General Public License is included in this distribution + in the file called LICENSE.GPL. +*/ +#include +#include +#include +#include +#include +#include +#include "topology.h" +#include "pre-processor.h" + +int tplg_build_hw_cfg_object(struct tplg_pre_processor *tplg_pp, + snd_config_t *obj_cfg, snd_config_t *parent) +{ + snd_config_t *hw_cfg, *obj; + const char *name; + int ret; + + obj = tplg_object_get_instance_config(tplg_pp, obj_cfg); + + name = tplg_object_get_name(tplg_pp, obj); + if (!name) + return -EINVAL; + + ret = tplg_build_object_from_template(tplg_pp, obj_cfg, &hw_cfg, NULL, false); + if (ret < 0) + return ret; + + return tplg_parent_update(tplg_pp, parent, "hw_configs", name); +} + +int tplg_build_fe_dai_object(struct tplg_pre_processor *tplg_pp, snd_config_t *obj_cfg, + snd_config_t *parent) +{ + return tplg_build_base_object(tplg_pp, obj_cfg, parent, false); +} + +static int tplg_update_pcm_object(struct tplg_pre_processor *tplg_pp, + snd_config_t *obj_cfg, snd_config_t *parent) +{ + snd_config_t *top, *parent_obj, *obj, *dest, *cfg, *pcm, *child; + const char *parent_name, *item_name, *direction; + int ret; + + /* get object name */ + obj = tplg_object_get_instance_config(tplg_pp, obj_cfg); + item_name = tplg_object_get_name(tplg_pp, obj); + if (!item_name) + return -EINVAL; + + /* get direction */ + ret = snd_config_search(obj, "direction", &cfg); + if (ret < 0) { + SNDERR("no direction attribute in %s\n", item_name); + return ret; + } + + ret = snd_config_get_string(cfg, &direction); + if (ret < 0) { + SNDERR("Invalid direction attribute in %s\n", item_name); + return ret; + } + + /* add to parent section */ + top = tplg_object_get_section(tplg_pp, parent); + if (!top) { + SNDERR("Cannot find parent for %s\n", item_name); + return -EINVAL; + } + + parent_obj = tplg_object_get_instance_config(tplg_pp, parent); + + /* get parent name. if parent has no name, skip adding config */ + parent_name = tplg_object_get_name(tplg_pp, parent_obj); + if (!parent_name) + return 0; + + /* find parent config with name */ + dest = tplg_find_config(top, parent_name); + if (!dest) { + SNDERR("Cannot find parent section %s\n", parent_name); + return -EINVAL; + } + + ret = snd_config_search(dest, "pcm", &pcm); + if (ret < 0) { + ret = tplg_config_make_add(&pcm, "pcm", SND_CONFIG_TYPE_COMPOUND, dest); + if (ret < 0) { + SNDERR("Error creating pcm config in %s\n", parent_name); + return ret; + } + } + + ret = snd_config_search(pcm, direction, &cfg); + if (ret >= 0) { + SNDERR("pcm.%s exists already in %s\n", direction, parent_name); + return -EEXIST; + } + + ret = tplg_config_make_add(&cfg, direction, SND_CONFIG_TYPE_COMPOUND, pcm); + + if (ret >= 0) + ret = tplg_config_make_add(&child, "capabilities", SND_CONFIG_TYPE_STRING, cfg); + + if (ret >= 0) + ret = snd_config_set_string(child, item_name); + + return ret; +} + +int tplg_build_pcm_caps_object(struct tplg_pre_processor *tplg_pp, + snd_config_t *obj_cfg, snd_config_t *parent) +{ + snd_config_t *caps; + int ret; + + ret = tplg_build_object_from_template(tplg_pp, obj_cfg, &caps, NULL, false); + if (ret < 0) + return ret; + + /* add pcm capabilities to parent */ + return tplg_update_pcm_object(tplg_pp, obj_cfg, parent); +} diff --git a/topology/pre-process-dapm.c b/topology/pre-process-dapm.c new file mode 100644 index 000000000..450ca7173 --- /dev/null +++ b/topology/pre-process-dapm.c @@ -0,0 +1,418 @@ +/* + Copyright(c) 2021 Intel Corporation + All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 of the GNU General Public License as + published by the Free Software Foundation. + + 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 + 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., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + The full GNU General Public License is included in this distribution + in the file called LICENSE.GPL. +*/ +#include +#include +#include +#include +#include +#include +#include "topology.h" +#include "pre-processor.h" + +int tplg_build_base_object(struct tplg_pre_processor *tplg_pp, snd_config_t *obj_cfg, + snd_config_t *parent, bool skip_name) +{ + snd_config_t *top, *parent_obj, *cfg, *dest; + const char *parent_name; + + /* find parent section config */ + top = tplg_object_get_section(tplg_pp, parent); + if (!top) + return -EINVAL; + + parent_obj = tplg_object_get_instance_config(tplg_pp, parent); + + /* get parent name */ + parent_name = tplg_object_get_name(tplg_pp, parent_obj); + if (!parent_name) + return 0; + + /* find parent config with name */ + dest = tplg_find_config(top, parent_name); + if (!dest) { + SNDERR("Cannot find parent config %s\n", parent_name); + return -EINVAL; + } + + /* build config from template and add to parent */ + return tplg_build_object_from_template(tplg_pp, obj_cfg, &cfg, dest, skip_name); +} + +int tplg_build_scale_object(struct tplg_pre_processor *tplg_pp, snd_config_t *obj_cfg, + snd_config_t *parent) +{ + return tplg_build_base_object(tplg_pp, obj_cfg, parent, true); +} + +int tplg_build_ops_object(struct tplg_pre_processor *tplg_pp, snd_config_t *obj_cfg, + snd_config_t *parent) +{ + return tplg_build_base_object(tplg_pp, obj_cfg, parent, false); +} + +int tplg_build_channel_object(struct tplg_pre_processor *tplg_pp, snd_config_t *obj_cfg, + snd_config_t *parent) +{ + return tplg_build_base_object(tplg_pp, obj_cfg, parent, false); +} + +int tplg_build_tlv_object(struct tplg_pre_processor *tplg_pp, snd_config_t *obj_cfg, + snd_config_t *parent) +{ + snd_config_t *cfg; + const char *name; + int ret; + + cfg = tplg_object_get_instance_config(tplg_pp, obj_cfg); + + name = tplg_object_get_name(tplg_pp, cfg); + if (!name) + return -EINVAL; + + ret = tplg_build_object_from_template(tplg_pp, obj_cfg, &cfg, NULL, false); + if (ret < 0) + return ret; + + return tplg_parent_update(tplg_pp, parent, "tlv", name); +} + +static int tplg_build_control(struct tplg_pre_processor *tplg_pp, snd_config_t *obj_cfg, + snd_config_t *parent, char *type) +{ + snd_config_t *cfg, *obj; + const char *name; + int ret; + + obj = tplg_object_get_instance_config(tplg_pp, obj_cfg); + + /* get control name */ + ret = snd_config_search(obj, "name", &cfg); + if (ret < 0) + return 0; + + ret = snd_config_get_string(cfg, &name); + if (ret < 0) + return ret; + + ret = tplg_build_object_from_template(tplg_pp, obj_cfg, &cfg, NULL, false); + if (ret < 0) + return ret; + + return tplg_parent_update(tplg_pp, parent, type, name); +} + +int tplg_build_mixer_control(struct tplg_pre_processor *tplg_pp, snd_config_t *obj_cfg, + snd_config_t *parent) +{ + return tplg_build_control(tplg_pp, obj_cfg, parent, "mixer"); +} + +int tplg_build_bytes_control(struct tplg_pre_processor *tplg_pp, snd_config_t *obj_cfg, + snd_config_t *parent) +{ + return tplg_build_control(tplg_pp, obj_cfg, parent, "bytes"); +} + +/* + * Widget names for pipeline endpoints can be of the following type: + * "class. ex: pga.0.1, buffer.1.1 etc + * Optionally, the index argument for a widget can be omitted and will be substituted with + * the index from the route: ex: pga..0, host..playback etc + */ +static int tplg_pp_get_widget_name(struct tplg_pre_processor *tplg_pp, + const char *string, long index, char **widget) +{ + snd_config_iterator_t i, next; + snd_config_t *temp_cfg, *child, *class_cfg, *n; + char *class_name, *args, *widget_name; + int ret; + + /* get class name */ + args = strchr(string, '.'); + class_name = calloc(1, strlen(string) - strlen(args) + 1); + if (!class_name) + return -ENOMEM; + + snprintf(class_name, strlen(string) - strlen(args) + 1, "%s", string); + + /* create config with Widget class type */ + ret = snd_config_make(&temp_cfg, "Widget", SND_CONFIG_TYPE_COMPOUND); + if (ret < 0) { + free(class_name); + return ret; + } + + /* create config with class name and add it to the Widget config */ + ret = tplg_config_make_add(&child, class_name, SND_CONFIG_TYPE_COMPOUND, temp_cfg); + if (ret < 0) { + free(class_name); + return ret; + } + + /* get class definition for widget */ + class_cfg = tplg_class_lookup(tplg_pp, temp_cfg); + snd_config_delete(temp_cfg); + if (!class_cfg) { + free(class_name); + return -EINVAL; + } + + /* get constructor for class */ + ret = snd_config_search(class_cfg, "attributes.constructor", &temp_cfg); + if (ret < 0) { + SNDERR("No arguments in class for widget %s\n", string); + free(class_name); + return ret; + } + + widget_name = strdup(class_name); + free(class_name); + if (!widget_name) + return -ENOMEM; + + /* construct widget name using the constructor argument values */ + snd_config_for_each(i, next, temp_cfg) { + const char *id; + char *arg, *remaining, *temp; + + n = snd_config_iterator_entry(i); + if (snd_config_get_string(n, &id) < 0) + continue; + + if (!args) { + SNDERR("insufficient arugments for widget %s\n", string); + return -EINVAL; + } + + remaining = strchr(args + 1, '.'); + if (remaining) { + arg = calloc(1, strlen(args + 1) - strlen(remaining) + 1); + if (!arg) { + ret = -ENOMEM; + goto err; + } + snprintf(arg, strlen(args + 1) - strlen(remaining) + 1, "%s", args + 1); + } else { + arg = calloc(1, strlen(args + 1) + 1); + if (!arg) { + ret = -ENOMEM; + goto err; + } + + snprintf(arg, strlen(args + 1) + 1, "%s", args + 1); + } + + /* if no index provided, substitue with route index */ + if (!strcmp(arg, "") && !strcmp(id, "index")) { + free(arg); + arg = tplg_snprintf("%ld", index); + if (!arg) { + ret = -ENOMEM; + free(arg); + goto err; + } + } + + temp = tplg_snprintf("%s.%s", widget_name, arg); + if (!temp) { + ret = -ENOMEM; + free(arg); + goto err; + } + + free(widget_name); + widget_name = temp; + free(arg); + if (remaining) + args = remaining; + else + args = NULL; + } + + *widget = widget_name; + return 0; + +err: + free(widget_name); + return ret; +} + +int tplg_build_dapm_route_object(struct tplg_pre_processor *tplg_pp, snd_config_t *obj_cfg, + snd_config_t *parent) +{ + snd_config_t *top, *obj, *cfg, *route, *child, *parent_obj; + const char *name, *wname; + const char *parent_name = "Endpoint"; + char *src_widget_name, *sink_widget_name, *line_str, *route_name; + const char *control = ""; + long index = 0; + int ret; + + obj = tplg_object_get_instance_config(tplg_pp, obj_cfg); + + ret = snd_config_get_id(obj, &name); + if (ret < 0) + return -EINVAL; + + /* endpoint connections at the top-level conf have no parent */ + if (parent) { + parent_obj = tplg_object_get_instance_config(tplg_pp, parent); + + ret = snd_config_get_id(parent_obj, &parent_name); + if (ret < 0) + return -EINVAL; + } + + tplg_pp_debug("Building DAPM route object: '%s' ...", name); + + ret = snd_config_search(tplg_pp->output_cfg, "SectionGraph", &top); + if (ret < 0) { + ret = tplg_config_make_add(&top, "SectionGraph", + SND_CONFIG_TYPE_COMPOUND, tplg_pp->output_cfg); + if (ret < 0) { + SNDERR("Error creating 'SectionGraph' config\n"); + return ret; + } + } + + /* get route index */ + ret = snd_config_search(obj, "index", &cfg); + if (ret >= 0) { + ret = snd_config_get_integer(cfg, &index); + if (ret < 0) { + SNDERR("Invalid index route %s\n", name); + return ret; + } + } + + /* get source widget name */ + ret = snd_config_search(obj, "source", &cfg); + if (ret < 0) { + SNDERR("No source for route %s\n", name); + return ret; + } + + ret = snd_config_get_string(cfg, &wname); + if (ret < 0) { + SNDERR("Invalid name for source in route %s\n", name); + return ret; + } + + ret = tplg_pp_get_widget_name(tplg_pp, wname, index, &src_widget_name); + if (ret < 0) { + SNDERR("error getting widget name for %s\n", wname); + return ret; + } + + /* get sink widget name */ + ret = snd_config_search(obj, "sink", &cfg); + if (ret < 0) { + SNDERR("No sink for route %s\n", name); + free(src_widget_name); + return ret; + } + + ret = snd_config_get_string(cfg, &wname); + if (ret < 0) { + SNDERR("Invalid name for sink in route %s\n", name); + free(src_widget_name); + return ret; + } + + ret = tplg_pp_get_widget_name(tplg_pp, wname, index, &sink_widget_name); + if (ret < 0) { + SNDERR("error getting widget name for %s\n", wname); + free(src_widget_name); + return ret; + } + + /* get control name */ + ret = snd_config_search(obj, "control", &cfg); + if (ret >= 0) { + ret = snd_config_get_string(cfg, &control); + if (ret < 0) { + SNDERR("Invalid control name for route %s\n", name); + goto err; + } + } + + /* add route */ + route_name = tplg_snprintf("%s.%s", parent_name, name); + if (!route_name) { + ret = -ENOMEM; + goto err; + } + + ret = snd_config_make(&route, route_name, SND_CONFIG_TYPE_COMPOUND); + free(route_name); + if (ret < 0) { + SNDERR("Error creating route config for %s %d\n", name, ret); + goto err; + } + + ret = snd_config_add(top, route); + if (ret < 0) { + SNDERR("Error adding route config for %s %d\n", name, ret); + goto err; + } + + /* add index */ + ret = tplg_config_make_add(&child, "index", SND_CONFIG_TYPE_INTEGER, route); + if (ret < 0) { + SNDERR("Error creating index config for %s\n", name); + goto err; + } + + ret = snd_config_set_integer(child, index); + if (ret < 0) { + SNDERR("Error setting index config for %s\n", name); + goto err; + } + + /* add lines */ + ret = tplg_config_make_add(&cfg, "lines", SND_CONFIG_TYPE_COMPOUND, route); + if (ret < 0) { + SNDERR("Error creating lines config for %s\n", name); + goto err; + } + + /* add route string */ + ret = tplg_config_make_add(&child, "0", SND_CONFIG_TYPE_STRING, cfg); + if (ret < 0) { + SNDERR("Error creating lines config for %s\n", name); + goto err; + } + + line_str = tplg_snprintf("%s, %s, %s", src_widget_name, control, sink_widget_name); + if (!line_str) { + ret = -ENOMEM; + goto err; + } + + /* set the line string */ + ret = snd_config_set_string(child, line_str); + free(line_str); + if (ret < 0) + SNDERR("Error creating lines config for %s\n", name); +err: + free(src_widget_name); + free(sink_widget_name); + return ret; +} diff --git a/topology/pre-process-object.c b/topology/pre-process-object.c new file mode 100644 index 000000000..09aa37585 --- /dev/null +++ b/topology/pre-process-object.c @@ -0,0 +1,1537 @@ +/* + Copyright(c) 2021 Intel Corporation + All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 of the GNU General Public License as + published by the Free Software Foundation. + + 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 + 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., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + The full GNU General Public License is included in this distribution + in the file called LICENSE.GPL. +*/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "gettext.h" +#include "topology.h" +#include "pre-processor.h" + +/* Parse VendorToken object, create the "SectionVendorToken" and save it */ +int tplg_build_vendor_token_object(struct tplg_pre_processor *tplg_pp, + snd_config_t *obj_cfg, snd_config_t *parent) +{ + snd_config_iterator_t i, next; + snd_config_t *vtop, *n, *obj; + const char *name; + int ret; + + ret = tplg_build_object_from_template(tplg_pp, obj_cfg, &vtop, NULL, false); + if (ret < 0) + return ret; + + ret = snd_config_get_id(vtop, &name); + if (ret < 0) + return ret; + + /* add the tuples */ + obj = tplg_object_get_instance_config(tplg_pp, obj_cfg); + snd_config_for_each(i, next, obj) { + snd_config_t *dst; + const char *id; + + n = snd_config_iterator_entry(i); + + if (snd_config_get_id(n, &id) < 0) + continue; + + if (!strcmp(id, "name")) + continue; + + ret = snd_config_copy(&dst, n); + if (ret < 0) { + SNDERR("Error copying config node %s for '%s'\n", id, name); + return ret; + } + + ret = snd_config_add(vtop, dst); + if (ret < 0) { + snd_config_delete(dst); + SNDERR("Error adding vendortoken %s for %s\n", id, name); + return ret; + } + } + + return ret; +} + +int tplg_parent_update(struct tplg_pre_processor *tplg_pp, snd_config_t *parent, + const char *section_name, const char *item_name) +{ + snd_config_iterator_t i, next; + snd_config_t *child, *cfg, *top, *item_config, *n; + const char *parent_name; + char *item_id; + int ret, id = 0; + + child = tplg_object_get_instance_config(tplg_pp, parent); + ret = snd_config_search(child, "name", &cfg); + if (ret < 0) { + ret = snd_config_get_id(child, &parent_name); + if (ret < 0) { + SNDERR("No name config for parent\n"); + return ret; + } + } else { + ret = snd_config_get_string(cfg, &parent_name); + if (ret < 0) { + SNDERR("Invalid name for parent\n"); + return ret; + } + } + + top = tplg_object_get_section(tplg_pp, parent); + if (!top) + return -EINVAL; + + /* get config with name */ + cfg = tplg_find_config(top, parent_name); + if (!cfg) + return ret; + + /* get section config */ + if (!strcmp(section_name, "tlv")) { + ret = tplg_config_make_add(&item_config, section_name, + SND_CONFIG_TYPE_STRING, cfg); + if (ret < 0) { + SNDERR("Error creating section config widget %s for %s\n", + section_name, parent_name); + return ret; + } + + return snd_config_set_string(item_config, item_name); + } + + ret = snd_config_search(cfg, section_name, &item_config); + if (ret < 0) { + ret = tplg_config_make_add(&item_config, section_name, + SND_CONFIG_TYPE_COMPOUND, cfg); + if (ret < 0) { + SNDERR("Error creating section config widget %s for %s\n", + section_name, parent_name); + return ret; + } + } + + snd_config_for_each(i, next, item_config) { + const char *name; + + n = snd_config_iterator_entry(i); + if (snd_config_get_string(n, &name) < 0) + continue; + + /* item already exists */ + if (!strcmp(name, item_name)) + return 0; + id++; + } + + /* add new item */ + item_id = tplg_snprintf("%d", id); + if (!item_id) + return -ENOMEM; + + ret = snd_config_make(&cfg, item_id, SND_CONFIG_TYPE_STRING); + free(item_id); + if (ret < 0) + return ret; + + ret = snd_config_set_string(cfg, item_name); + if (ret < 0) + return ret; + + ret = snd_config_add(item_config, cfg); + if (ret < 0) + snd_config_delete(cfg); + + return ret; +} + +/* Parse data object, create the "SectionData" and save it. Only "bytes" data supported for now */ +int tplg_build_data_object(struct tplg_pre_processor *tplg_pp, snd_config_t *obj_cfg, + snd_config_t *parent) +{ + snd_config_t *dtop; + const char *name; + int ret; + + ret = tplg_build_object_from_template(tplg_pp, obj_cfg, &dtop, NULL, false); + if (ret < 0) + return ret; + + ret = snd_config_get_id(dtop, &name); + if (ret < 0) + return ret; + + return tplg_parent_update(tplg_pp, parent, "data", name); +} + +static int tplg_create_config_template(struct tplg_pre_processor *tplg_pp, + snd_config_t **template, + const struct config_template_items *items) +{ + snd_config_t *top, *child; + int ret, i; + + ret = snd_config_make(&top, "template", SND_CONFIG_TYPE_COMPOUND); + if (ret < 0) + return ret; + + /* add integer configs */ + if (items->int_config_ids) + for (i = 0; i < MAX_CONFIGS_IN_TEMPLATE; i++) + if (items->int_config_ids[i]) { + ret = tplg_config_make_add(&child, items->int_config_ids[i], + SND_CONFIG_TYPE_INTEGER, top); + if (ret < 0) + goto err; + } + + /* add string configs */ + if (items->string_config_ids) + for (i = 0; i < MAX_CONFIGS_IN_TEMPLATE; i++) + if (items->string_config_ids[i]) { + ret = tplg_config_make_add(&child, items->string_config_ids[i], + SND_CONFIG_TYPE_STRING, top); + if (ret < 0) + goto err; + } + + /* add compound configs */ + if (items->compound_config_ids) + for (i = 0; i < MAX_CONFIGS_IN_TEMPLATE; i++) { + if (items->compound_config_ids[i]) { + ret = tplg_config_make_add(&child, items->compound_config_ids[i], + SND_CONFIG_TYPE_COMPOUND, top); + if (ret < 0) + goto err; + } + } + +err: + if (ret < 0) { + snd_config_delete(top); + return ret; + } + + *template = top; + return ret; +} + +static void tplg_attribute_print_valid_values(snd_config_t *valid_values, const char *name) +{ + snd_config_iterator_t i, next; + snd_config_t *n; + + SNDERR("valid values for attribute %s are:\n", name); + + snd_config_for_each(i, next, valid_values) { + const char *s, *id; + + n = snd_config_iterator_entry(i); + if (snd_config_get_id(n, &id) < 0) + continue; + + if (snd_config_get_string(n, &s) < 0) + continue; + + SNDERR("%s", s); + } +} + +/* check is attribute value belongs in the set of valid values */ +static bool tplg_is_attribute_valid_value(snd_config_t *valid_values, const char *value) +{ + snd_config_iterator_t i, next; + snd_config_t *n; + + snd_config_for_each(i, next, valid_values) { + const char *s, *id; + + n = snd_config_iterator_entry(i); + if (snd_config_get_id(n, &id) < 0) + continue; + + if (snd_config_get_string(n, &s) < 0) + continue; + + if (!strcmp(value, s)) + return true; + } + + return false; +} + +/* check if attribute value passes the min/max value constraints */ +static bool tplg_object_is_attribute_min_max_valid(snd_config_t *attr, snd_config_t *obj_attr, + bool min_check) +{ + snd_config_type_t type = snd_config_get_type(obj_attr); + snd_config_t *valid; + const char *attr_name; + int ret; + + if (snd_config_get_id(attr, &attr_name) < 0) + return false; + + if (min_check) { + ret = snd_config_search(attr, "constraints.min", &valid); + if (ret < 0) + return true; + } else { + ret = snd_config_search(attr, "constraints.max", &valid); + if (ret < 0) + return true; + } + + switch(type) { + case SND_CONFIG_TYPE_INTEGER: + { + long v, m; + + if (snd_config_get_integer(valid, &m) < 0) + return true; + + if (snd_config_get_integer(obj_attr, &v) < 0) + return false; + + if (min_check) { + if (v < m) { + SNDERR("attribute '%s' value: %ld is less than min value: %d\n", + attr_name, v, m); + return false; + } + } else { + if (v > m) { + SNDERR("attribute '%s' value: %ld is greater than max value: %d\n", + attr_name, v, m); + return false; + } + } + + return true; + } + case SND_CONFIG_TYPE_INTEGER64: + { + long long v; + long m; + + if (snd_config_get_integer(valid, &m) < 0) + return true; + + if (snd_config_get_integer64(obj_attr, &v) < 0) + return false; + + if (min_check) { + if (v < m) { + SNDERR("attribute '%s' value: %ld is less than min value: %d\n", + attr_name, v, m); + return false; + } + } else { + if (v > m) { + SNDERR("attribute '%s' value: %ld is greater than max value: %d\n", + attr_name, v, m); + return false; + } + } + + return true; + } + default: + break; + } + + return false; +} + +/* check for min/max and valid value constraints */ +static bool tplg_object_is_attribute_valid(struct tplg_pre_processor *tplg_pp, + snd_config_t *attr, snd_config_t *object) +{ + snd_config_iterator_t i, next; + snd_config_t *valid, *obj_attr, *n; + snd_config_type_t type; + const char *attr_name, *obj_value; + int ret; + + if (snd_config_get_id(attr, &attr_name) < 0) + return false; + + ret = snd_config_search(object, attr_name, &obj_attr); + if (ret < 0) { + SNDERR("attr %s not found \n", attr_name); + return false; + } + type = snd_config_get_type(obj_attr); + + /* check if attribute has valid values */ + ret = snd_config_search(attr, "constraints.valid_values", &valid); + if (ret < 0) + goto min_max_check; + + switch(type) { + case SND_CONFIG_TYPE_STRING: + if (snd_config_get_string(obj_attr, &obj_value) < 0) + return false; + if (!tplg_is_attribute_valid_value(valid, obj_value)) { + tplg_attribute_print_valid_values(valid, attr_name); + return false; + } + return true; + case SND_CONFIG_TYPE_COMPOUND: + snd_config_for_each(i, next, obj_attr) { + const char *s, *id; + + n = snd_config_iterator_entry(i); + if (snd_config_get_id(n, &id) < 0) + continue; + + if (snd_config_get_string(n, &s) < 0) + continue; + + if (!tplg_is_attribute_valid_value(valid, s)) { + tplg_attribute_print_valid_values(valid, attr_name); + return false; + } + } + return true; + default: + break; + } + + return false; + +min_max_check: + if (!tplg_object_is_attribute_min_max_valid(attr, obj_attr, true)) + return false; + + return tplg_object_is_attribute_min_max_valid(attr, obj_attr, false); +} + +/* get object's name attribute value */ +const char *tplg_object_get_name(struct tplg_pre_processor *tplg_pp, + snd_config_t *object) +{ + snd_config_t *cfg; + const char *name; + int ret; + + ret = snd_config_search(object, "name", &cfg); + if (ret < 0) + return NULL; + + ret = snd_config_get_string(cfg, &name); + if (ret < 0) + return NULL; + + return name; +} + +/* look up the instance of object in a config */ +static snd_config_t *tplg_object_lookup_in_config(struct tplg_pre_processor *tplg_pp, + snd_config_t *class, const char *type, + const char *class_name, const char *id) +{ + snd_config_t *obj_cfg = NULL; + char *config_id; + + config_id = tplg_snprintf("Object.%s.%s.%s", type, class_name, id); + if (!config_id) + return NULL; + + snd_config_search(class, config_id, &obj_cfg); + free(config_id); + return obj_cfg; +} + +static int tplg_pp_add_object_tuple_section(struct tplg_pre_processor *tplg_pp, + snd_config_t *class_cfg, + snd_config_t *attr, char *data_name, + const char *token_ref) +{ + snd_config_t *top, *tuple_cfg, *child, *cfg, *new; + const char *id; + char *token, *type; + long tuple_value; + int ret; + + tplg_pp_debug("Building vendor tuples section: '%s' ...", data_name); + + ret = snd_config_search(tplg_pp->output_cfg, "SectionVendorTuples", &top); + if (ret < 0) { + ret = tplg_config_make_add(&top, "SectionVendorTuples", + SND_CONFIG_TYPE_COMPOUND, tplg_pp->output_cfg); + if (ret < 0) { + SNDERR("Error creating SectionVendorTuples config\n"); + return ret; + } + } + + type = strchr(token_ref, '.'); + token = calloc(1, strlen(token_ref) - strlen(type) + 1); + if (!token) + return -ENOMEM; + snprintf(token, strlen(token_ref) - strlen(type) + 1, "%s", token_ref); + + tuple_cfg = tplg_find_config(top, data_name); + if (!tuple_cfg) { + /* add new SectionVendorTuples */ + ret = tplg_config_make_add(&tuple_cfg, data_name, SND_CONFIG_TYPE_COMPOUND, top); + if (ret < 0) { + SNDERR("Error creating new vendor tuples config %s\n", data_name); + goto err; + } + + ret = tplg_config_make_add(&child, "tokens", SND_CONFIG_TYPE_STRING, + tuple_cfg); + if (ret < 0) { + SNDERR("Error creating tokens config for '%s'\n", data_name); + goto err; + } + + ret = snd_config_set_string(child, token); + if (ret < 0) { + SNDERR("Error setting tokens config for '%s'\n", data_name); + goto err; + } + + ret = tplg_config_make_add(&child, "tuples", SND_CONFIG_TYPE_COMPOUND, + tuple_cfg); + if (ret < 0) { + SNDERR("Error creating tuples config for '%s'\n", data_name); + goto err; + } + + ret = tplg_config_make_add(&cfg, type + 1, SND_CONFIG_TYPE_COMPOUND, + child); + if (ret < 0) { + SNDERR("Error creating tuples type config for '%s'\n", data_name); + goto err; + } + } else { + char *id; + + id = tplg_snprintf("tuples.%s", type + 1); + if (!id) { + ret = -ENOMEM; + goto err; + } + + ret = snd_config_search(tuple_cfg, id , &cfg); + free(id); + if (ret < 0) { + SNDERR("can't find type config %s\n", type + 1); + goto err; + } + } + + ret = snd_config_get_id(attr, &id); + if (ret < 0) + goto err; + + /* tuple exists already? */ + ret = snd_config_search(cfg, id, &child); + if (ret >=0) + goto err; + + /* add attribute to tuples */ + tuple_value = tplg_class_attribute_valid_tuple_value(tplg_pp, class_cfg, attr); + if (tuple_value < 0) { + /* just copy attribute cfg as is */ + ret = snd_config_copy(&new, attr); + if (ret < 0) { + SNDERR("can't copy attribute for %s\n", data_name); + goto err; + } + } else { + ret = snd_config_make(&new, id, SND_CONFIG_TYPE_INTEGER); + if (ret < 0) + goto err; + + ret = snd_config_set_integer(new, tuple_value); + if (ret < 0) + goto err; + } + + ret = snd_config_add(cfg, new); + if (ret < 0) + goto err; + +err: + free(token); + return ret; +} + +static int tplg_pp_add_object_data_section(struct tplg_pre_processor *tplg_pp, + snd_config_t *obj_data, char *data_name) +{ + snd_config_iterator_t i, next; + snd_config_t *top, *data_cfg, *child; + char *data_id; + int ret, id = 0; + + ret = snd_config_search(tplg_pp->output_cfg, "SectionData", &top); + if (ret < 0) { + ret = tplg_config_make_add(&top, "SectionData", SND_CONFIG_TYPE_COMPOUND, + tplg_pp->output_cfg); + if (ret < 0) { + SNDERR("Failed to add SectionData\n"); + return ret; + } + } + + /* nothing to do if data section already exists */ + data_cfg = tplg_find_config(top, data_name); + if (data_cfg) + return 0; + + tplg_pp_debug("Building data section %s ...", data_name); + + /* add new SectionData */ + ret = tplg_config_make_add(&data_cfg, data_name, SND_CONFIG_TYPE_COMPOUND, top); + if (ret < 0) + return ret; + + ret = tplg_config_make_add(&child, "tuples", SND_CONFIG_TYPE_STRING, data_cfg); + if (ret < 0) { + SNDERR("error adding data ref for %s\n", data_name); + return ret; + } + + ret = snd_config_set_string(child, data_name); + if (ret < 0) { + SNDERR("error setting tuples ref for %s\n", data_name); + return ret; + } + + /* add data item to object */ + snd_config_for_each(i, next, obj_data) + id++; + + data_id = tplg_snprintf("%d", id); + if (!data_id) + return -ENOMEM; + + ret = tplg_config_make_add(&child, data_id, SND_CONFIG_TYPE_STRING, obj_data); + free(data_id); + if (ret < 0) { + SNDERR("error adding data ref %s\n", data_name); + return ret; + } + + return snd_config_set_string(child, data_name); +} + +static int tplg_add_object_data(struct tplg_pre_processor *tplg_pp, snd_config_t *obj_cfg, + snd_config_t *top) +{ + snd_config_iterator_t i, next; + snd_config_t *data_cfg, *class_cfg, *n, *obj; + const char *object_id; + int ret; + + if (snd_config_get_id(top, &object_id) < 0) + return 0; + + obj = tplg_object_get_instance_config(tplg_pp, obj_cfg); + + class_cfg = tplg_class_lookup(tplg_pp, obj_cfg); + if (!class_cfg) + return -EINVAL; + + /* add data config to top */ + ret = snd_config_search(top, "data", &data_cfg); + if (ret < 0) { + ret = tplg_config_make_add(&data_cfg, "data", SND_CONFIG_TYPE_COMPOUND, top); + if (ret < 0) { + SNDERR("error creating data config for %s\n", object_id); + return ret; + } + } + + /* add data items to object's data section */ + snd_config_for_each(i, next, obj) { + const char *id, *token; + char *data_cfg_name; + + n = snd_config_iterator_entry(i); + if (snd_config_get_id(n, &id) < 0) + continue; + + token = tplg_class_get_attribute_token_ref(tplg_pp, class_cfg, id); + if (!token) + continue; + + data_cfg_name = tplg_snprintf("%s.%s", object_id, token); + if (!data_cfg_name) + return -ENOMEM; + + ret = tplg_pp_add_object_data_section(tplg_pp, data_cfg, data_cfg_name); + if (ret < 0) { + SNDERR("Failed to add data section %s\n", data_cfg_name); + free(data_cfg_name); + return ret; + } + + ret = tplg_pp_add_object_tuple_section(tplg_pp, class_cfg, n, data_cfg_name, + token); + free(data_cfg_name); + if (ret < 0) { + SNDERR("Failed to add data section %s\n", data_cfg_name); + return ret; + } + } + + return 0; +} + +/* search for all template configs in the source config and copy them to the destination */ +static int tplg_object_add_attributes(snd_config_t *dst, snd_config_t *template, + snd_config_t *src) +{ + snd_config_iterator_t i, next; + snd_config_t *n; + int ret; + + snd_config_for_each(i, next, template) { + snd_config_t *attr, *new; + const char *id; + + n = snd_config_iterator_entry(i); + if (snd_config_get_id(n, &id) < 0) + continue; + + ret = snd_config_search(src, id, &attr); + if (ret < 0) + continue; + + /* skip if attribute is already set */ + ret = snd_config_search(dst, id, &new); + if (ret >= 0) + continue; + + ret = snd_config_copy(&new, attr); + if (ret < 0) { + SNDERR("failed to copy attribute %s\n", id); + return ret; + } + + ret = snd_config_add(dst, new); + if (ret < 0) { + snd_config_delete(new); + SNDERR("failed to add attribute %s\n", id); + return ret; + } + } + + return 0; +} + +static const struct build_function_map *tplg_object_get_map(struct tplg_pre_processor *tplg_pp, + snd_config_t *obj); + +/* + * Function to create a new "section" config based on the template. The new config will be + * added to the output_cfg or the top_config input parameter. + */ +int tplg_build_object_from_template(struct tplg_pre_processor *tplg_pp, snd_config_t *obj_cfg, + snd_config_t **wtop, snd_config_t *top_config, + bool skip_name) +{ + snd_config_t *top, *template, *obj; + const struct build_function_map *map; + const char *object_name; + int ret; + + /* look up object map */ + map = tplg_object_get_map(tplg_pp, obj_cfg); + if (!map) { + SNDERR("unknown object type or class name\n"); + return -EINVAL; + } + + obj = tplg_object_get_instance_config(tplg_pp, obj_cfg); + + /* look up or create the corresponding section config for object */ + if (!top_config) + top_config = tplg_pp->output_cfg; + + ret = snd_config_search(top_config, map->section_name, &top); + if (ret < 0) { + ret = tplg_config_make_add(&top, map->section_name, SND_CONFIG_TYPE_COMPOUND, + top_config); + if (ret < 0) { + SNDERR("Error creating %s config\n", map->section_name); + return ret; + } + } + + /* get object name */ + object_name = tplg_object_get_name(tplg_pp, obj); + if (!object_name) { + ret = snd_config_get_id(obj, &object_name); + if (ret < 0) { + SNDERR("Invalid ID for %s\n", map->section_name); + return ret; + } + } + + tplg_pp_debug("Building object: '%s' ...", object_name); + + /* create and add new object config with name, if needed */ + if (skip_name) { + *wtop = top; + } else { + *wtop = tplg_find_config(top, object_name); + if (!(*wtop)) { + ret = tplg_config_make_add(wtop, object_name, SND_CONFIG_TYPE_COMPOUND, + top); + if (ret < 0) { + SNDERR("Error creating config for %s\n", object_name); + return ret; + } + } + } + + /* create template config */ + if (!map->template_items) + return 0; + + ret = tplg_create_config_template(tplg_pp, &template, map->template_items); + if (ret < 0) { + SNDERR("Error creating template config for %s\n", object_name); + return ret; + } + + /* update section config based on template and the attribute values in the object */ + ret = tplg_object_add_attributes(*wtop, template, obj); + snd_config_delete(template); + if (ret < 0) + SNDERR("Error adding attributes for object '%s'\n", object_name); + + return ret; +} + +static int tplg_build_generic_object(struct tplg_pre_processor *tplg_pp, snd_config_t *obj_cfg, + snd_config_t *parent) +{ + snd_config_t *wtop; + const char *name; + int ret; + + ret = tplg_build_object_from_template(tplg_pp, obj_cfg, &wtop, NULL, false); + if (ret < 0) + return ret; + + ret = snd_config_get_id(wtop, &name); + if (ret < 0) + return ret; + + ret = tplg_add_object_data(tplg_pp, obj_cfg, wtop); + if (ret < 0) + SNDERR("Failed to add data section for %s\n", name); + + return ret; +} + +const struct config_template_items pcm_caps_config = { + .int_config_ids = {"rate_min", "rate_max", "channels_min", "channels_max", "periods_min", + "periods_max", "period_size_min", "period_size_max", "buffer_size_min", + "buffer_size_max", "sig_bits"}, + .string_config_ids = {"formats", "rates"}, +}; + +const struct config_template_items fe_dai_config = { + .int_config_ids = {"id"}, +}; + +const struct config_template_items hwcfg_config = { + .int_config_ids = {"id", "bclk_freq", "bclk_invert", "fsync_invert", "fsync_freq", + "mclk_freq", "pm_gate_clocks", "tdm_slots", "tdm_slot_width", + "tx_slots", "rx_slots", "tx_channels", "rx_channels"}, + .string_config_ids = {"format", "bclk", "fsync", "mclk"}, +}; + +const struct config_template_items be_dai_config = { + .int_config_ids = {"id", "default_hw_conf_id", "symmertic_rates", "symmetric_channels", + "symmetric_sample_bits"}, + .string_config_ids = {"stream_name"}, +}; + +const struct config_template_items pcm_config = { + .int_config_ids = {"id", "compress", "symmertic_rates", "symmetric_channels", + "symmetric_sample_bits"}, +}; + +const struct config_template_items mixer_control_config = { + .int_config_ids = {"index", "max", "invert"}, + .compound_config_ids = {"access"} +}; + +const struct config_template_items bytes_control_config = { + .int_config_ids = {"index", "base", "num_regs", "max", "mask"}, +}; + +const struct config_template_items scale_config = { + .int_config_ids = {"min", "step", "mute"}, +}; + +const struct config_template_items ops_config = { + .int_config_ids = {"get", "put"}, + .string_config_ids = {"info"}, +}; + +const struct config_template_items channel_config = { + .int_config_ids = {"reg", "shift"}, +}; + +const struct config_template_items widget_config = { + .int_config_ids = {"index", "no_pm", "shift", "invert", "subseq", "event_type", + "event_flags"}, + .string_config_ids = {"type", "stream_name"}, +}; + +const struct config_template_items data_config = { + .string_config_ids = {"bytes"} +}; + +const struct build_function_map object_build_map[] = { + {"Base", "manifest", "SectionManifest", &tplg_build_generic_object, NULL}, + {"Base", "data", "SectionData", &tplg_build_data_object, &data_config}, + {"Base", "tlv", "SectionTLV", &tplg_build_tlv_object, NULL}, + {"Base", "scale", "scale", &tplg_build_scale_object, &scale_config}, + {"Base", "ops", "ops" ,&tplg_build_ops_object, &ops_config}, + {"Base", "extops", "extops" ,&tplg_build_ops_object, &ops_config}, + {"Base", "channel", "channel", &tplg_build_channel_object, &channel_config}, + {"Base", "VendorToken", "SectionVendorTokens", &tplg_build_vendor_token_object, NULL}, + {"Base", "hw_config", "SectionHWConfig", &tplg_build_hw_cfg_object, + &hwcfg_config}, + {"Base", "fe_dai", "dai", &tplg_build_fe_dai_object, &fe_dai_config}, + {"Base", "route", "SectionGraph", &tplg_build_dapm_route_object, NULL}, + {"Widget", "", "SectionWidget", &tplg_build_generic_object, &widget_config}, + {"Control", "mixer", "SectionControlMixer", &tplg_build_mixer_control, + &mixer_control_config}, + {"Control", "bytes", "SectionControlBytes", &tplg_build_bytes_control, + &bytes_control_config}, + {"Dai", "", "SectionBE", &tplg_build_generic_object, &be_dai_config}, + {"PCM", "pcm", "SectionPCM", &tplg_build_generic_object, &pcm_config}, + {"PCM", "pcm_caps", "SectionPCMCapabilities", &tplg_build_pcm_caps_object, + &pcm_caps_config}, +}; + +static const struct build_function_map *tplg_object_get_map(struct tplg_pre_processor *tplg_pp, + snd_config_t *obj) +{ + snd_config_iterator_t first; + snd_config_t *class; + const char *class_type, *class_name; + unsigned int i; + + first = snd_config_iterator_first(obj); + class = snd_config_iterator_entry(first); + + if (snd_config_get_id(class, &class_name) < 0) + return NULL; + + if (snd_config_get_id(obj, &class_type) < 0) + return NULL; + + for (i = 0; i < ARRAY_SIZE(object_build_map); i++) { + if (!strcmp(class_type, "Widget") && + !strcmp(object_build_map[i].class_type, "Widget")) + return &object_build_map[i]; + + if (!strcmp(class_type, "Dai") && + !strcmp(object_build_map[i].class_type, "Dai")) + return &object_build_map[i]; + + /* for other type objects, also match the object class_name */ + if (!strcmp(class_type, object_build_map[i].class_type) && + !strcmp(object_build_map[i].class_name, class_name)) + return &object_build_map[i]; + } + + return NULL; +} + +/* search for section name based on class type and name and return the config in output_cfg */ +snd_config_t *tplg_object_get_section(struct tplg_pre_processor *tplg_pp, snd_config_t *class) +{ + const struct build_function_map *map; + snd_config_t *cfg = NULL; + int ret; + + map = tplg_object_get_map(tplg_pp, class); + if (!map) + return NULL; + + ret = snd_config_search(tplg_pp->output_cfg, map->section_name, &cfg); + if (ret < 0) + SNDERR("Section config for %s not found\n", map->section_name); + + return cfg; +} + +/* return 1 if attribute not found in search_config, 0 on success and negative value on error */ +static int tplg_object_copy_and_add_param(struct tplg_pre_processor *tplg_pp, + snd_config_t *obj, + snd_config_t *attr_cfg, + snd_config_t *search_config) +{ + snd_config_t *attr, *new; + const char *id, *search_id; + int ret; + + if (snd_config_get_id(attr_cfg, &id) < 0) + return 0; + + if (snd_config_get_id(search_config, &search_id) < 0) + return 0; + + /* copy object value */ + ret = snd_config_search(search_config, id, &attr); + if (ret < 0) + return 1; + + ret = snd_config_copy(&new, attr); + if (ret < 0) { + SNDERR("error copying attribute '%s' value from %s\n", id, search_id); + return ret; + } + + ret = snd_config_add(obj, new); + if (ret < 0) { + snd_config_delete(new); + SNDERR("error adding attribute '%s' value to %s\n", id, search_id); + } + + return ret; +} + +/* + * Attribute values for an object can be set in one of the following in order of + * precedence: + * 1. Value set in object instance + * 2. Default value set in the object's class definition + * 3. Inherited value from the parent object + * 4. Value set in the object instance embedded in the parent object + * 5. Value set in the object instance embedded in the parent class definition + */ +static int tplg_object_update(struct tplg_pre_processor *tplg_pp, snd_config_t *obj, + snd_config_t *parent) +{ + snd_config_iterator_t i, next; + snd_config_t *n, *cfg, *args; + snd_config_t *obj_cfg, *class_cfg, *parent_obj; + const char *obj_id, *class_name, *class_type; + int ret; + + class_cfg = tplg_class_lookup(tplg_pp, obj); + if (!class_cfg) + return -EINVAL; + + /* find config for class attributes */ + ret = snd_config_search(class_cfg, "DefineAttribute", &args); + if (ret < 0) + return 0; + + if (snd_config_get_id(obj, &class_type) < 0) + return 0; + + if (snd_config_get_id(class_cfg, &class_name) < 0) + return 0; + + /* get obj cfg */ + obj_cfg = tplg_object_get_instance_config(tplg_pp, obj); + if (snd_config_get_id(obj_cfg, &obj_id) < 0) + return 0; + + /* copy and add attributes */ + snd_config_for_each(i, next, args) { + snd_config_t *attr; + const char *id; + + n = snd_config_iterator_entry(i); + if (snd_config_get_id(n, &id) < 0) + continue; + + if (tplg_class_is_attribute_unique(id, class_cfg)) + continue; + + if (tplg_class_is_attribute_immutable(id, class_cfg)) + goto class; + + /* check if attribute value is set in the object */ + ret = snd_config_search(obj_cfg, id, &attr); + if (ret < 0) + goto class; + goto validate; +class: + /* search for attributes value in class */ + ret = tplg_object_copy_and_add_param(tplg_pp, obj_cfg, n, class_cfg); + if (ret == 1) { + if (tplg_class_is_attribute_immutable(id, class_cfg)) { + SNDERR("Immutable attribute %s not set in class %s\n", + id, class_name); + return -EINVAL; + } + goto parent; + } + else if (ret < 0) + return ret; + goto validate; +parent: + /* search for attribute value in parent */ + if (!parent) + goto parent_object; + + /* get parent obj cfg */ + parent_obj = tplg_object_get_instance_config(tplg_pp, parent); + if (!parent_obj) + goto parent_object; + + ret = tplg_object_copy_and_add_param(tplg_pp, obj_cfg, n, parent_obj); + if (ret == 1) + goto parent_object; + else if (ret < 0) + return ret; + goto validate; +parent_object: + if (!parent) + goto parent_class; + + cfg = tplg_object_lookup_in_config(tplg_pp, parent_obj, class_type, + class_name, obj_id); + if (!cfg) + goto parent_class; + + ret = tplg_object_copy_and_add_param(tplg_pp, obj_cfg, n, cfg); + if (ret == 1) + goto parent_class; + else if (ret < 0) + return ret; + goto validate; +parent_class: + if (!parent) + goto check; + + cfg = tplg_class_lookup(tplg_pp, parent); + if (!cfg) + return -EINVAL; + + cfg = tplg_object_lookup_in_config(tplg_pp, cfg, class_type, + class_name, obj_id); + if (!cfg) + goto check; + + ret = tplg_object_copy_and_add_param(tplg_pp, obj_cfg, n, cfg); + if (ret == 1) + goto check; + else if (ret < 0) + return ret; + goto validate; +check: + if (tplg_class_is_attribute_mandatory(id, class_cfg)) { + SNDERR("Mandatory attribute %s not set for class %s\n", id, class_name); + return -EINVAL; + } + continue; +validate: + if (!tplg_object_is_attribute_valid(tplg_pp, n, obj_cfg)) + return -EINVAL; + } + + return 0; +} + +static int tplg_object_pre_process_children(struct tplg_pre_processor *tplg_pp, + snd_config_t *parent, snd_config_t *cfg) +{ + snd_config_iterator_t i, next; + snd_config_t *children, *n; + int ret; + + ret = snd_config_search(cfg, "Object", &children); + if (ret < 0) + return 0; + + /* create all embedded objects */ + snd_config_for_each(i, next, children) { + const char *id; + + n = snd_config_iterator_entry(i); + if (snd_config_get_id(n, &id) < 0) + continue; + + ret = tplg_pre_process_objects(tplg_pp, n, parent); + if (ret < 0) + return ret; + } + + return 0; +} + +static int tplg_construct_object_name(struct tplg_pre_processor *tplg_pp, snd_config_t *obj, + snd_config_t *class_cfg) +{ + snd_config_iterator_t i, next; + snd_config_t *args, *n; + const char *id, *class_id, *obj_id, *s; + char *new_name; + int ret; + + /* find config for class constructor attributes. Nothing to do if not defined */ + ret = snd_config_search(class_cfg, "attributes.constructor", &args); + if (ret < 0) + return 0; + + /* set class name as the name prefix for the object */ + snd_config_get_id(obj, &obj_id); + snd_config_get_id(class_cfg, &class_id); + new_name = strdup(class_id); + if (!new_name) + return -ENOMEM; + + /* iterate through all class arguments and set object name */ + snd_config_for_each(i, next, args) { + snd_config_t *arg; + char *arg_value, *temp; + + n = snd_config_iterator_entry(i); + + if (snd_config_get_id(n, &id) < 0) { + SNDERR("Invalid ID for constructor argument\n"); + ret = -EINVAL; + goto err; + } + + if (snd_config_get_string(n, &s) < 0) { + SNDERR("Invalid value for constructor argument\n"); + ret = -EINVAL; + goto err; + } + + /* find and replace with value set in object */ + ret = snd_config_search(obj, s, &arg); + if (ret < 0) { + SNDERR("Argument %s not set for object '%s.%s'\n", s, class_id, obj_id); + ret = -ENOENT; + goto err; + } + + /* concat arg value to object name. arg types must be either integer or string */ + switch (snd_config_get_type(arg)) { + case SND_CONFIG_TYPE_INTEGER: + { + long v; + ret = snd_config_get_integer(arg, &v); + assert(ret >= 0); + + arg_value = tplg_snprintf("%ld", v); + if (!arg_value) { + ret = -ENOMEM; + goto err; + } + break; + } + case SND_CONFIG_TYPE_STRING: + { + const char *s; + + ret = snd_config_get_string(arg, &s); + assert(ret >= 0); + + arg_value = strdup(s); + if (!arg_value) { + ret = -ENOMEM; + goto err; + } + break; + } + default: + SNDERR("Argument '%s' in object '%s.%s' is not an integer or a string\n", + s, class_id, obj_id); + return -EINVAL; + } + + /* alloc and concat arg value to the name */ + temp = tplg_snprintf("%s.%s", new_name, arg_value); + if (!temp) { + ret = -ENOMEM; + goto err; + } + free(new_name); + new_name = temp; + free(arg_value); + } + + ret = snd_config_set_id(obj, new_name); +err: + free(new_name); + return ret; +} + +/* set the attribute value by type */ +static int tplg_set_attribute_value(snd_config_t *attr, const char *value) +{ + int err; + snd_config_type_t type = snd_config_get_type(attr); + + switch (type) { + case SND_CONFIG_TYPE_INTEGER: + { + long v; + + v = strtol(value, NULL, 10); + err = snd_config_set_integer(attr, v); + assert(err >= 0); + break; + } + case SND_CONFIG_TYPE_INTEGER64: + { + long long v; + + v = strtoll(value, NULL, 10); + err = snd_config_set_integer64(attr, v); + assert(err >= 0); + break; + } + case SND_CONFIG_TYPE_STRING: + { + err = snd_config_set_string(attr, value); + assert(err >= 0); + break; + } + default: + return -EINVAL; + } + + return 0; +} + + +/* + * Find the unique attribute in the class definition and set its value and type. + * Only string or integer types are allowed for unique values. + */ +static int tplg_object_set_unique_attribute(struct tplg_pre_processor *tplg_pp, + snd_config_t *obj, snd_config_t *class_cfg, + const char *id) +{ + snd_config_t *unique_attr, *new; + const char *unique_name, *class_id; + int ret; + + if (snd_config_get_id(class_cfg, &class_id) < 0) + return 0; + + /* find config for class unique attribute */ + unique_name = tplg_class_get_unique_attribute_name(tplg_pp, class_cfg); + if (!unique_name) + return -ENOENT; + + /* find the unique attribute definition in the class */ + unique_attr = tplg_class_find_attribute_by_name(tplg_pp, class_cfg, unique_name); + if (!unique_attr) + return -ENOENT; + + /* override value if unique attribute is set in the object instance */ + ret = snd_config_search(obj, unique_name, &new); + if (ret < 0) { + ret = snd_config_make(&new, unique_name, + tplg_class_get_attribute_type(tplg_pp, unique_attr)); + if (ret < 0) { + SNDERR("error creating new attribute cfg for object %s\n", id); + return ret; + } + ret = snd_config_add(obj, new); + if (ret < 0) { + SNDERR("error adding new attribute cfg for object %s\n", id); + return ret; + } + } + + ret = tplg_set_attribute_value(new, id); + if (ret < 0) { + SNDERR("error setting unique attribute cfg for object %s\n", id); + return ret; + } + + return ret; +} + +/* + * Helper function to get object instance config which is 2 nodes down from class_type config. + * ex: Get the pointer to the config node with ID "0" from the input config Widget.pga.0 {} + */ +snd_config_t *tplg_object_get_instance_config(struct tplg_pre_processor *tplg_pp, + snd_config_t *class_type) +{ + snd_config_iterator_t first; + snd_config_t *cfg; + + first = snd_config_iterator_first(class_type); + cfg = snd_config_iterator_entry(first); + first = snd_config_iterator_first(cfg); + return snd_config_iterator_entry(first); +} + +/* build object config and its child objects recursively */ +static int tplg_build_object(struct tplg_pre_processor *tplg_pp, snd_config_t *new_obj, + snd_config_t *parent) +{ + snd_config_t *obj_local, *class_cfg; + const struct build_function_map *map; + build_func builder; + const char *id, *class_id; + int ret; + + obj_local = tplg_object_get_instance_config(tplg_pp, new_obj); + if (!obj_local) + return -EINVAL; + + class_cfg = tplg_class_lookup(tplg_pp, new_obj); + if (!class_cfg) + return -EINVAL; + + if (snd_config_get_id(obj_local, &id) < 0) + return 0; + + if (snd_config_get_id(class_cfg, &class_id) < 0) + return 0; + + /* set unique attribute value */ + ret = tplg_object_set_unique_attribute(tplg_pp, obj_local, class_cfg, id); + if (ret < 0) { + SNDERR("error setting unique attribute value for '%s.%s'\n", class_id, id); + return ret; + } + + /* update object attributes and validate them */ + ret = tplg_object_update(tplg_pp, new_obj, parent); + if (ret < 0) { + SNDERR("Failed to update attributes for object '%s.%s'\n", class_id, id); + return ret; + } + + /* construct object name using class constructor */ + ret = tplg_construct_object_name(tplg_pp, obj_local, class_cfg); + if (ret < 0) { + SNDERR("Failed to construct object name for %s\n", id); + return ret; + } + + /* skip object if not supported and pre-process its child objects */ + map = tplg_object_get_map(tplg_pp, new_obj); + if (!map) + goto child; + + /* build the object and save the sections to the output config */ + builder = map->builder; + ret = builder(tplg_pp, new_obj, parent); + if (ret < 0) + return ret; + +child: + /* create child objects in the object instance */ + ret = tplg_object_pre_process_children(tplg_pp, new_obj, obj_local); + if (ret < 0) { + SNDERR("error processing child objects in object %s\n", id); + return ret; + } + + /* create child objects in the object's class definition */ + ret = tplg_object_pre_process_children(tplg_pp, new_obj, class_cfg); + if (ret < 0) + SNDERR("error processing child objects in class %s\n", class_id); + + return ret; +} + +/* create top-level topology objects */ +int tplg_pre_process_objects(struct tplg_pre_processor *tplg_pp, snd_config_t *cfg, + snd_config_t *parent) +{ + snd_config_iterator_t i, next, i2, next2; + snd_config_t *n, *n2, *_obj_type, *_obj_class, *_obj; + const char *id, *class_type, *class_name; + int ret; + + if (snd_config_get_id(cfg, &class_type) < 0) + return 0; + + /* create all objects of the same type and class */ + snd_config_for_each(i, next, cfg) { + n = snd_config_iterator_entry(i); + if (snd_config_get_id(n, &class_name) < 0) + continue; + snd_config_for_each(i2, next2, n) { + n2 = snd_config_iterator_entry(i2); + if (snd_config_get_id(n2, &id) < 0) { + SNDERR("Invalid id for object\n"); + return -EINVAL; + } + + /* create a temp config for object with class type as the root node */ + ret = snd_config_make(&_obj_type, class_type, SND_CONFIG_TYPE_COMPOUND); + if (ret < 0) + return ret; + + ret = snd_config_make(&_obj_class, class_name, SND_CONFIG_TYPE_COMPOUND); + if (ret < 0) + goto err; + + ret = snd_config_add(_obj_type, _obj_class); + if (ret < 0) { + snd_config_delete(_obj_class); + goto err; + } + + ret = snd_config_copy(&_obj, n2); + if (ret < 0) + goto err; + + ret = snd_config_add(_obj_class, _obj); + if (ret < 0) { + snd_config_delete(_obj); + goto err; + } + + /* Build the object now */ + ret = tplg_build_object(tplg_pp, _obj_type, parent); + if (ret < 0) + SNDERR("Error building object %s.%s.%s\n", + class_type, class_name, id); +err: + snd_config_delete(_obj_type); + if (ret < 0) + return ret; + } + } + + return 0; +} diff --git a/topology/pre-processor.c b/topology/pre-processor.c new file mode 100644 index 000000000..0458c3cce --- /dev/null +++ b/topology/pre-processor.c @@ -0,0 +1,271 @@ +/* + Copyright(c) 2021 Intel Corporation + All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 of the GNU General Public License as + published by the Free Software Foundation. + + 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 + 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., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + The full GNU General Public License is included in this distribution + in the file called LICENSE.GPL. +*/ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include "gettext.h" +#include "topology.h" +#include "pre-processor.h" + +/* + * Helper function to find config by id. + * Topology2.0 object names are constructed with attribute values separated by '.'. + * So snd_config_search() cannot be used as it interprets the '.' as the node separator. + */ +snd_config_t *tplg_find_config(snd_config_t *config, const char *name) +{ + snd_config_iterator_t i, next; + snd_config_t *n; + const char *id; + + snd_config_for_each(i, next, config) { + n = snd_config_iterator_entry(i); + if (snd_config_get_id(n, &id) < 0) + continue; + + if (!strcmp(id, name)) + return n; + } + + return NULL; +} + +/* make a new config and add it to parent */ +int tplg_config_make_add(snd_config_t **config, const char *id, snd_config_type_t type, + snd_config_t *parent) +{ + int ret; + + ret = snd_config_make(config, id, type); + if (ret < 0) + return ret; + + ret = snd_config_add(parent, *config); + if (ret < 0) + snd_config_delete(*config); + + return ret; +} + +/* + * The pre-processor will need to concat multiple strings separate by '.' to construct the object + * name and search for configs with ID's separated by '.'. + * This function helps concat input strings in the specified input format + */ +char *tplg_snprintf(char *fmt, ...) +{ + char *string; + int len = 1; + + va_list va; + + va_start(va, fmt); + len += vsnprintf(NULL, 0, fmt, va); + va_end(va); + + string = calloc(1, len); + if (!string) + return NULL; + + va_start(va, fmt); + vsnprintf(string, len, fmt, va); + va_end(va); + + return string; +} + +#ifdef TPLG_DEBUG +void tplg_pp_debug(char *fmt, ...) +{ + char msg[DEBUG_MAX_LENGTH]; + va_list va; + + va_start(va, fmt); + vsnprintf(msg, DEBUG_MAX_LENGTH, fmt, va); + va_end(va); + + fprintf(stdout, "%s\n", msg); +} + +void tplg_pp_config_debug(struct tplg_pre_processor *tplg_pp, snd_config_t *cfg) +{ + snd_config_save(cfg, tplg_pp->dbg_output); +} +#else +void tplg_pp_debug(char *fmt, ...) {} +void tplg_pp_config_debug(struct tplg_pre_processor *tplg_pp, snd_config_t *cfg){} +#endif + +static int pre_process_config(struct tplg_pre_processor *tplg_pp, snd_config_t *cfg) +{ + snd_config_iterator_t i, next, i2, next2; + snd_config_t *n, *n2; + const char *id; + int err; + + if (snd_config_get_type(cfg) != SND_CONFIG_TYPE_COMPOUND) { + fprintf(stderr, "compound type expected at top level"); + return -EINVAL; + } + + /* parse topology objects */ + 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, "Object")) + continue; + + if (snd_config_get_type(n) != SND_CONFIG_TYPE_COMPOUND) { + fprintf(stderr, "compound type expected for %s", id); + return -EINVAL; + } + + snd_config_for_each(i2, next2, n) { + n2 = snd_config_iterator_entry(i2); + + if (snd_config_get_id(n, &id) < 0) + continue; + + if (snd_config_get_type(n2) != SND_CONFIG_TYPE_COMPOUND) { + fprintf(stderr, "compound type expected for %s", id); + return -EINVAL; + } + + /* pre-process Object instance. Top-level object have no parent */ + err = tplg_pre_process_objects(tplg_pp, n2, NULL); + if (err < 0) + return err; + } + } + + return 0; +} + +void free_pre_preprocessor(struct tplg_pre_processor *tplg_pp) +{ + snd_output_close(tplg_pp->output); + snd_output_close(tplg_pp->dbg_output); + snd_config_delete(tplg_pp->output_cfg); + free(tplg_pp); +} + +int init_pre_precessor(struct tplg_pre_processor **tplg_pp, snd_output_type_t type, + const char *output_file) +{ + struct tplg_pre_processor *_tplg_pp; + int ret; + + _tplg_pp = calloc(1, sizeof(struct tplg_pre_processor)); + if (!_tplg_pp) + ret = -ENOMEM; + + *tplg_pp = _tplg_pp; + + /* create output top-level config node */ + ret = snd_config_top(&_tplg_pp->output_cfg); + if (ret < 0) + goto err; + + /* open output based on type */ + if (type == SND_OUTPUT_STDIO) { + ret = snd_output_stdio_open(&_tplg_pp->output, output_file, "w"); + if (ret < 0) { + fprintf(stderr, "failed to open file output\n"); + goto open_err; + } + } else { + ret = snd_output_buffer_open(&_tplg_pp->output); + if (ret < 0) { + fprintf(stderr, "failed to open buffer output\n"); + goto open_err; + } + } + + /* debug output */ + ret = snd_output_stdio_attach(&_tplg_pp->dbg_output, stdout, 0); + if (ret < 0) { + fprintf(stderr, "failed to open stdout output\n"); + goto out_close; + } + + return 0; +out_close: + snd_output_close(_tplg_pp->output); +open_err: + snd_config_delete(_tplg_pp->output_cfg); +err: + free(_tplg_pp); + return ret; +} + +int pre_process(struct tplg_pre_processor *tplg_pp, char *config, size_t config_size) +{ + snd_input_t *in; + snd_config_t *top; + int err; + + /* create input buffer */ + err = snd_input_buffer_open(&in, config, config_size); + if (err < 0) { + fprintf(stderr, "Unable to open input buffer\n"); + return err; + } + + /* create top-level config node */ + err = snd_config_top(&top); + if (err < 0) + goto input_close; + + /* load config */ + err = snd_config_load(top, in); + if (err < 0) { + fprintf(stderr, "Unable not load configuration\n"); + goto err; + } + + tplg_pp->input_cfg = top; + + err = pre_process_config(tplg_pp, top); + if (err < 0) { + fprintf(stderr, "Unable to pre-process configuration\n"); + goto err; + } + + /* save config to output */ + err = snd_config_save(tplg_pp->output_cfg, tplg_pp->output); + if (err < 0) + fprintf(stderr, "failed to save pre-processed output file\n"); + +err: + snd_config_delete(top); +input_close: + snd_input_close(in); + + return err; +} diff --git a/topology/pre-processor.h b/topology/pre-processor.h new file mode 100644 index 000000000..390037212 --- /dev/null +++ b/topology/pre-processor.h @@ -0,0 +1,112 @@ +/* + * 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. + */ + +#ifndef __PRE_PROCESSOR_H +#define __PRE_PROCESSOR_H + +#include +#include +#include +#include "topology.h" + +#define DEBUG_MAX_LENGTH 256 +#define ARRAY_SIZE(a) (sizeof (a) / sizeof (a)[0]) + +#define MAX_CONFIGS_IN_TEMPLATE 32 +struct config_template_items { + char *int_config_ids[MAX_CONFIGS_IN_TEMPLATE]; + char *string_config_ids[MAX_CONFIGS_IN_TEMPLATE]; + char *compound_config_ids[MAX_CONFIGS_IN_TEMPLATE]; +}; + +typedef int (*build_func)(struct tplg_pre_processor *tplg_pp, snd_config_t *obj, + snd_config_t *parent); + +struct build_function_map { + char *class_type; + char *class_name; + char *section_name; + build_func builder; + const struct config_template_items *template_items; +}; + +extern const struct build_function_map object_build_map[]; + +/* debug helpers */ +void tplg_pp_debug(char *fmt, ...); +void tplg_pp_config_debug(struct tplg_pre_processor *tplg_pp, snd_config_t *cfg); + +/* object build helpers */ +int tplg_build_object_from_template(struct tplg_pre_processor *tplg_pp, snd_config_t *obj_cfg, + snd_config_t **wtop, snd_config_t *top_config, + bool skip_name); +int tplg_build_tlv_object(struct tplg_pre_processor *tplg_pp, snd_config_t *obj_cfg, + snd_config_t *parent); +int tplg_build_scale_object(struct tplg_pre_processor *tplg_pp, snd_config_t *obj_cfg, + snd_config_t *parent); +int tplg_build_ops_object(struct tplg_pre_processor *tplg_pp, snd_config_t *obj_cfg, + snd_config_t *parent); +int tplg_build_channel_object(struct tplg_pre_processor *tplg_pp, snd_config_t *obj_cfg, + snd_config_t *parent); +int tplg_build_mixer_control(struct tplg_pre_processor *tplg_pp, snd_config_t *obj_cfg, + snd_config_t *parent); +int tplg_build_bytes_control(struct tplg_pre_processor *tplg_pp, snd_config_t *obj_cfg, + snd_config_t *parent); +int tplg_build_dapm_route_object(struct tplg_pre_processor *tplg_pp, snd_config_t *obj_cfg, + snd_config_t *parent); +int tplg_build_hw_cfg_object(struct tplg_pre_processor *tplg_pp, + snd_config_t *obj_cfg, snd_config_t *parent); +int tplg_build_fe_dai_object(struct tplg_pre_processor *tplg_pp, snd_config_t *obj_cfg, + snd_config_t *parent); +int tplg_build_base_object(struct tplg_pre_processor *tplg_pp, snd_config_t *obj_cfg, + snd_config_t *parent, bool skip_name); +int tplg_build_pcm_caps_object(struct tplg_pre_processor *tplg_pp, + snd_config_t *obj_cfg, snd_config_t *parent); +int tplg_parent_update(struct tplg_pre_processor *tplg_pp, snd_config_t *parent, + const char *section_name, const char *item_name); + +/* object helpers */ +int tplg_pre_process_objects(struct tplg_pre_processor *tplg_pp, snd_config_t *cfg, + snd_config_t *parent); +snd_config_t *tplg_object_get_instance_config(struct tplg_pre_processor *tplg_pp, + snd_config_t *class_type); +const char *tplg_object_get_name(struct tplg_pre_processor *tplg_pp, + snd_config_t *object); +snd_config_t *tplg_object_get_section(struct tplg_pre_processor *tplg_pp, snd_config_t *class); + +/* class helpers */ +snd_config_t *tplg_class_lookup(struct tplg_pre_processor *tplg_pp, snd_config_t *cfg); +snd_config_t *tplg_class_find_attribute_by_name(struct tplg_pre_processor *tplg_pp, + snd_config_t *class, const char *name); +bool tplg_class_is_attribute_mandatory(const char *attr, snd_config_t *class_cfg); +bool tplg_class_is_attribute_immutable(const char *attr, snd_config_t *class_cfg); +bool tplg_class_is_attribute_unique(const char *attr, snd_config_t *class_cfg); +const char *tplg_class_get_unique_attribute_name(struct tplg_pre_processor *tplg_pp, + snd_config_t *class); +snd_config_type_t tplg_class_get_attribute_type(struct tplg_pre_processor *tplg_pp, + snd_config_t *attr); +const char *tplg_class_get_attribute_token_ref(struct tplg_pre_processor *tplg_pp, + snd_config_t *class, const char *attr_name); +long tplg_class_attribute_valid_tuple_value(struct tplg_pre_processor *tplg_pp, + snd_config_t *class, snd_config_t *attr); + +/* config helpers */ +snd_config_t *tplg_find_config(snd_config_t *config, const char *name); +int tplg_config_make_add(snd_config_t **config, const char *id, snd_config_type_t type, + snd_config_t *parent); + +char *tplg_snprintf(char *fmt, ...); +#endif diff --git a/topology/topology.c b/topology/topology.c index c2f094324..e0c7e7c88 100644 --- a/topology/topology.c +++ b/topology/topology.c @@ -20,6 +20,7 @@ in the file called LICENSE.GPL. */ +#include #include #include #include @@ -35,6 +36,9 @@ #include #include "gettext.h" #include "version.h" +#include "topology.h" + +bool pre_process_config = false; static snd_output_t *log; @@ -45,6 +49,8 @@ _("Usage: %s [OPTIONS]...\n" "\n" "-h, --help help\n" "-c, --compile=FILE compile configuration file\n" +"-p, --pre-process pre-process Topology2.0 configuration file before compilation\n" +"-P, --pre-process=FILE pre-process Topology2.0 configuration file\n" "-d, --decode=FILE decode binary topology file\n" "-n, --normalize=FILE normalize configuration file\n" "-u, --dump=FILE dump (reparse) configuration file\n" @@ -227,8 +233,38 @@ static int dump(const char *source_file, const char *output_file, int cflags, in return err; } +/* Convert Topology2.0 conf to the existing conf syntax */ +static int pre_process_conf(const char *source_file, const char *output_file) +{ + struct tplg_pre_processor *tplg_pp; + size_t config_size; + char *config; + int err; + + err = load(source_file, (void **)&config, &config_size); + if (err) + return err; + + /* init pre-processor */ + err = init_pre_precessor(&tplg_pp, SND_OUTPUT_STDIO, output_file); + if (err < 0) { + fprintf(stderr, _("failed to init pre-processor for Topology2.0\n")); + free(config); + return err; + } + + /* pre-process conf file */ + err = pre_process(tplg_pp, config, config_size); + + /* free pre-processor */ + free_pre_preprocessor(tplg_pp); + free(config); + return err; +} + static int compile(const char *source_file, const char *output_file, int cflags) { + struct tplg_pre_processor *tplg_pp = NULL; snd_tplg_t *tplg; char *config; void *bin; @@ -238,7 +274,32 @@ static int compile(const char *source_file, const char *output_file, int cflags) err = load(source_file, (void **)&config, &config_size); if (err) return err; - err = load_topology(&tplg, config, config_size, cflags); + + /* pre-process before compiling */ + if (pre_process_config) { + char *pconfig; + size_t size; + + /* init pre-processor */ + init_pre_precessor(&tplg_pp, SND_OUTPUT_BUFFER, NULL); + + /* pre-process conf file */ + err = pre_process(tplg_pp, config, config_size); + if (err) { + free_pre_preprocessor(tplg_pp); + free(config); + return err; + } + + /* load topology */ + size = snd_output_buffer_string(tplg_pp->output, &pconfig); + err = load_topology(&tplg, pconfig, size, cflags); + + /* free pre-processor */ + free_pre_preprocessor(tplg_pp); + } else { + err = load_topology(&tplg, config, config_size, cflags); + } free(config); if (err) return err; @@ -292,11 +353,12 @@ static int decode(const char *source_file, const char *output_file, int main(int argc, char *argv[]) { - static const char short_options[] = "hc:d:n:u:v:o:sgxzV"; + static const char short_options[] = "hc:d:n:u:v:o:pP:sgxzV"; static const struct option long_options[] = { {"help", 0, NULL, 'h'}, {"verbose", 1, NULL, 'v'}, {"compile", 1, NULL, 'c'}, + {"pre-process", 1, NULL, 'p'}, {"decode", 1, NULL, 'd'}, {"normalize", 1, NULL, 'n'}, {"dump", 1, NULL, 'u'}, @@ -336,7 +398,7 @@ int main(int argc, char *argv[]) case 'n': case 'u': if (source_file) { - fprintf(stderr, _("Cannot combine operations (compile, normalize, dump)\n")); + fprintf(stderr, _("Cannot combine operations (compile, normalize, pre-process, dump)\n")); return 1; } source_file = optarg; @@ -348,6 +410,13 @@ int main(int argc, char *argv[]) case 's': sflags |= SND_TPLG_SAVE_SORT; break; + case 'P': + op = 'P'; + source_file = optarg; + break; + case 'p': + pre_process_config = true; + break; case 'g': sflags |= SND_TPLG_SAVE_GROUPS; break; @@ -384,6 +453,9 @@ int main(int argc, char *argv[]) case 'd': err = decode(source_file, output_file, cflags, dflags, sflags); break; + case 'P': + err = pre_process_conf(source_file, output_file); + break; default: err = dump(source_file, output_file, cflags, sflags); break; diff --git a/topology/topology.h b/topology/topology.h new file mode 100644 index 000000000..494612b2d --- /dev/null +++ b/topology/topology.h @@ -0,0 +1,34 @@ +/* + * 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. + */ + +#ifndef __TOPOLOGY_H +#define __TOPOLOGY_H + +#include + +/* pre_processor */ +struct tplg_pre_processor { + snd_config_t *input_cfg; + snd_config_t *output_cfg; + snd_output_t *output; + snd_output_t *dbg_output; +}; + +int pre_process(struct tplg_pre_processor *tplg_pp, char *config, size_t config_size); +int init_pre_precessor(struct tplg_pre_processor **tplg_pp, snd_output_type_t type, + const char *output_file); +void free_pre_preprocessor(struct tplg_pre_processor *tplg_pp); +#endif