From b0c4ed248e9f543afe671e253fe1bb285df06477 Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Mon, 11 Jan 2021 10:40:53 +0100 Subject: [PATCH 01/88] alsamixer: remove dead fcn widget_handle_key() in widget.c Signed-off-by: Jaroslav Kysela --- alsamixer/widget.c | 7 ------- 1 file changed, 7 deletions(-) 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); From e165d3413e911b32441a0311d0ec0f280748e22b Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Mon, 11 Jan 2021 10:41:32 +0100 Subject: [PATCH 02/88] alsamixer: remove unused variable y in display_scroll_indicators() Signed-off-by: Jaroslav Kysela --- alsamixer/mixer_display.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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) From 19cc5daef42c84bdadbaa25d1c4e1da33eeae3cc Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Mon, 11 Jan 2021 10:44:38 +0100 Subject: [PATCH 03/88] alsamixer: fix shift in parse_words() Signed-off-by: Jaroslav Kysela --- alsamixer/configparser.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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; From b8a1e95773227e2b4942d2f67cc10f7d133d75ad Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Tue, 19 Jan 2021 12:36:28 +0100 Subject: [PATCH 04/88] aplay: fix the test position test for playback (avail > delay) The avail > delay condition is invalid only for capture, of course. Signed-off-by: Jaroslav Kysela --- aplay/aplay.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/aplay/aplay.c b/aplay/aplay.c index b75be6c55..9c827f468 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); From 8cd781be74473a99c055a87548885e6349f71d1a Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Tue, 26 Jan 2021 19:30:19 +0100 Subject: [PATCH 05/88] alsa-info.sh: add audio keyword to the dmesg filter Example: sof-audio-pci 0000:00:1f.3: SoundWire enabled on CannonLake+ platform, using SOF driver Signed-off-by: Jaroslav Kysela --- alsa-info/alsa-info.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/alsa-info/alsa-info.sh b/alsa-info/alsa-info.sh index 3871b97a2..2e5b9016c 100755 --- a/alsa-info/alsa-info.sh +++ b/alsa-info/alsa-info.sh @@ -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 } From c990f9a8ad2efebcf866045c219f194a8c67d064 Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Mon, 22 Feb 2021 10:53:12 +0100 Subject: [PATCH 06/88] alsa-info.sh: add card number to the ALSA module list section Previous output: !!Loaded ALSA modules !!------------------- snd_hda_intel snd_usb_audio New output: !!Loaded ALSA modules !!------------------- snd_hda_intel (card 0) snd_usb_audio (card 1) Signed-off-by: Jaroslav Kysela --- alsa-info/alsa-info.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/alsa-info/alsa-info.sh b/alsa-info/alsa-info.sh index 2e5b9016c..252a607ec 100755 --- a/alsa-info/alsa-info.sh +++ b/alsa-info/alsa-info.sh @@ -1,6 +1,6 @@ #!/bin/bash -SCRIPT_VERSION=0.4.65 +SCRIPT_VERSION=0.4.66 CHANGELOG="http://www.alsa-project.org/alsa-info.sh.changelog" ################################################################################# @@ -461,7 +461,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 From c1f8cc2f07d8e69db8f51d6af4563ed8a9ecf912 Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Mon, 22 Feb 2021 10:56:31 +0100 Subject: [PATCH 07/88] alsa-info.sh: add sysfs card info section It may be useful to dump the sysfs tree to gather more runtime information. Signed-off-by: Jaroslav Kysela --- alsa-info/alsa-info.sh | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/alsa-info/alsa-info.sh b/alsa-info/alsa-info.sh index 252a607ec..0044ee741 100755 --- a/alsa-info/alsa-info.sh +++ b/alsa-info/alsa-info.sh @@ -611,8 +611,7 @@ echo "" >> $FILE echo "" >> $FILE fi -if [ "$SNDOPTIONS" ] -then +if [ "$SNDOPTIONS" ]; then echo "!!Modprobe options (Sound related)" >> $FILE echo "!!--------------------------------" >> $FILE echo "" >> $FILE @@ -635,6 +634,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 From 17b4129e6c89d1a96d4d86dabea38389927e3cf4 Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Fri, 26 Feb 2021 19:28:03 +0100 Subject: [PATCH 08/88] alsactl: add 'clean' command to remove the application controls It is handy to remove all card controls created by applications. This change allows to remove those controls for all cards, selected card or selected card with a control id filter list like: alsactl clean 0 "name='PCM'" "name='Mic Phantom'" Signed-off-by: Jaroslav Kysela --- alsactl/Makefile.am | 2 +- alsactl/alsactl.1 | 2 + alsactl/alsactl.c | 5 + alsactl/alsactl.h | 1 + alsactl/clean.c | 228 ++++++++++++++++++++++++++++++++++++++++++++ configure.ac | 2 +- 6 files changed, 238 insertions(+), 2 deletions(-) create mode 100644 alsactl/clean.c 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..6fdc0999d 100644 --- a/alsactl/alsactl.1 +++ b/alsactl/alsactl.1 @@ -39,6 +39,8 @@ rescan, save_and_quit). \fImonitor\fP is for monitoring the events received from the given control device. +\fIclean\fP clean the controls created by applications. + If no soundcards are specified, setup for all cards will be saved, loaded or monitored. diff --git a/alsactl/alsactl.c b/alsactl/alsactl.c index 20ebac156..06ea70730 100644 --- a/alsactl/alsactl.c +++ b/alsactl/alsactl.c @@ -187,6 +187,7 @@ int main(int argc, char *argv[]) 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 */ @@ -346,6 +347,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; @@ -391,6 +394,8 @@ 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 { fprintf(stderr, "alsactl: Unknown command '%s'...\n", cmd); res = -ENODEV; diff --git a/alsactl/alsactl.h b/alsactl/alsactl.h index c47869570..74079ded1 100644 --- a/alsactl/alsactl.h +++ b/alsactl/alsactl.h @@ -40,6 +40,7 @@ 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); /* utils */ diff --git a/alsactl/clean.c b/alsactl/clean.c new file mode 100644 index 000000000..b74714f2d --- /dev/null +++ b/alsactl/clean.c @@ -0,0 +1,228 @@ +/* + * 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 +#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(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) +{ + int err; + + 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 = clean_controls(card, extra_args))) + 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 = clean_controls(cardno, extra_args))) { + goto out; + } + } +out: + return err; +} diff --git a/configure.ac b/configure.ac index 1f119c5ba..7005ccce5 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])]) From f67020abcff2d39d6759d4e77ad204d8851fba9c Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Fri, 5 Mar 2021 18:09:47 +0100 Subject: [PATCH 09/88] alsa-info.sh: bumb version to 0.5.0 Signed-off-by: Jaroslav Kysela --- alsa-info/alsa-info.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/alsa-info/alsa-info.sh b/alsa-info/alsa-info.sh index 0044ee741..cd81acf38 100755 --- a/alsa-info/alsa-info.sh +++ b/alsa-info/alsa-info.sh @@ -1,6 +1,6 @@ #!/bin/bash -SCRIPT_VERSION=0.4.66 +SCRIPT_VERSION=0.5.0 CHANGELOG="http://www.alsa-project.org/alsa-info.sh.changelog" ################################################################################# From 30809f395ec48c5607136545e22bc58651687dcd Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Fri, 5 Mar 2021 18:20:55 +0100 Subject: [PATCH 10/88] alsa-info.sh: add PipeWire daemon detection Signed-off-by: Jaroslav Kysela --- alsa-info/alsa-info.sh | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/alsa-info/alsa-info.sh b/alsa-info/alsa-info.sh index cd81acf38..0ef647862 100755 --- a/alsa-info/alsa-info.sh +++ b/alsa-info/alsa-info.sh @@ -425,6 +425,7 @@ 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) @@ -555,6 +556,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 From a4cff92223995529bcd8ac65f2b7039760f994c5 Mon Sep 17 00:00:00 2001 From: Bruno Vernay Date: Fri, 5 Mar 2021 21:10:28 +0100 Subject: [PATCH 11/88] alsa-info.sh: Use HTTPS instead of HTTP Signed-off-by: Bruno Vernay Signed-off-by: Jaroslav Kysela --- alsa-info/alsa-info.sh | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/alsa-info/alsa-info.sh b/alsa-info/alsa-info.sh index 0ef647862..c835ca9f5 100755 --- a/alsa-info/alsa-info.sh +++ b/alsa-info/alsa-info.sh @@ -1,7 +1,7 @@ #!/bin/bash SCRIPT_VERSION=0.5.0 -CHANGELOG="http://www.alsa-project.org/alsa-info.sh.changelog" +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 ]] @@ -815,7 +815,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)" @@ -844,28 +844,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 @@ -919,9 +919,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 @@ -964,7 +964,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. From c47e93fa7ef8c6197970fc2def2585734464f302 Mon Sep 17 00:00:00 2001 From: Matt Campbell Date: Sun, 14 Feb 2021 11:47:19 -0500 Subject: [PATCH 12/88] amixer: Expand on channel docs in man page Add missing channel params to the amixer man page. Also call out that the channel param must come before the value to take effect. signed-off-by: Matthew Campbell Signed-off-by: Jaroslav Kysela --- amixer/amixer.1 | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) 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. From bdb10da3aaa876f24effcf12c32829f84767f6ed Mon Sep 17 00:00:00 2001 From: bengan Date: Wed, 20 Jan 2021 12:13:44 +0100 Subject: [PATCH 13/88] alsa-info.sh: Add jack2 (jackdbus) detection BugLink: https://github.com/alsa-project/alsa-utils/pull/74 Signed-off-by: bengan Signed-off-by: Jaroslav Kysela --- alsa-info/alsa-info.sh | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/alsa-info/alsa-info.sh b/alsa-info/alsa-info.sh index c835ca9f5..01a910a43 100755 --- a/alsa-info/alsa-info.sh +++ b/alsa-info/alsa-info.sh @@ -429,6 +429,7 @@ 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) @@ -591,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 From c867aa8a84a7e9fbf7f9547a3462f8521cfc69b0 Mon Sep 17 00:00:00 2001 From: Ryan Burns Date: Sat, 6 Feb 2021 14:16:55 -0800 Subject: [PATCH 14/88] alsamixer: use background color instead of COLOR_BLACK BugLink: https://github.com/alsa-project/alsa-utils/pull/77 Signed-off-by: Ryan Burns Signed-off-by: Jaroslav Kysela --- alsamixer/colors.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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); From bccebc59a02e235f2688379853a490e6f4401eba Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Fri, 5 Mar 2021 21:29:29 +0100 Subject: [PATCH 15/88] aplay: fix the wrong pointer dereference in playbackv_go() BugLink: https://github.com/alsa-project/alsa-utils/issues/70 Signed-off-by: Jaroslav Kysela --- aplay/aplay.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aplay/aplay.c b/aplay/aplay.c index 9c827f468..9ebaae410 100644 --- a/aplay/aplay.c +++ b/aplay/aplay.c @@ -3343,7 +3343,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) { From ade71cf8b09c892908c6a0d1bd087cee8d3fb8be Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Fri, 5 Mar 2021 21:43:45 +0100 Subject: [PATCH 16/88] alsactl: fix some compiler warnings BugLink: https://github.com/alsa-project/alsa-utils/issues/17 Signed-off-by: Jaroslav Kysela --- alsactl/alsactl.c | 5 ++++- alsactl/utils.c | 12 ++++++------ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/alsactl/alsactl.c b/alsactl/alsactl.c index 06ea70730..a3a6bfad4 100644 --- a/alsactl/alsactl.c +++ b/alsactl/alsactl.c @@ -356,7 +356,10 @@ 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)); + goto out; + } } cmd = argv[optind]; diff --git a/alsactl/utils.c b/alsactl/utils.c index a83aa0ab1..f67421def 100644 --- a/alsactl/utils.c +++ b/alsactl/utils.c @@ -92,12 +92,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); } From f8ce4f1e3af8a14151e711c33c086e5b7429ef95 Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Fri, 5 Mar 2021 21:50:28 +0100 Subject: [PATCH 17/88] amidi, aseqnet: handle write errors BugLink: https://github.com/alsa-project/alsa-utils/issues/17 Signed-off-by: Jaroslav Kysela --- amidi/amidi.c | 7 +++++-- seq/aseqnet/aseqnet.c | 7 +++++-- 2 files changed, 10 insertions(+), 4 deletions(-) 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/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; } From e1551de8dd28c3a63f8d7c146952a8d2649ac9de Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Sat, 6 Mar 2021 08:37:08 +0100 Subject: [PATCH 18/88] axfer: test - add run-test-in-tmpdir.sh script BugLink: https://github.com/alsa-project/alsa-utils/issues/19 Signed-off-by: Jaroslav Kysela --- axfer/test/run-test-in-tmpdir.sh | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100755 axfer/test/run-test-in-tmpdir.sh diff --git a/axfer/test/run-test-in-tmpdir.sh b/axfer/test/run-test-in-tmpdir.sh new file mode 100755 index 000000000..e66fa7335 --- /dev/null +++ b/axfer/test/run-test-in-tmpdir.sh @@ -0,0 +1,19 @@ +#!/bin/sh + +bin="$1" + +test -z ${bin} && exit 90 +test ! -x ${bin} && exit 91 +test -z ${TMPDIR} && exit 92 +test ! -d ${TMPDIR} && exit 93 + +tmp_dir=$(mktemp -d ${TMPDIR}/container-test.XXXXX) +cur_dir=$(pwd) + +echo ${tmp_dir} +cd ${tmp_dir} +${cur_dir}/${bin} +retval=$? +cd ${cur_dir} +rm -rf ${tmp_dir} +exit $retval From 65453da000a03910f47010795bef45b4ad555f9f Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Sat, 6 Mar 2021 12:40:54 +0100 Subject: [PATCH 19/88] alsactl: honor ignore_nocards flag in init() BugLink: https://github.com/alsa-project/alsa-utils/issues/75 Signed-off-by: Jaroslav Kysela --- alsactl/init_parse.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/alsactl/init_parse.c b/alsactl/init_parse.c index 71348da37..7a6402a26 100644 --- a/alsactl/init_parse.c +++ b/alsactl/init_parse.c @@ -1758,6 +1758,8 @@ int init(const char *filename, int flags, const char *cardname) break; if (card < 0) { if (first) { + if (ignore_nocards) + return 0; error("No soundcards found..."); return -ENODEV; } From 09c04f89354bcc7138306379ce4e89b8b48517ae Mon Sep 17 00:00:00 2001 From: MichaIng Date: Sat, 6 Mar 2021 16:19:49 +0100 Subject: [PATCH 20/88] alsactl: init command now honors -g flag as well BugLink: https://github.com/alsa-project/alsa-utils/issues/75 Signed-off-by: MichaIng Signed-off-by: Jaroslav Kysela --- alsactl/alsactl.1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/alsactl/alsactl.1 b/alsactl/alsactl.1 index 6fdc0999d..1c9c9b9cb 100644 --- a/alsactl/alsactl.1 +++ b/alsactl/alsactl.1 @@ -83,7 +83,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 From c1474594dcb3fa741d00966630cb5770e4e504b0 Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Sun, 7 Mar 2021 19:58:33 +0100 Subject: [PATCH 21/88] alsactl: add dump-cfg and dump-state commands Signed-off-by: Jaroslav Kysela --- alsactl/alsactl.1 | 4 ++++ alsactl/alsactl.c | 51 ++++++++++++++++++++++++++++++++++++++++++-- alsactl/alsactl.h | 3 +++ alsactl/clean.c | 1 - alsactl/daemon.c | 1 - alsactl/init_parse.c | 1 - alsactl/state.c | 32 +++++---------------------- alsactl/utils.c | 44 ++++++++++++++++++++++++++++++++++++-- 8 files changed, 103 insertions(+), 34 deletions(-) diff --git a/alsactl/alsactl.1 b/alsactl/alsactl.1 index 1c9c9b9cb..5452ef599 100644 --- a/alsactl/alsactl.1 +++ b/alsactl/alsactl.1 @@ -41,6 +41,10 @@ control device. \fIclean\fP clean the controls created by applications. +\fIdump-state\fP dump the current state (all cards). + +\fIdump-cfg\fP dump the current configuration (all cards, hooks are evaluated). + If no soundcards are specified, setup for all cards will be saved, loaded or monitored. diff --git a/alsactl/alsactl.c b/alsactl/alsactl.c index a3a6bfad4..1d2ff3ed0 100644 --- a/alsactl/alsactl.c +++ b/alsactl/alsactl.c @@ -29,7 +29,6 @@ #include #include #include -#include #include "alsactl.h" #ifndef SYS_ASOUNDRC @@ -111,6 +110,8 @@ 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" }, +{ EMPCMD, "dump-state", "dump the state (for all cards)" }, +{ EMPCMD, "dump-cfg", "dump the configuration (expanded, for all cards)" }, { 0, NULL, NULL } }; @@ -139,7 +140,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 +155,48 @@ 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); + snd_config_update_free_global(); + 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 */ + snd_config_search_definition(top, "cards", "dummy", &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) @@ -399,6 +442,10 @@ int main(int argc, char *argv[]) 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 74079ded1..119e3b4b8 100644 --- a/alsactl/alsactl.h +++ b/alsactl/alsactl.h @@ -1,3 +1,5 @@ +#include + extern int debugflag; extern int force_restore; extern int ignore_nocards; @@ -28,6 +30,7 @@ void error_handler(const char *file, int line, const char *function, int err, co #define FLAG_UCM_DISABLED (1<<0) #define FLAG_UCM_DEFAULTS (1<<1) +int load_configuration(const char *file, snd_config_t **top, int *open_failed); int init(const char *file, int flags, const char *cardname); int init_ucm(int flags, int cardno); int state_lock(const char *file, int timeout); diff --git a/alsactl/clean.c b/alsactl/clean.c index b74714f2d..4808225a0 100644 --- a/alsactl/clean.c +++ b/alsactl/clean.c @@ -26,7 +26,6 @@ #include #include #include -#include #include "alsactl.h" static int clean_one_control(snd_ctl_t *handle, snd_ctl_elem_id_t *elem_id, 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 7a6402a26..e7b352c2a 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" diff --git a/alsactl/state.c b/alsactl/state.c index ea1d3bcaa..71e5465f9 100644 --- a/alsactl/state.c +++ b/alsactl/state.c @@ -27,7 +27,6 @@ #include #include #include -#include #include "alsactl.h" @@ -1648,38 +1647,17 @@ int save_state(const char *file, const char *cardname) int load_state(const char *file, const char *initfile, int initflags, const char *cardname, int do_init) { - int err, finalerr = 0; + int err, finalerr = 0, open_failed; snd_config_t *config; - snd_input_t *in; - int stdio, lock_fd = -EINVAL; - 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 { + + if (open_failed) { int card, first = 1; char cardname1[16]; - if (lock_fd >= 0) - state_unlock(lock_fd, file); error("Cannot open %s for reading: %s", file, snd_strerror(err)); finalerr = err; if (cardname) { diff --git a/alsactl/utils.c b/alsactl/utils.c index f67421def..ede7319c3 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) @@ -193,3 +191,45 @@ 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; + } +} From b137145a92ae4ef03c37520a59177587c20751ff Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Sun, 7 Mar 2021 20:00:15 +0100 Subject: [PATCH 22/88] alsactl: fix the compiler warning (uninitialized variable res) Signed-off-by: Jaroslav Kysela --- alsactl/alsactl.c | 1 + 1 file changed, 1 insertion(+) diff --git a/alsactl/alsactl.c b/alsactl/alsactl.c index 1d2ff3ed0..3a6f79d29 100644 --- a/alsactl/alsactl.c +++ b/alsactl/alsactl.c @@ -401,6 +401,7 @@ int main(int argc, char *argv[]) use_syslog = 1; if (daemon(0, 0)) { syslog(LOG_INFO, "alsactl " SND_UTIL_VERSION_STR " daemon cannot be started: %s", strerror(errno)); + res = EXIT_FAILURE; goto out; } } From c3f2344b7209463f88671af3a8316c5b97112ca4 Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Sun, 7 Mar 2021 20:18:12 +0100 Subject: [PATCH 23/88] alsactl: add 'clean' cmd to help, improve man page Signed-off-by: Jaroslav Kysela --- alsactl/alsactl.1 | 58 ++++++++++++++++++++++++++++++++++++----------- alsactl/alsactl.c | 1 + 2 files changed, 46 insertions(+), 13 deletions(-) diff --git a/alsactl/alsactl.1 b/alsactl/alsactl.1 index 5452ef599..0bd6c1bb9 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,37 +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. -\fIrdaemon\fP like \fIdaemon\fP but restore the sound state at first. +.SS rdaemon -\fIkill\fP notifies the daemon to do the specified operation (quit, +This command is like \fIdaemon\fP but restore the sound state at first. + +.SS kill + +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. -\fIclean\fP clean the controls created by applications. +.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 -\fIdump-state\fP dump the current state (all cards). +This command dumps the current state (all cards) to stdout. -\fIdump-cfg\fP dump the current configuration (all cards, hooks are evaluated). +.SS dump-cfg -If no soundcards are specified, setup for all cards will be saved, -loaded or monitored. +This command dumps the current configuration (all cards) to stdout. +Note that the configuration hooks are evaluated. .SH OPTIONS diff --git a/alsactl/alsactl.c b/alsactl/alsactl.c index 3a6f79d29..446365aed 100644 --- a/alsactl/alsactl.c +++ b/alsactl/alsactl.c @@ -110,6 +110,7 @@ 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 } From 7f5622c106afa457981669cac6c30ea50f547a7b Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Wed, 10 Mar 2021 18:12:09 +0100 Subject: [PATCH 24/88] alsactl: fix possible memory leak for dump-cfg Also remove extra snd_config_update_free_global() call for dump-state. There's a global call in the main() function. Signed-off-by: Jaroslav Kysela --- alsactl/alsactl.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/alsactl/alsactl.c b/alsactl/alsactl.c index 446365aed..cc984e9ba 100644 --- a/alsactl/alsactl.c +++ b/alsactl/alsactl.c @@ -179,7 +179,6 @@ static int dump_state(const char *file) return err; err = dump_config_tree(top); snd_config_delete(top); - snd_config_update_free_global(); return err; } @@ -192,7 +191,9 @@ static int dump_configuration(void) if (err < 0) return err; /* expand cards.* tree */ - snd_config_search_definition(top, "cards", "dummy", &cfg2); + 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; From 9a2115b5ccb53b0224bdc3bc965c1e6de7e82a2d Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Wed, 10 Mar 2021 19:26:51 +0100 Subject: [PATCH 25/88] alsactl: Add ucm support for the FixedBootSequence Signed-off-by: Jaroslav Kysela --- alsactl/alsactl.c | 2 +- alsactl/alsactl.h | 4 +++- alsactl/init_parse.c | 16 ++++++---------- alsactl/init_ucm.c | 22 +++++++++++++++------- alsactl/state.c | 10 +++++++--- 5 files changed, 32 insertions(+), 22 deletions(-) diff --git a/alsactl/alsactl.c b/alsactl/alsactl.c index cc984e9ba..c8000877e 100644 --- a/alsactl/alsactl.c +++ b/alsactl/alsactl.c @@ -420,7 +420,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(initfile, initflags | FLAG_UCM_FBOOT | FLAG_UCM_BOOT, cardname); snd_config_update_free_global(); } else if (!strcmp(cmd, "store")) { res = save_state(cfgfile, cardname); diff --git a/alsactl/alsactl.h b/alsactl/alsactl.h index 119e3b4b8..0e24d609f 100644 --- a/alsactl/alsactl.h +++ b/alsactl/alsactl.h @@ -28,7 +28,9 @@ 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) int load_configuration(const char *file, snd_config_t **top, int *open_failed); int init(const char *file, int flags, const char *cardname); diff --git a/alsactl/init_parse.c b/alsactl/init_parse.c index e7b352c2a..4034b4d38 100644 --- a/alsactl/init_parse.c +++ b/alsactl/init_parse.c @@ -1765,11 +1765,9 @@ int init(const char *filename, int flags, const char *cardname) break; } first = 0; - if (!(flags & FLAG_UCM_DISABLED)) { - err = init_ucm(flags, card); - if (err == 0) - continue; - } + err = init_ucm(flags, card); + if (err == 0) + continue; err = init_space(&space, card); if (err == 0) { space->rootdir = new_root_dir(filename); @@ -1792,11 +1790,9 @@ int init(const char *filename, int flags, const char *cardname) error("Cannot find soundcard '%s'...", cardname); goto error; } - if (!(flags & FLAG_UCM_DISABLED)) { - err = init_ucm(flags, card); - if (err == 0) - return 0; - } + err = init_ucm(flags, card); + if (err == 0) + return 0; memset(&space, 0, sizeof(space)); err = init_space(&space, card); if (err == 0) { diff --git a/alsactl/init_ucm.c b/alsactl/init_ucm.c index 6468c9d59..9ac8db03c 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,24 @@ 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 < 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 +65,7 @@ int init_ucm(int flags, int cardno) int init_ucm(int flags, int cardno) { - return 0; + return -ENXIO; } #endif diff --git a/alsactl/state.c b/alsactl/state.c index 71e5465f9..e39e8789a 100644 --- a/alsactl/state.c +++ b/alsactl/state.c @@ -1682,7 +1682,7 @@ int load_state(const char *file, const char *initfile, int initflags, if (!do_init) break; sprintf(cardname1, "%i", card); - err = init(initfile, initflags, cardname1); + err = init(initfile, initflags | FLAG_UCM_FBOOT | FLAG_UCM_BOOT, cardname1); if (err < 0) { finalerr = err; initfailed(card, "init", err); @@ -1718,10 +1718,12 @@ int load_state(const char *file, const char *initfile, int initflags, break; } first = 0; + /* error is ignored */ + init_ucm(initflags | FLAG_UCM_FBOOT, card); /* 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); + err = init(initfile, initflags | FLAG_UCM_BOOT, cardname1); if (err < 0) { initfailed(card, "init", err); finalerr = err; @@ -1742,9 +1744,11 @@ int load_state(const char *file, const char *initfile, int initflags, err = -ENODEV; goto out; } + /* error is ignored */ + init_ucm(initflags | FLAG_UCM_FBOOT, cardno); /* do a check if controls matches state file */ if (do_init && set_controls(cardno, config, 0)) { - err = init(initfile, initflags, cardname); + err = init(initfile, initflags | FLAG_UCM_BOOT, cardname); if (err < 0) { initfailed(cardno, "init", err); finalerr = err; From eefc2c61cfda4325373e1fa2d3be1642c92eee83 Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Wed, 10 Mar 2021 20:06:24 +0100 Subject: [PATCH 26/88] alsactl: use card iterator functions for all card loops Take the card iterator idea from the monitor code and use it for all card loops. It reduces the code duplications and makes things easy to review. Signed-off-by: Jaroslav Kysela --- alsactl/alsactl.h | 13 ++++ alsactl/clean.c | 47 +++---------- alsactl/init_parse.c | 74 ++++++--------------- alsactl/monitor.c | 28 +------- alsactl/state.c | 155 ++++++++++--------------------------------- alsactl/utils.c | 51 ++++++++++++++ 6 files changed, 131 insertions(+), 237 deletions(-) diff --git a/alsactl/alsactl.h b/alsactl/alsactl.h index 0e24d609f..ca723e319 100644 --- a/alsactl/alsactl.h +++ b/alsactl/alsactl.h @@ -1,3 +1,4 @@ +#include #include extern int debugflag; @@ -9,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, ...); @@ -32,6 +40,11 @@ void error_handler(const char *file, int line, const char *function, int err, co #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 load_configuration(const char *file, snd_config_t **top, int *open_failed); int init(const char *file, int flags, const char *cardname); int init_ucm(int flags, int cardno); diff --git a/alsactl/clean.c b/alsactl/clean.c index 4808225a0..6dfc6d3bb 100644 --- a/alsactl/clean.c +++ b/alsactl/clean.c @@ -182,46 +182,15 @@ static int clean_controls(int cardno, char *const *controls) int clean(const char *cardname, char *const *extra_args) { + struct snd_card_iterator iter; int err; - 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 = clean_controls(card, extra_args))) - 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 = clean_controls(cardno, extra_args))) { - goto out; - } + 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; } -out: - return err; + return snd_card_iterator_error(&iter); } diff --git a/alsactl/init_parse.c b/alsactl/init_parse.c index 4034b4d38..ab2c9065b 100644 --- a/alsactl/init_parse.c +++ b/alsactl/init_parse.c @@ -1746,63 +1746,33 @@ static int parse(struct space *space, const char *filename) int init(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) { - if (ignore_nocards) - return 0; - error("No soundcards found..."); - return -ENODEV; - } - break; - } - first = 0; - 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; - } - err = init_ucm(flags, card); + err = snd_card_iterator_sinit(&iter, cardname); + while (snd_card_iterator_next(&iter)) { + err = init_ucm(flags, iter.card); if (err == 0) - return 0; - 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); + 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/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 e39e8789a..0612970f5 100644 --- a/alsactl/state.c +++ b/alsactl/state.c @@ -1544,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) { @@ -1577,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 { @@ -1648,119 +1622,58 @@ int load_state(const char *file, const char *initfile, int initflags, const char *cardname, int do_init) { int err, finalerr = 0, open_failed; + struct snd_card_iterator iter; snd_config_t *config; + const char *cardname1; err = load_configuration(file, &config, &open_failed); if (err < 0 && !open_failed) return err; if (open_failed) { - int card, first = 1; - char cardname1[16]; - 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 | 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; - /* error is ignored */ - init_ucm(initflags | FLAG_UCM_FBOOT, card); - /* do a check if controls matches state file */ - if (do_init && set_controls(card, config, 0)) { - sprintf(cardname1, "%i", card); - err = init(initfile, initflags | FLAG_UCM_BOOT, 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, cardno); + 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 | FLAG_UCM_BOOT, cardname); + if (do_init && set_controls(iter.card, config, 0)) { + err = init(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 ede7319c3..d0b1ac670 100644 --- a/alsactl/utils.c +++ b/alsactl/utils.c @@ -233,3 +233,54 @@ int load_configuration(const char *file, snd_config_t **top, int *open_failed) 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; +} From d81a0f93bc1b50d585b38f400949125e5d5e3915 Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Thu, 11 Mar 2021 14:21:32 +0900 Subject: [PATCH 27/88] axfer: minor code arrangement for container module in a point of nonblocking flag In internal container module, any file descriptor is expected as non-blocking mode. Current implementation distinguish the case of standard input and output from the case to open actual file since O_NONBLOCK is used for the latter case. However, in both cases, fcntl(2) is available to set non-blocking mode to the file descriptor. This commit arranges to use fcntl(2) for both cases. Signed-off-by: Takashi Sakamoto Signed-off-by: Jaroslav Kysela --- axfer/container.c | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/axfer/container.c b/axfer/container.c index 566acd058..8733ff764 100644 --- a/axfer/container.c +++ b/axfer/container.c @@ -176,16 +176,17 @@ 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); + cntr->fd = open(path, O_RDONLY); 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) @@ -260,17 +261,17 @@ 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); + cntr->fd = open(path, O_RDWR | 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. From 81c93f646024d007fb7a3595b9c0c205b35a41fc Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Thu, 11 Mar 2021 14:21:33 +0900 Subject: [PATCH 28/88] axfer: minor code arrangement in a point of stdio detection Current implementation sets stdio member in a condition branch, however it's convenient to set it always regardless of any condition. This commit arranges assignment to the member. Signed-off-by: Takashi Sakamoto Signed-off-by: Jaroslav Kysela --- axfer/container.c | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/axfer/container.c b/axfer/container.c index 8733ff764..fb35eba32 100644 --- a/axfer/container.c +++ b/axfer/container.c @@ -169,6 +169,14 @@ int container_parser_init(struct container_context *cntr, // Open a target descriptor. if (!strcmp(path, "-")) { cntr->fd = fileno(stdin); + } else { + cntr->fd = open(path, O_RDONLY); + if (cntr->fd < 0) + return -errno; + } + + cntr->stdio = (cntr->fd == fileno(stdin)); + if (cntr->stdio) { if (isatty(cntr->fd)) { fprintf(stderr, "A terminal is referred for standard input. " @@ -176,11 +184,6 @@ int container_parser_init(struct container_context *cntr, "should be referred instead.\n"); return -EIO; } - cntr->stdio = true; - } else { - cntr->fd = open(path, O_RDONLY); - if (cntr->fd < 0) - return -errno; } err = set_nonblock_flag(cntr->fd); @@ -254,6 +257,14 @@ int container_builder_init(struct container_context *cntr, return -EINVAL; if (!strcmp(path, "-")) { cntr->fd = fileno(stdout); + } else { + cntr->fd = open(path, O_RDWR | O_CREAT | O_TRUNC, 0644); + if (cntr->fd < 0) + return -errno; + } + + cntr->stdio = (cntr->fd == fileno(stdout)); + if (cntr->stdio) { if (isatty(cntr->fd)) { fprintf(stderr, "A terminal is referred for standard output. " @@ -261,11 +272,6 @@ int container_builder_init(struct container_context *cntr, "should be referred instead.\n"); return -EIO; } - cntr->stdio = true; - } else { - cntr->fd = open(path, O_RDWR | O_CREAT | O_TRUNC, 0644); - if (cntr->fd < 0) - return -errno; } err = set_nonblock_flag(cntr->fd); From 85ab708f50915cb9f5c23704f51b8e9a7a020911 Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Thu, 11 Mar 2021 14:21:34 +0900 Subject: [PATCH 29/88] axfer: minor code arrangement in a point of opened file descriptor This commit arranges assignment to fd member after checking file descriptor. Signed-off-by: Takashi Sakamoto Signed-off-by: Jaroslav Kysela --- axfer/container.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/axfer/container.c b/axfer/container.c index fb35eba32..b4646b9ad 100644 --- a/axfer/container.c +++ b/axfer/container.c @@ -151,6 +151,7 @@ int container_parser_init(struct container_context *cntr, [CONTAINER_FORMAT_AU] = &container_parser_au, [CONTAINER_FORMAT_VOC] = &container_parser_voc, }; + int fd; const struct container_parser *parser; unsigned int size; int i; @@ -168,12 +169,13 @@ int container_parser_init(struct container_context *cntr, // Open a target descriptor. if (!strcmp(path, "-")) { - cntr->fd = fileno(stdin); + fd = fileno(stdin); } else { - cntr->fd = open(path, O_RDONLY); - if (cntr->fd < 0) + fd = open(path, O_RDONLY); + if (fd < 0) return -errno; } + cntr->fd = fd; cntr->stdio = (cntr->fd == fileno(stdin)); if (cntr->stdio) { @@ -239,6 +241,7 @@ int container_builder_init(struct container_context *cntr, [CONTAINER_FORMAT_VOC] = &container_builder_voc, [CONTAINER_FORMAT_RAW] = &container_builder_raw, }; + int fd; const struct container_builder *builder; int err; @@ -256,12 +259,13 @@ int container_builder_init(struct container_context *cntr, if (path == NULL || *path == '\0') return -EINVAL; if (!strcmp(path, "-")) { - cntr->fd = fileno(stdout); + fd = fileno(stdout); } else { - cntr->fd = open(path, O_RDWR | O_CREAT | O_TRUNC, 0644); - if (cntr->fd < 0) + fd = open(path, O_RDWR | O_CREAT | O_TRUNC, 0644); + if (fd < 0) return -errno; } + cntr->fd = fd; cntr->stdio = (cntr->fd == fileno(stdout)); if (cntr->stdio) { From 4bf9e269b2ca6509612db7e8edbd0939226f827c Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Thu, 11 Mar 2021 14:21:35 +0900 Subject: [PATCH 30/88] axfer: minor code arrangement to allocate containers This commit unifies duplicated code to allocate for container structure. Signed-off-by: Takashi Sakamoto Signed-off-by: Jaroslav Kysela --- axfer/subcmd-transfer.c | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/axfer/subcmd-transfer.c b/axfer/subcmd-transfer.c index 8746e6f49..6962208e9 100644 --- a/axfer/subcmd-transfer.c +++ b/axfer/subcmd-transfer.c @@ -146,6 +146,16 @@ 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; + + 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 +174,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; @@ -212,10 +221,9 @@ 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) { snd_pcm_format_t format; From 5cb67b5ab6e6bc28a14a383950d8c614b86b45b8 Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Thu, 11 Mar 2021 14:21:36 +0900 Subject: [PATCH 31/88] axfer: open file descriptor outside of container module Internal container module operates file descriptor to media file. For this purpose, the structure has fd member and any file operation is done internally. However, the case to use special file descriptor such as memfd requires to maintain file descriptor externally. This commit opens file descriptor outside of container module. The internal APIs to initialize container get an argument for the file descriptor instead of file path. Signed-off-by: Takashi Sakamoto Signed-off-by: Jaroslav Kysela --- axfer/container.c | 35 ++++++----------------------------- axfer/container.h | 9 ++++----- axfer/subcmd-transfer.c | 26 ++++++++++++++++++++++---- axfer/test/container-test.c | 12 ++++++++++-- axfer/test/mapper-test.c | 19 ++++++++++++++----- 5 files changed, 56 insertions(+), 45 deletions(-) diff --git a/axfer/container.c b/axfer/container.c index b4646b9ad..255f12ce9 100644 --- a/axfer/container.c +++ b/axfer/container.c @@ -143,23 +143,21 @@ 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, [CONTAINER_FORMAT_AU] = &container_parser_au, [CONTAINER_FORMAT_VOC] = &container_parser_voc, }; - int fd; const struct container_parser *parser; unsigned int size; int i; int err; assert(cntr); - assert(path); - assert(path[0] != '\0'); + assert(fd >= 0); // Detect forgotten to destruct. assert(cntr->fd == 0); @@ -167,14 +165,6 @@ int container_parser_init(struct container_context *cntr, memset(cntr, 0, sizeof(*cntr)); - // Open a target descriptor. - if (!strcmp(path, "-")) { - fd = fileno(stdin); - } else { - fd = open(path, O_RDONLY); - if (fd < 0) - return -errno; - } cntr->fd = fd; cntr->stdio = (cntr->fd == fileno(stdin)); @@ -231,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, @@ -241,13 +230,11 @@ int container_builder_init(struct container_context *cntr, [CONTAINER_FORMAT_VOC] = &container_builder_voc, [CONTAINER_FORMAT_RAW] = &container_builder_raw, }; - int fd; const struct container_builder *builder; int err; assert(cntr); - assert(path); - assert(path[0] != '\0'); + assert(fd >= 0); // Detect forgotten to destruct. assert(cntr->fd == 0); @@ -255,16 +242,6 @@ 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, "-")) { - fd = fileno(stdout); - } else { - fd = open(path, O_RDWR | O_CREAT | O_TRUNC, 0644); - if (fd < 0) - return -errno; - } cntr->fd = fd; cntr->stdio = (cntr->fd == fileno(stdout)); diff --git a/axfer/container.h b/axfer/container.h index cb64816dd..0840369ac 100644 --- a/axfer/container.h +++ b/axfer/container.h @@ -61,11 +61,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 6962208e9..52c32d524 100644 --- a/axfer/subcmd-transfer.c +++ b/axfer/subcmd-transfer.c @@ -185,10 +185,19 @@ 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; + } + + err = container_builder_init(ctx->cntrs + i, fd, ctx->xfer.cntr_format, ctx->xfer.verbose > 1); if (err < 0) @@ -226,13 +235,22 @@ static int playback_pre_process(struct context *ctx, snd_pcm_access_t *access, 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; + } + + err = container_parser_init(ctx->cntrs + i, fd, ctx->xfer.verbose > 1); if (err < 0) return err; diff --git a/axfer/test/container-test.c b/axfer/test/container-test.c index 9b30ae3d3..fbef3a462 100644 --- a/axfer/test/container-test.c +++ b/axfer/test/container-test.c @@ -33,6 +33,7 @@ static void test_builder(struct container_context *cntr, void *frame_buffer, unsigned int frame_count, bool verbose) { + int fd; snd_pcm_format_t sample; unsigned int channels; unsigned int rate; @@ -41,7 +42,10 @@ static void test_builder(struct container_context *cntr, uint64_t total_frame_count; int err; - err = container_builder_init(cntr, name, format, verbose); + fd = open(name, O_RDWR | O_CREAT | O_TRUNC, 0644); + assert(fd >= 0); + + err = container_builder_init(cntr, fd, format, verbose); assert(err == 0); sample = sample_format; @@ -79,6 +83,7 @@ static void test_parser(struct container_context *cntr, void *frame_buffer, unsigned int frame_count, bool verbose) { + int fd; snd_pcm_format_t sample; unsigned int channels; unsigned int rate; @@ -86,7 +91,10 @@ static void test_parser(struct container_context *cntr, unsigned int handled_frame_count; int err; - err = container_parser_init(cntr, name, verbose); + fd = open(name, O_RDONLY); + assert(fd >= 0); + + err = container_parser_init(cntr, fd, verbose); assert(err == 0); sample = sample_format; diff --git a/axfer/test/mapper-test.c b/axfer/test/mapper-test.c index f0376c77d..625290054 100644 --- a/axfer/test/mapper-test.c +++ b/axfer/test/mapper-test.c @@ -66,7 +66,6 @@ static int test_demux(struct mapper_trial *trial, snd_pcm_access_t access, 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; @@ -74,12 +73,17 @@ static int test_demux(struct mapper_trial *trial, snd_pcm_access_t access, int err = 0; for (i = 0; i < cntr_count; ++i) { + const char *path = trial->paths[i]; + int fd; snd_pcm_format_t format; unsigned int channels; unsigned int rate; - err = container_builder_init(cntrs + i, paths[i], cntr_format, - 0); + fd = open(path, O_RDWR | O_CREAT | O_TRUNC, 0644); + if (fd < 0) + return -errno; + + err = container_builder_init(cntrs + i, fd, cntr_format, 0); if (err < 0) goto end; @@ -159,18 +163,23 @@ static int test_mux(struct mapper_trial *trial, snd_pcm_access_t access, 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; int err = 0; for (i = 0; i < cntr_count; ++i) { + const char *path = trial->paths[i]; + int fd; snd_pcm_format_t format; unsigned int channels; unsigned int rate; - err = container_parser_init(cntrs + i, paths[i], 0); + fd = open(path, O_RDONLY); + if (fd < 0) + return -errno; + + err = container_parser_init(cntrs + i, fd, 0); if (err < 0) goto end; From 6b0f1b20ae07a94efeb0d3763363b8a4f39e9078 Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Thu, 11 Mar 2021 14:21:37 +0900 Subject: [PATCH 32/88] axfer: maintain lifetime of file descriptor outside of container module This commit closes file descriptor outside of container module so that maintenance of lifetime for the descriptor is delegated to container user. Signed-off-by: Takashi Sakamoto Signed-off-by: Jaroslav Kysela --- axfer/container.c | 1 - axfer/container.h | 1 - axfer/subcmd-transfer.c | 18 +++++++++++++-- axfer/test/container-test.c | 2 ++ axfer/test/mapper-test.c | 44 +++++++++++++++++++++++++++---------- 5 files changed, 50 insertions(+), 16 deletions(-) diff --git a/axfer/container.c b/axfer/container.c index 255f12ce9..8c88d5c40 100644 --- a/axfer/container.c +++ b/axfer/container.c @@ -451,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 0840369ac..71017a6e2 100644 --- a/axfer/container.h +++ b/axfer/container.h @@ -11,7 +11,6 @@ #define _LARGEFILE64_SOURCE #include -#include #include #include diff --git a/axfer/subcmd-transfer.c b/axfer/subcmd-transfer.c index 52c32d524..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; @@ -153,6 +155,10 @@ static int allocate_containers(struct context *ctx, unsigned int count) return -ENOMEM; ctx->cntr_count = count; + ctx->cntr_fds = calloc(count, sizeof(*ctx->cntrs)); + if (ctx->cntr_fds == NULL) + return -ENOMEM; + return 0; } @@ -196,8 +202,9 @@ static int capture_pre_process(struct context *ctx, snd_pcm_access_t *access, if (fd < 0) return -errno; } + ctx->cntr_fds[i] = fd; - err = container_builder_init(ctx->cntrs + i, fd, + err = container_builder_init(ctx->cntrs + i, ctx->cntr_fds[i], ctx->xfer.cntr_format, ctx->xfer.verbose > 1); if (err < 0) @@ -249,8 +256,9 @@ static int playback_pre_process(struct context *ctx, snd_pcm_access_t *access, if (fd < 0) return -errno; } + ctx->cntr_fds[i] = fd; - err = container_parser_init(ctx->cntrs + i, fd, + err = container_parser_init(ctx->cntrs + i, ctx->cntr_fds[i], ctx->xfer.verbose > 1); if (err < 0) return err; @@ -447,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 fbef3a462..d89852ac5 100644 --- a/axfer/test/container-test.c +++ b/axfer/test/container-test.c @@ -73,6 +73,7 @@ static void test_builder(struct container_context *cntr, assert(total_frame_count == frame_count); container_context_destroy(cntr); + close(fd); } static void test_parser(struct container_context *cntr, @@ -121,6 +122,7 @@ static void test_parser(struct container_context *cntr, assert(total_frame_count == handled_frame_count); container_context_destroy(cntr); + close(fd); } static int callback(struct test_generator *gen, snd_pcm_access_t access, diff --git a/axfer/test/mapper-test.c b/axfer/test/mapper-test.c index 625290054..78a063aad 100644 --- a/axfer/test/mapper-test.c +++ b/axfer/test/mapper-test.c @@ -67,23 +67,29 @@ static int test_demux(struct mapper_trial *trial, snd_pcm_access_t access, { struct container_context *cntrs = trial->cntrs; enum container_format cntr_format = trial->cntr_format; + int *cntr_fds; unsigned int bytes_per_sample; uint64_t total_frame_count; int i; int err = 0; + 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]; - int fd; snd_pcm_format_t format; unsigned int channels; unsigned int rate; - fd = open(path, O_RDWR | O_CREAT | O_TRUNC, 0644); - if (fd < 0) - return -errno; + cntr_fds[i] = open(path, O_RDWR | O_CREAT | O_TRUNC, 0644); + if (cntr_fds[i] < 0) { + err = -errno; + goto end; + } - err = container_builder_init(cntrs + i, fd, cntr_format, 0); + err = container_builder_init(cntrs + i, cntr_fds[i], cntr_format, 0); if (err < 0) goto end; @@ -118,8 +124,12 @@ static int test_demux(struct mapper_trial *trial, snd_pcm_access_t access, assert(total_frame_count == frame_count); } end: - for (i = 0; i < cntr_count; ++i) + for (i = 0; i < cntr_count; ++i) { container_context_destroy(cntrs + i); + close(cntr_fds[i]); + } + + free(cntr_fds); return err; } @@ -163,23 +173,29 @@ static int test_mux(struct mapper_trial *trial, snd_pcm_access_t access, unsigned int cntr_count) { struct container_context *cntrs = trial->cntrs; + int *cntr_fds; unsigned int bytes_per_sample; uint64_t total_frame_count; int i; int err = 0; + 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]; - int fd; snd_pcm_format_t format; unsigned int channels; unsigned int rate; - fd = open(path, O_RDONLY); - if (fd < 0) - return -errno; + cntr_fds[i] = open(path, O_RDONLY); + if (cntr_fds[i] < 0) { + err = -errno; + goto end; + } - err = container_parser_init(cntrs + i, fd, 0); + err = container_parser_init(cntrs + i, cntr_fds[i], 0); if (err < 0) goto end; @@ -214,8 +230,12 @@ static int test_mux(struct mapper_trial *trial, snd_pcm_access_t access, assert(total_frame_count == frame_count); } end: - for (i = 0; i < cntr_count; ++i) + for (i = 0; i < cntr_count; ++i) { container_context_destroy(cntrs + i); + close(cntr_fds[i]); + } + + free(cntr_fds); return err; } From e9a4d32e2989d6b4446503630ac11d5b0ddd8f25 Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Thu, 11 Mar 2021 14:21:38 +0900 Subject: [PATCH 33/88] autotools: preparation to use memfd_create(2) This is a preparation to use memfd_create(2) system call for test programs of axfer. The system call was introduced at Linux kernel v3.17 and relatively new. For safe, this commit adds detection of memfd_create() in autotools side so that application can handle the case not to detect. Signed-off-by: Takashi Sakamoto Signed-off-by: Jaroslav Kysela --- configure.ac | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/configure.ac b/configure.ac index 7005ccce5..ff3e1f6c7 100644 --- a/configure.ac +++ b/configure.ac @@ -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") From c45d994867cbfeaf5b8f0ef80511da06c80d4c90 Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Thu, 11 Mar 2021 14:21:39 +0900 Subject: [PATCH 34/88] axfer: test: minor code arrangement to use the same file descriptor for container-test In container test program, two file descriptors open to the same file for builder and parser contexts, however the same file descriptor is available for the case. This commit arranges to use the same file descriptor for the contexts. Signed-off-by: Takashi Sakamoto Signed-off-by: Jaroslav Kysela --- axfer/test/container-test.c | 40 +++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/axfer/test/container-test.c b/axfer/test/container-test.c index d89852ac5..501e2af04 100644 --- a/axfer/test/container-test.c +++ b/axfer/test/container-test.c @@ -24,8 +24,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, @@ -33,7 +33,6 @@ static void test_builder(struct container_context *cntr, void *frame_buffer, unsigned int frame_count, bool verbose) { - int fd; snd_pcm_format_t sample; unsigned int channels; unsigned int rate; @@ -42,9 +41,6 @@ static void test_builder(struct container_context *cntr, uint64_t total_frame_count; int err; - fd = open(name, O_RDWR | O_CREAT | O_TRUNC, 0644); - assert(fd >= 0); - err = container_builder_init(cntr, fd, format, verbose); assert(err == 0); @@ -73,18 +69,16 @@ static void test_builder(struct container_context *cntr, assert(total_frame_count == frame_count); container_context_destroy(cntr); - close(fd); } -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, void *frame_buffer, unsigned int frame_count, bool verbose) { - int fd; snd_pcm_format_t sample; unsigned int channels; unsigned int rate; @@ -92,9 +86,6 @@ static void test_parser(struct container_context *cntr, unsigned int handled_frame_count; int err; - fd = open(name, O_RDONLY); - assert(fd >= 0); - err = container_parser_init(cntr, fd, verbose); assert(err == 0); @@ -122,7 +113,6 @@ static void test_parser(struct container_context *cntr, assert(total_frame_count == handled_frame_count); container_context_destroy(cntr); - close(fd); } static int callback(struct test_generator *gen, snd_pcm_access_t access, @@ -156,26 +146,42 @@ static int callback(struct test_generator *gen, snd_pcm_access_t access, 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, + fd = open(name, O_RDWR | O_CREAT | O_TRUNC, 0644); + 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); + close(fd); unlink(name); } free(buf); - return 0; + return err; } int main(int argc, const char *argv[]) From 2314d746b9009838523ba24c377253ae656c9f36 Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Thu, 11 Mar 2021 14:21:40 +0900 Subject: [PATCH 35/88] axfer: test: use memfd_create() for container-test The container test program writes audio data frame to file, and read them from the file, then validate them. For the operations, usage of any in-memory file is good to shorten time of overall operations. This commit uses shm via memfd_create(). As a result, overall time to run is shorten one half of before, depending on machine environment. I note that we can achieve the same result by using O_TMPFILE flag in open(2) system call, however the implementation of O_TMPFILE is to add i-node without name on underling file system, thus it has overhead depending on implementation in each file system. On the other hand, memfd_create() is directly relevant to shm and expected to be less overhead. Signed-off-by: Takashi Sakamoto Signed-off-by: Jaroslav Kysela --- axfer/test/container-test.c | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/axfer/test/container-test.c b/axfer/test/container-test.c index 501e2af04..0b231d054 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 @@ -142,16 +151,17 @@ 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]; +#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; @@ -176,7 +186,6 @@ static int callback(struct test_generator *gen, snd_pcm_access_t access, assert(err == 0); close(fd); - unlink(name); } free(buf); From 5ab26fbc146bcb1a894970421a10188feed714a2 Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Thu, 11 Mar 2021 14:21:41 +0900 Subject: [PATCH 36/88] axfer: test: minor code arrangement to use the same file descriptor for mappter-test In mapper test program, two set of file descriptors open to the same files for container builder and parser contexts, however the same file descriptor is available for the case. This commit arranges to use the same file descriptor for the contexts. Signed-off-by: Takashi Sakamoto Signed-off-by: Jaroslav Kysela --- axfer/test/mapper-test.c | 73 +++++++++++++++++++--------------------- 1 file changed, 34 insertions(+), 39 deletions(-) diff --git a/axfer/test/mapper-test.c b/axfer/test/mapper-test.c index 78a063aad..0bed4bb50 100644 --- a/axfer/test/mapper-test.c +++ b/axfer/test/mapper-test.c @@ -63,32 +63,20 @@ 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; enum container_format cntr_format = trial->cntr_format; - int *cntr_fds; unsigned int bytes_per_sample; uint64_t total_frame_count; int i; int err = 0; - 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]; snd_pcm_format_t format; unsigned int channels; unsigned int rate; - cntr_fds[i] = open(path, O_RDWR | O_CREAT | O_TRUNC, 0644); - if (cntr_fds[i] < 0) { - err = -errno; - goto end; - } - err = container_builder_init(cntrs + i, cntr_fds[i], cntr_format, 0); if (err < 0) goto end; @@ -124,12 +112,8 @@ static int test_demux(struct mapper_trial *trial, snd_pcm_access_t access, assert(total_frame_count == frame_count); } end: - for (i = 0; i < cntr_count; ++i) { + for (i = 0; i < cntr_count; ++i) container_context_destroy(cntrs + i); - close(cntr_fds[i]); - } - - free(cntr_fds); return err; } @@ -170,31 +154,19 @@ 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; - int *cntr_fds; unsigned int bytes_per_sample; uint64_t total_frame_count; int i; int err = 0; - 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]; snd_pcm_format_t format; unsigned int channels; unsigned int rate; - cntr_fds[i] = open(path, O_RDONLY); - if (cntr_fds[i] < 0) { - err = -errno; - goto end; - } - err = container_parser_init(cntrs + i, cntr_fds[i], 0); if (err < 0) goto end; @@ -230,12 +202,8 @@ static int test_mux(struct mapper_trial *trial, snd_pcm_access_t access, assert(total_frame_count == frame_count); } end: - for (i = 0; i < cntr_count; ++i) { + for (i = 0; i < cntr_count; ++i) container_context_destroy(cntrs + i); - close(cntr_fds[i]); - } - - free(cntr_fds); return err; } @@ -247,6 +215,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; @@ -254,18 +223,44 @@ 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]; + + cntr_fds[i] = open(path, O_RDWR | O_CREAT | O_TRUNC, 0644); + 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) + for (i = 0; i < cntr_count; ++i) { unlink(trial->paths[i]); + close(cntr_fds[i]); + } + + free(cntr_fds); return err; } From 92004425fc5d260052601dc5c329457eee73e7a3 Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Thu, 11 Mar 2021 14:21:42 +0900 Subject: [PATCH 37/88] axfer: test: use memfd_create() for mapper-test The mapper test program writes audio data frame to files, and read them from the files, then validate them. For the operations, usage of any in-memory file is good to shorten time of overall operations. This commit uses shm by memfd_create(). Signed-off-by: Takashi Sakamoto Signed-off-by: Jaroslav Kysela --- axfer/test/mapper-test.c | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/axfer/test/mapper-test.c b/axfer/test/mapper-test.c index 0bed4bb50..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 @@ -230,7 +239,11 @@ static int test_mapper(struct mapper_trial *trial, snd_pcm_access_t access, 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; @@ -255,10 +268,8 @@ static int test_mapper(struct mapper_trial *trial, snd_pcm_access_t access, frames_per_second, frames_per_buffer, check_buffer, frame_count, cntr_fds, cntr_count); end: - for (i = 0; i < cntr_count; ++i) { - unlink(trial->paths[i]); + for (i = 0; i < cntr_count; ++i) close(cntr_fds[i]); - } free(cntr_fds); From 0a03f061343ac5a16e551c0e9ac44f1a60615589 Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Thu, 11 Mar 2021 14:21:43 +0900 Subject: [PATCH 38/88] axfer: test: reduce test case for maximum number of frame count This commit reduces test case for maximum number of frame count so that overall time is shortened. The count of total iteration is still the same. Signed-off-by: Takashi Sakamoto Signed-off-by: Jaroslav Kysela --- axfer/test/container-test.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/axfer/test/container-test.c b/axfer/test/container-test.c index 0b231d054..1f686d7ff 100644 --- a/axfer/test/container-test.c +++ b/axfer/test/container-test.c @@ -300,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, 128, 23, 3000, 512, sizeof(struct container_trial)); if (err >= 0) { trial = gen.private_data; From 61c8622de403d54799355342b44ad52564cd19b7 Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Thu, 11 Mar 2021 14:21:44 +0900 Subject: [PATCH 39/88] Revert "axfer: test - add run-test-in-tmpdir.sh script" This reverts commit e1551de8dd28c3a63f8d7c146952a8d2649ac9de since tests run for in-memory files now. Signed-off-by: Takashi Sakamoto Signed-off-by: Jaroslav Kysela --- axfer/test/run-test-in-tmpdir.sh | 19 ------------------- 1 file changed, 19 deletions(-) delete mode 100755 axfer/test/run-test-in-tmpdir.sh diff --git a/axfer/test/run-test-in-tmpdir.sh b/axfer/test/run-test-in-tmpdir.sh deleted file mode 100755 index e66fa7335..000000000 --- a/axfer/test/run-test-in-tmpdir.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/sh - -bin="$1" - -test -z ${bin} && exit 90 -test ! -x ${bin} && exit 91 -test -z ${TMPDIR} && exit 92 -test ! -d ${TMPDIR} && exit 93 - -tmp_dir=$(mktemp -d ${TMPDIR}/container-test.XXXXX) -cur_dir=$(pwd) - -echo ${tmp_dir} -cd ${tmp_dir} -${cur_dir}/${bin} -retval=$? -cd ${cur_dir} -rm -rf ${tmp_dir} -exit $retval From c2d27acfce79dfdd23a397f884c3886511acbbf2 Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Thu, 11 Mar 2021 14:21:45 +0900 Subject: [PATCH 40/88] axfer: test: reduce test case for maximum number of samples per frame This commit reduces test case for maximum number of samples per frame so that overall time is shortened. The count of total iteration is also reduced by one quarter. Signed-off-by: Takashi Sakamoto Signed-off-by: Jaroslav Kysela --- axfer/test/container-test.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/axfer/test/container-test.c b/axfer/test/container-test.c index 1f686d7ff..e5b62dd5e 100644 --- a/axfer/test/container-test.c +++ b/axfer/test/container-test.c @@ -300,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, 3000, 512, + 1, 32, 23, 3000, 512, sizeof(struct container_trial)); if (err >= 0) { trial = gen.private_data; From 00be486131129a6d209de62202d2a4974638127f Mon Sep 17 00:00:00 2001 From: Pierre-Louis Bossart Date: Fri, 12 Mar 2021 11:03:16 -0600 Subject: [PATCH 41/88] speaker-test: add support for S24_LE and S24_BE These formats are sometimes advertised by drivers, e.g. SOF. The format is 3 bytes packed in 32-bit container, with the MSB zeroed out. sample: 0x00123456 S24_LE format: b0 56 b1 34 b2 12 b3 00 S24_BE format: b0 00 b1 12 b2 34 b3 56 I only tested the S24_LE format with the SOF driver, S24_BE was added for symmetry only. Signed-off-by: Pierre-Louis Bossart Signed-off-by: Takashi Iwai --- speaker-test/speaker-test.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) 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; From c9e9a79c6cfef3212bdb5f9be4f6ea1d2a5e8670 Mon Sep 17 00:00:00 2001 From: Andreas Pape Date: Fri, 19 Mar 2021 11:57:13 +0100 Subject: [PATCH 42/88] aplay: avoid any further PCM writing if in abort Terminating stream with CTRL-C will set in_aborting flag which is used to leave any write/read loop on the ALSA device. After ending the read/write loop aplay tries to drain the stream which is not required and can also lead to malfunctions: -If user interrupts a blocked/non responsive PCM (e.g. usb uac2 gadget which does not consume data due to stream stopped by host) it will successfully terminate the write loop but will hang again in drain call. This would require to hit CTRL-C again to unblock which should be avoided. Aplay currently anyhow allows signal handler to get invoked only once. Signed-off-by: Andreas Pape Signed-off-by: Jaroslav Kysela --- aplay/aplay.c | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/aplay/aplay.c b/aplay/aplay.c index 9ebaae410..0b7884e80 100644 --- a/aplay/aplay.c +++ b/aplay/aplay.c @@ -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) @@ -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) From 2c753a85a7834f6cbaa130d798f54fbf5bd87520 Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Tue, 23 Mar 2021 17:52:09 +0100 Subject: [PATCH 43/88] alsactl: snd_ctl_elem_id_compare was renamed to snd_ctl_elem_id_compare_set Signed-off-by: Jaroslav Kysela --- alsactl/clean.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/alsactl/clean.c b/alsactl/clean.c index 6dfc6d3bb..1eb82c0f5 100644 --- a/alsactl/clean.c +++ b/alsactl/clean.c @@ -52,7 +52,7 @@ static int clean_one_control(snd_ctl_t *handle, snd_ctl_elem_id_t *elem_id, dbg("Application control \"%s\" found.", s); if (filter) { for (; *filter; filter++) { - if (snd_ctl_elem_id_compare(elem_id, *filter) == 0) + if (snd_ctl_elem_id_compare_set(elem_id, *filter) == 0) break; } if (*filter == NULL) { From 69276d4a6c92599fb3cc9a9a4db64ce22cff8504 Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Mon, 29 Mar 2021 11:36:17 +0200 Subject: [PATCH 44/88] amixer: print error when snd_hctl_handle_events() fails It may be possible that the controls are quickly added and removed, thus the "hard" fail is not optimal here. Signed-off-by: Jaroslav Kysela --- amixer/amixer.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/amixer/amixer.c b/amixer/amixer.c index b3b9b4809..0bdae997e 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); From 204ae460a1768356e7a8eb561e7434b23e13e4a0 Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Tue, 30 Mar 2021 13:31:13 +0200 Subject: [PATCH 45/88] amixer: cleanups for valgrind Call snd_config_update_free_global() to check the memory leaks and wrong memory access. Signed-off-by: Jaroslav Kysela --- amixer/amixer.c | 41 ++++++++++++++++++++++++----------------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/amixer/amixer.c b/amixer/amixer.c index 0bdae997e..3f078426c 100644 --- a/amixer/amixer.c +++ b/amixer/amixer.c @@ -1772,7 +1772,7 @@ static int exec_stdin(void) int main(int argc, char *argv[]) { - int morehelp, level = 0; + int morehelp, retval, level = 0; int read_stdin = 0; static const struct option long_option[] = { @@ -1865,39 +1865,46 @@ int main(int argc, char *argv[]) } 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; } From 7a7e064f83f128e4e115c24ef15ba6788b1709a6 Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Wed, 31 Mar 2021 16:38:37 +0200 Subject: [PATCH 46/88] alsaloop: samplerate - fix the wrong pointer operation It seems that the warnings fix introduced a regression. BugLink: https://github.com/alsa-project/alsa-utils/issues/85 Fixes: cc46d02 ("alsaloop: pcmjob - fix few warnings") Signed-off-by: Jaroslav Kysela --- alsaloop/pcmjob.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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; From 0a6b63a2c423821d8537b4e5d13f0ea163decb01 Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Tue, 6 Apr 2021 18:38:18 +0200 Subject: [PATCH 47/88] amixer/alsamixer: use sysdefault: devices instead hw: The alsa-lib 1.2.5 introduced a new scheme for the default control devices. Signed-off-by: Jaroslav Kysela --- alsamixer/card_select.c | 4 ++++ alsamixer/cli.c | 6 +++++- amixer/amixer.c | 4 ++++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/alsamixer/card_select.c b/alsamixer/card_select.c index a58c0370c..f2ec01f4f 100644 --- a/alsamixer/card_select.c +++ b/alsamixer/card_select.c @@ -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/amixer/amixer.c b/amixer/amixer.c index 3f078426c..8424e7bc3 100644 --- a/amixer/amixer.c +++ b/amixer/amixer.c @@ -1806,7 +1806,11 @@ 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++; From a589d8886236983cb3bed16831b8ef44120991dd Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Tue, 13 Apr 2021 11:15:55 +0200 Subject: [PATCH 48/88] alsactl: clean the boot / hotplug card specific configuration directory The /var/lib/alsa/card.conf.d directories should be emptied when the card is initialized. Implement this functionality directly to alsactl. Signed-off-by: Jaroslav Kysela --- alsactl/alsactl.1 | 4 ++++ alsactl/alsactl.c | 14 +++++++++++--- alsactl/alsactl.h | 6 ++++-- alsactl/init_parse.c | 8 +++++++- alsactl/state.c | 7 ++++--- alsactl/utils.c | 43 +++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 73 insertions(+), 9 deletions(-) diff --git a/alsactl/alsactl.1 b/alsactl/alsactl.1 index 0bd6c1bb9..8296663a7 100644 --- a/alsactl/alsactl.1 +++ b/alsactl/alsactl.1 @@ -98,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 diff --git a/alsactl/alsactl.c b/alsactl/alsactl.c index c8000877e..a0112844c 100644 --- a/alsactl/alsactl.c +++ b/alsactl/alsactl.c @@ -31,8 +31,11 @@ #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" @@ -73,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 ")" }, @@ -227,6 +231,7 @@ 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; @@ -286,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; @@ -420,7 +428,7 @@ int main(int argc, char *argv[]) snd_lib_error_set_handler(error_handler); if (!strcmp(cmd, "init")) { - res = init(initfile, initflags | FLAG_UCM_FBOOT | FLAG_UCM_BOOT, 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); @@ -429,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); diff --git a/alsactl/alsactl.h b/alsactl/alsactl.h index ca723e319..bbdf6c88b 100644 --- a/alsactl/alsactl.h +++ b/alsactl/alsactl.h @@ -46,12 +46,13 @@ const char *snd_card_iterator_next(struct snd_card_iterator *iter); int snd_card_iterator_error(struct snd_card_iterator *iter); int load_configuration(const char *file, snd_config_t **top, int *open_failed); -int init(const char *file, int flags, const char *cardname); +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); @@ -59,6 +60,7 @@ 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/init_parse.c b/alsactl/init_parse.c index ab2c9065b..9d0f473c1 100644 --- a/alsactl/init_parse.c +++ b/alsactl/init_parse.c @@ -1743,7 +1743,7 @@ 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; struct snd_card_iterator iter; @@ -1752,6 +1752,12 @@ int init(const char *filename, int flags, const char *cardname) sysfs_init(); 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; + } err = init_ucm(flags, iter.card); if (err == 0) continue; diff --git a/alsactl/state.c b/alsactl/state.c index 0612970f5..44fda3fe7 100644 --- a/alsactl/state.c +++ b/alsactl/state.c @@ -1618,7 +1618,8 @@ 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, open_failed; @@ -1640,7 +1641,7 @@ int load_state(const char *file, const char *initfile, int initflags, while ((cardname1 = snd_card_iterator_next(&iter)) != NULL) { if (!do_init) break; - err = init(initfile, initflags | FLAG_UCM_FBOOT | FLAG_UCM_BOOT, cardname1); + err = init(cfgdir, initfile, initflags | FLAG_UCM_FBOOT | FLAG_UCM_BOOT, cardname1); if (err < 0) { finalerr = err; initfailed(iter.card, "init", err); @@ -1661,7 +1662,7 @@ int load_state(const char *file, const char *initfile, int initflags, init_ucm(initflags | FLAG_UCM_FBOOT, iter.card); /* do a check if controls matches state file */ if (do_init && set_controls(iter.card, config, 0)) { - err = init(initfile, initflags | FLAG_UCM_BOOT, cardname1); + err = init(cfgdir, initfile, initflags | FLAG_UCM_BOOT, cardname1); if (err < 0) { initfailed(iter.card, "init", err); finalerr = err; diff --git a/alsactl/utils.c b/alsactl/utils.c index d0b1ac670..c79fd951b 100644 --- a/alsactl/utils.c +++ b/alsactl/utils.c @@ -284,3 +284,46 @@ 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; +} From 0fe5048a309523bc360d0142f3edec4d94b26400 Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Tue, 13 Apr 2021 19:18:38 +0200 Subject: [PATCH 49/88] alsactl: ucm - try both fixed boot and boot sequences The -ENOENT error means that there's no special configuration. Try to fall-back to the legacy config. Signed-off-by: Jaroslav Kysela --- alsactl/init_ucm.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/alsactl/init_ucm.c b/alsactl/init_ucm.c index 9ac8db03c..b3266010f 100644 --- a/alsactl/init_ucm.c +++ b/alsactl/init_ucm.c @@ -46,8 +46,11 @@ int init_ucm(int flags, int cardno) return err; if (flags & FLAG_UCM_FBOOT) { err = snd_use_case_set(uc_mgr, "_fboot", NULL); - if (err < 0) + 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); From 0369271aace516647ae7a3d4d365ce2a9be6b5d0 Mon Sep 17 00:00:00 2001 From: nootc <78979619+nootc@users.noreply.github.com> Date: Mon, 22 Mar 2021 18:02:24 -0400 Subject: [PATCH 50/88] alsaloop: man page - correct "rate" option Change "rate" option from "-c" to "-r". BugLink: https://github.com/alsa-project/alsa-utils/pull/83 From: nootc Signed-off-by: Jaroslav Kysela --- alsaloop/alsaloop.1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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). From 71003a2acb7840413cd2e7777fff2c75c20891f0 Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Thu, 15 Apr 2021 11:27:25 +0200 Subject: [PATCH 51/88] alsamixer: increase control device name buffer (sysdefault) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit card_select.c:129:28: warning: ‘%d’ directive writing between 1 and 10 bytes into a region of size 5 [-Wformat-overflow=] 129 | sprintf(buf, "sysdefault:%d", number); Signed-off-by: Jaroslav Kysela --- alsamixer/card_select.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/alsamixer/card_select.c b/alsamixer/card_select.c index f2ec01f4f..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 = "-"; From bc803446259ed16ea1a3b66078be78121647730f Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Mon, 10 May 2021 16:31:06 +0200 Subject: [PATCH 52/88] amixer: don't show help on argument parsing error Signed-off-by: Jaroslav Kysela --- amixer/amixer.c | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/amixer/amixer.c b/amixer/amixer.c index 8424e7bc3..f9cbb8984 100644 --- a/amixer/amixer.c +++ b/amixer/amixer.c @@ -1772,7 +1772,7 @@ static int exec_stdin(void) int main(int argc, char *argv[]) { - int morehelp, retval, level = 0; + int badopt, retval, level = 0; int read_stdin = 0; static const struct option long_option[] = { @@ -1791,7 +1791,7 @@ int main(int argc, char *argv[]) {NULL, 0, NULL, 0}, }; - morehelp = 0; + badopt = 0; while (1) { int c; @@ -1812,8 +1812,8 @@ int main(int argc, char *argv[]) sprintf(card, "hw:%i", i); #endif else { - fprintf(stderr, "Invalid card number.\n"); - morehelp++; + fprintf(stderr, "Invalid card number '%s'.\n", optarg); + badopt++; } } break; @@ -1835,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)); @@ -1846,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': @@ -1859,14 +1859,13 @@ 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) { From 76b4af26167a60f8fffbfc765cf090afd1b8af8c Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Thu, 13 May 2021 11:54:13 +0200 Subject: [PATCH 53/88] alsaucm: add 'getval' and 'getival' commands Print the value only without the variable name prefix Signed-off-by: Jaroslav Kysela --- alsaucm/usecase.c | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) 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; From 9af7148fa333ed1adfc3a400a017f1d4283a61ea Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Thu, 13 May 2021 22:48:28 +0900 Subject: [PATCH 54/88] axfer: fix regression of timeout in timer-based scheduling model MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In timer-based scheduling model, event waiting is just to measure time elapse since no event is expected to occur. However, as a result to applying commit e5e6a7838b06, -ETIMEDOUT returns in the case and the caller handles it as error. This results in disorder of the scheduling model. This commit fixes the regression so that the -ETIMEDOUT case is expected. Reported-by: Amadeusz Sławiński Link: https://lore.kernel.org/alsa-devel/687f9871-7484-1370-04d1-9c968e86f72b@linux.intel.com/ Fixes: e5e6a7838b06 ("axfer: return ETIMEDOUT when no event occurs after waiter expiration") Signed-off-by: Takashi Sakamoto Signed-off-by: Jaroslav Kysela --- axfer/xfer-libasound-timer-mmap.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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. From 75e644df810473f115d8dd1d68683425aa680d6c Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Mon, 17 May 2021 16:07:39 +0200 Subject: [PATCH 55/88] amixer: link volume_mapping.c from alsamixer to amixer ... otherwise automake complains Signed-off-by: Jaroslav Kysela --- amixer/Makefile.am | 2 +- amixer/volume_mapping.c | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) create mode 120000 amixer/volume_mapping.c diff --git a/amixer/Makefile.am b/amixer/Makefile.am index b4526cabb..d92434ee0 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 +amixer_SOURCES = amixer.c volume_mapping.c noinst_HEADERS = amixer.h man_MANS = amixer.1 EXTRA_DIST = amixer.1 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 From a82058e6ecd186528ca870025d7655e74f44fd47 Mon Sep 17 00:00:00 2001 From: Ranjani Sridharan Date: Fri, 26 Mar 2021 14:44:13 -0700 Subject: [PATCH 56/88] topology: Add support for pre-processing Topology2.0 syntax This patch adds support for pre-processing the Topology2.0. The '-p' switch add pre-processing support during compilation and the '-P' switch is for converting the Topology2.0 configuration file into the existing syntax. Topology2.0 is a high level keyword extension on top of the existing ALSA conf topology format designed to: 1) Simplify the ALSA conf topology definitions by providing high level "classes" so topology designers need to write less config for common object definitions. 2) Allow simple reuse of objects. Define once and reuse (like M4) with the ability to alter objects configuration attributes from defaults. 3) Allow data type and value verification. This is not done today and frequently crops up in FW bug reports. Common Topology Classes ----------------------- Topology today has some common classes that are often reused throughout with slightly altered configurations. i.e. widgets (components), pipelines, dais and controls. Topology2.0 introduces the high level concept of reusable "class" like definition for that can be used to create topology objects. Common Topology Attributes -------------------------- Topology defines a lot of attributes per object with different types and constraints. Today there is no easy way to validate type or constraints and this can lead to many hard to find problems in FW at runtime. A new keyword "DefineAttribute" has been added to define attribute constraints such as min value, max value, enum_values etc. This then allows alsatplg to validate each topology object attribute. Topology Classes define the list of attributes that they use and whether the attribute is mandatory, can be overridden by parent users or is immutable. This also helps alsatplg emit the appropriate errors for attribute misuse. Class constructor attributes ---------------------------- Some attributes in the class definition are declared as constructor attributes and these will be used to construct the name of the object. For ex: for the host widget, the index and direction are constructor attributes and the name for the widget is derived as follows: host.1.playback or host.2.capture etc. Attribute Inheritance: ---------------------- One of the key features of Topology2.0 is how the attribute values are propagated from a parent object to a child object. For ex: a pipeline object can pass down the pipeline_id attribute to all its widgets. Inheritance is implicit when an object and its embedded child objects have matching names for a attribute/argument. Attribute values set explicitly in an object instance always has precedence over the values inherited from the parent object. Signed-off-by: Ranjani Sridharan 1 1 1 Signed-off-by: Jaroslav Kysela --- topology/Makefile.am | 4 +- topology/pre-processor.c | 175 +++++++++++++++++++++++++++++++++++++++ topology/topology.c | 78 ++++++++++++++++- topology/topology.h | 34 ++++++++ 4 files changed, 287 insertions(+), 4 deletions(-) create mode 100644 topology/pre-processor.c create mode 100644 topology/topology.h diff --git a/topology/Makefile.am b/topology/Makefile.am index a56c109c1..47f32f05a 100644 --- a/topology/Makefile.am +++ b/topology/Makefile.am @@ -8,7 +8,9 @@ endif %.1: %.rst rst2man $< > $@ -alsatplg_SOURCES = topology.c +alsatplg_SOURCES = topology.c pre-processor.c + +noinst_HEADERS = topology.h AM_CPPFLAGS = \ -Wall -I$(top_srcdir)/include diff --git a/topology/pre-processor.c b/topology/pre-processor.c new file mode 100644 index 000000000..212adfe30 --- /dev/null +++ b/topology/pre-processor.c @@ -0,0 +1,175 @@ +/* + 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 "gettext.h" +#include "topology.h" + +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; + + 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; + } + + /* TODO: Add support for parsing objects */ + } + } + + 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/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 From d508b1682a1b6805476c64cfc9ffce26d904f314 Mon Sep 17 00:00:00 2001 From: Ranjani Sridharan Date: Sat, 27 Mar 2021 12:33:33 -0700 Subject: [PATCH 57/88] topology: pre-processor: Add debug print helpers Add a couple of helper functions to print debug messages and the generated config. Signed-off-by: Ranjani Sridharan Signed-off-by: Jaroslav Kysela --- topology/Makefile.am | 2 +- topology/pre-processor.c | 25 +++++++++++++++++++++++++ topology/pre-processor.h | 28 ++++++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 topology/pre-processor.h diff --git a/topology/Makefile.am b/topology/Makefile.am index 47f32f05a..d7f7b09f6 100644 --- a/topology/Makefile.am +++ b/topology/Makefile.am @@ -10,7 +10,7 @@ endif alsatplg_SOURCES = topology.c pre-processor.c -noinst_HEADERS = topology.h +noinst_HEADERS = topology.h pre-processor.h AM_CPPFLAGS = \ -Wall -I$(top_srcdir)/include diff --git a/topology/pre-processor.c b/topology/pre-processor.c index 212adfe30..5ddd4f908 100644 --- a/topology/pre-processor.c +++ b/topology/pre-processor.c @@ -17,6 +17,8 @@ The full GNU General Public License is included in this distribution in the file called LICENSE.GPL. */ + +#include #include #include #include @@ -27,6 +29,29 @@ #include #include "gettext.h" #include "topology.h" +#include "pre-processor.h" + +#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) { diff --git a/topology/pre-processor.h b/topology/pre-processor.h new file mode 100644 index 000000000..9a7b6f140 --- /dev/null +++ b/topology/pre-processor.h @@ -0,0 +1,28 @@ +/* + * 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 "topology.h" + +#define DEBUG_MAX_LENGTH 256 + +/* debug helpers */ +void tplg_pp_debug(char *fmt, ...); +void tplg_pp_config_debug(struct tplg_pre_processor *tplg_pp, snd_config_t *cfg); +#endif From 94eaca13cebede3d99940b8742eae081e0a50671 Mon Sep 17 00:00:00 2001 From: Ranjani Sridharan Date: Fri, 23 Apr 2021 11:06:29 -0700 Subject: [PATCH 58/88] topology: pre-processor: Add a couple of config helpers Add a couple of helper functions for searching config by ID and creating and adding configs to a parent config. Signed-off-by: Ranjani Sridharan Signed-off-by: Jaroslav Kysela --- topology/pre-processor.c | 40 ++++++++++++++++++++++++++++++++++++++++ topology/pre-processor.h | 5 +++++ 2 files changed, 45 insertions(+) diff --git a/topology/pre-processor.c b/topology/pre-processor.c index 5ddd4f908..100c9ad22 100644 --- a/topology/pre-processor.c +++ b/topology/pre-processor.c @@ -31,6 +31,46 @@ #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; +} + #ifdef TPLG_DEBUG void tplg_pp_debug(char *fmt, ...) { diff --git a/topology/pre-processor.h b/topology/pre-processor.h index 9a7b6f140..cac464b55 100644 --- a/topology/pre-processor.h +++ b/topology/pre-processor.h @@ -25,4 +25,9 @@ /* debug helpers */ void tplg_pp_debug(char *fmt, ...); void tplg_pp_config_debug(struct tplg_pre_processor *tplg_pp, snd_config_t *cfg); + +/* 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); #endif From eb514c6bd7ff669ecad6548885be2545fc02fea5 Mon Sep 17 00:00:00 2001 From: Ranjani Sridharan Date: Fri, 23 Apr 2021 11:15:59 -0700 Subject: [PATCH 59/88] topology: pre-processor: Add a helper function to concat strings The pre-processor needs to concatinate strings separated by '.' for building object names from constructor attribute values and searching for configs with ID's containing strings separate by '.'. Add a helper function to concat strings in the specified input format. Signed-off-by: Ranjani Sridharan Signed-off-by: Jaroslav Kysela --- topology/pre-processor.c | 27 +++++++++++++++++++++++++++ topology/pre-processor.h | 2 ++ 2 files changed, 29 insertions(+) diff --git a/topology/pre-processor.c b/topology/pre-processor.c index 100c9ad22..3d50d8ce2 100644 --- a/topology/pre-processor.c +++ b/topology/pre-processor.c @@ -71,6 +71,33 @@ int tplg_config_make_add(snd_config_t **config, const char *id, snd_config_type_ 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, ...) { diff --git a/topology/pre-processor.h b/topology/pre-processor.h index cac464b55..d9f0f3ea8 100644 --- a/topology/pre-processor.h +++ b/topology/pre-processor.h @@ -30,4 +30,6 @@ void tplg_pp_config_debug(struct tplg_pre_processor *tplg_pp, snd_config_t *cfg) 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 From c832f4840416d3ae7cbdf9182d3abf447be3105d Mon Sep 17 00:00:00 2001 From: Ranjani Sridharan Date: Fri, 23 Apr 2021 12:23:19 -0700 Subject: [PATCH 60/88] topology: pre-process-class: Add helper function to look up class definition Add a helper function to look up the class definition for an object. ex: for an object instance, Object.Widget.pga.0{}, the function returns the config pointing to Class.Widget.pga{} in the input conf. Signed-off-by: Ranjani Sridharan Signed-off-by: Jaroslav Kysela --- topology/Makefile.am | 2 +- topology/pre-process-class.c | 68 ++++++++++++++++++++++++++++++++++++ topology/pre-processor.h | 3 ++ 3 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 topology/pre-process-class.c diff --git a/topology/Makefile.am b/topology/Makefile.am index d7f7b09f6..f97851a7a 100644 --- a/topology/Makefile.am +++ b/topology/Makefile.am @@ -8,7 +8,7 @@ endif %.1: %.rst rst2man $< > $@ -alsatplg_SOURCES = topology.c pre-processor.c +alsatplg_SOURCES = topology.c pre-processor.c pre-process-class.c noinst_HEADERS = topology.h pre-processor.h diff --git a/topology/pre-process-class.c b/topology/pre-process-class.c new file mode 100644 index 000000000..9e7f4fced --- /dev/null +++ b/topology/pre-process-class.c @@ -0,0 +1,68 @@ +/* + 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" + +/* + * 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; +} diff --git a/topology/pre-processor.h b/topology/pre-processor.h index d9f0f3ea8..8c32ce3b0 100644 --- a/topology/pre-processor.h +++ b/topology/pre-processor.h @@ -26,6 +26,9 @@ void tplg_pp_debug(char *fmt, ...); void tplg_pp_config_debug(struct tplg_pre_processor *tplg_pp, snd_config_t *cfg); +/* class helpers */ +snd_config_t *tplg_class_lookup(struct tplg_pre_processor *tplg_pp, snd_config_t *cfg); + /* 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, From 1422c09afdd79d8b1253707c7fb67256f579c408 Mon Sep 17 00:00:00 2001 From: Ranjani Sridharan Date: Fri, 23 Apr 2021 12:35:56 -0700 Subject: [PATCH 61/88] topology: pre-process-class: Add function to look up attribute definition in class Add a helper function look up attribute definition in the "DefineAttribute" config in the class definition. Signed-off-by: Ranjani Sridharan Signed-off-by: Jaroslav Kysela --- topology/pre-process-class.c | 25 +++++++++++++++++++++++++ topology/pre-processor.h | 2 ++ 2 files changed, 27 insertions(+) diff --git a/topology/pre-process-class.c b/topology/pre-process-class.c index 9e7f4fced..cd4c7c352 100644 --- a/topology/pre-process-class.c +++ b/topology/pre-process-class.c @@ -66,3 +66,28 @@ snd_config_t *tplg_class_lookup(struct tplg_pre_processor *tplg_pp, snd_config_t 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; +} diff --git a/topology/pre-processor.h b/topology/pre-processor.h index 8c32ce3b0..d7d1b1190 100644 --- a/topology/pre-processor.h +++ b/topology/pre-processor.h @@ -28,6 +28,8 @@ void tplg_pp_config_debug(struct tplg_pre_processor *tplg_pp, snd_config_t *cfg) /* 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); /* config helpers */ snd_config_t *tplg_find_config(snd_config_t *config, const char *name); From 7bf31094e299b3ef4e897c18e891e6546ee2e0c6 Mon Sep 17 00:00:00 2001 From: Ranjani Sridharan Date: Fri, 23 Apr 2021 13:13:01 -0700 Subject: [PATCH 62/88] topology: pre-process-class: Add functions to check attribute constraints Add helper functions to check if an attribute is mandatory, immutable or unique in the class definition. ex: for a host widget component, these are defined as follows: attributes { # # host objects instantiated within the same alsaconf node must have unique # direction attribute # unique "direction" mandatory [ "type" "stream_name" ] immutable [ "uuid" ] } Signed-off-by: Ranjani Sridharan Signed-off-by: Jaroslav Kysela --- topology/pre-process-class.c | 59 ++++++++++++++++++++++++++++++++++++ topology/pre-processor.h | 5 +++ 2 files changed, 64 insertions(+) diff --git a/topology/pre-process-class.c b/topology/pre-process-class.c index cd4c7c352..90333435f 100644 --- a/topology/pre-process-class.c +++ b/topology/pre-process-class.c @@ -26,6 +26,65 @@ #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 diff --git a/topology/pre-processor.h b/topology/pre-processor.h index d7d1b1190..a5cfa0acb 100644 --- a/topology/pre-processor.h +++ b/topology/pre-processor.h @@ -18,6 +18,8 @@ #define __PRE_PROCESSOR_H #include +#include +#include #include "topology.h" #define DEBUG_MAX_LENGTH 256 @@ -30,6 +32,9 @@ void tplg_pp_config_debug(struct tplg_pre_processor *tplg_pp, snd_config_t *cfg) 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); /* config helpers */ snd_config_t *tplg_find_config(snd_config_t *config, const char *name); From 0b0c16d4a7899c09126c5f12836a3c90ecebc099 Mon Sep 17 00:00:00 2001 From: Ranjani Sridharan Date: Fri, 23 Apr 2021 13:32:26 -0700 Subject: [PATCH 63/88] topology: pre-process-class: add funcion to get the name of the unique attribute in a class Every class must have a unique attribute that will be used to instantiate the object. The value provided for this attribute must be unique within the same alsaconf node for objects of the same class. Add a helper function to get the name of the attribute that must have a unique value in the object instance. For example, when instantiating 2 buffer widgets within a pipeline, they must be given unique instance attribute values as: Object.Widget.buffer.0{} and Object.Widget.buffer.1{}. Signed-off-by: Ranjani Sridharan Signed-off-by: Jaroslav Kysela --- topology/pre-process-class.c | 25 +++++++++++++++++++++++++ topology/pre-processor.h | 2 ++ 2 files changed, 27 insertions(+) diff --git a/topology/pre-process-class.c b/topology/pre-process-class.c index 90333435f..3d09a8627 100644 --- a/topology/pre-process-class.c +++ b/topology/pre-process-class.c @@ -150,3 +150,28 @@ snd_config_t *tplg_class_find_attribute_by_name(struct tplg_pre_processor *tplg_ 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; +} diff --git a/topology/pre-processor.h b/topology/pre-processor.h index a5cfa0acb..04e1cfcaf 100644 --- a/topology/pre-processor.h +++ b/topology/pre-processor.h @@ -35,6 +35,8 @@ snd_config_t *tplg_class_find_attribute_by_name(struct tplg_pre_processor *tplg_ 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); /* config helpers */ snd_config_t *tplg_find_config(snd_config_t *config, const char *name); From d8e9466e594dd62779bbc19eb2a379c79413f0d9 Mon Sep 17 00:00:00 2001 From: Ranjani Sridharan Date: Fri, 23 Apr 2021 13:36:48 -0700 Subject: [PATCH 64/88] topology: pre-process-class: function to get attribute type Add a helper function to get attribute type from the attribute definition and convert them to SND_CONFIG_TYPE_* values. When no type if provided for an attribute, type defaults to integer. Signed-off-by: Ranjani Sridharan Signed-off-by: Jaroslav Kysela --- topology/pre-process-class.c | 32 ++++++++++++++++++++++++++++++++ topology/pre-processor.h | 3 ++- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/topology/pre-process-class.c b/topology/pre-process-class.c index 3d09a8627..b039716cc 100644 --- a/topology/pre-process-class.c +++ b/topology/pre-process-class.c @@ -17,6 +17,7 @@ The full GNU General Public License is included in this distribution in the file called LICENSE.GPL. */ +#include #include #include #include @@ -175,3 +176,34 @@ const char *tplg_class_get_unique_attribute_name(struct tplg_pre_processor *tplg 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; +} diff --git a/topology/pre-processor.h b/topology/pre-processor.h index 04e1cfcaf..17a7dd569 100644 --- a/topology/pre-processor.h +++ b/topology/pre-processor.h @@ -37,11 +37,12 @@ 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); /* 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 From 963578af1e1ab617850a0848adf182521c8e3b94 Mon Sep 17 00:00:00 2001 From: Ranjani Sridharan Date: Fri, 23 Apr 2021 13:41:45 -0700 Subject: [PATCH 65/88] topology: pre-process-class: add function to look up token_ref for an attribute in class Some attributes may have the token_ref set which is used to look up the token value for the tuple data that is appended to the object's private data. For example, in the buffer widget object: DefineAttribute."size" { # Token reference and type token_ref "sof_tkn_buffer.word" } The token_ref must include the reference to the vendor token object name followed by the type of the tuple. Signed-off-by: Ranjani Sridharan Signed-off-by: Jaroslav Kysela --- topology/pre-process-class.c | 27 +++++++++++++++++++++++++++ topology/pre-processor.h | 2 ++ 2 files changed, 29 insertions(+) diff --git a/topology/pre-process-class.c b/topology/pre-process-class.c index b039716cc..909865ad0 100644 --- a/topology/pre-process-class.c +++ b/topology/pre-process-class.c @@ -207,3 +207,30 @@ snd_config_type_t tplg_class_get_attribute_type(struct tplg_pre_processor *tplg_ 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; +} diff --git a/topology/pre-processor.h b/topology/pre-processor.h index 17a7dd569..81063b770 100644 --- a/topology/pre-processor.h +++ b/topology/pre-processor.h @@ -39,6 +39,8 @@ const char *tplg_class_get_unique_attribute_name(struct tplg_pre_processor *tplg 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); /* config helpers */ snd_config_t *tplg_find_config(snd_config_t *config, const char *name); From e3ad68b185c1305cdf924b620ceb091e1b5bbe31 Mon Sep 17 00:00:00 2001 From: Ranjani Sridharan Date: Fri, 23 Apr 2021 13:46:24 -0700 Subject: [PATCH 66/88] topology: pre-process-class: add function to convert valid attribute values to integer tuple values Some attributes have valid values that need to be converted to integer tuple values before it is appended to the object's private data: For ex, the buffer widget object's "caps" attribute has the following definition: DefineAttribute."caps" { type "string" # Token reference and type token_ref "sof_tkn_buffer.word" constraints { value_ref "sof_tkn_mem" valid_values [ "dai" "host" "pass" "comp" ] tuple_values [ 113 113 113 65 ] } } Depending on the user input, the value string values for "caps" will be converted to the appropriate tuple values. Signed-off-by: Ranjani Sridharan Signed-off-by: Jaroslav Kysela --- topology/pre-process-class.c | 67 ++++++++++++++++++++++++++++++++++++ topology/pre-processor.h | 2 ++ 2 files changed, 69 insertions(+) diff --git a/topology/pre-process-class.c b/topology/pre-process-class.c index 909865ad0..f4f3b01fd 100644 --- a/topology/pre-process-class.c +++ b/topology/pre-process-class.c @@ -18,6 +18,7 @@ in the file called LICENSE.GPL. */ #include +#include #include #include #include @@ -234,3 +235,69 @@ const char *tplg_class_get_attribute_token_ref(struct tplg_pre_processor *tplg_p 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-processor.h b/topology/pre-processor.h index 81063b770..ce72e096b 100644 --- a/topology/pre-processor.h +++ b/topology/pre-processor.h @@ -41,6 +41,8 @@ snd_config_type_t tplg_class_get_attribute_type(struct tplg_pre_processor *tplg_ 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); From 4d413567b07f8f42a1e87d33464da31924e1d61c Mon Sep 17 00:00:00 2001 From: Ranjani Sridharan Date: Mon, 26 Apr 2021 09:34:48 -0700 Subject: [PATCH 67/88] topology: pre-process-object: Add support for pre-processing Objects Add support for pre-processing object instances in the input config. An object's attributes can be set in multiple places such as, within the object instance, default values in the class defnition, inherited from a parent object or explicitly set in a parent object. Before converting the object config into the relevant section in the existing syntax, all the attribute values must be consolidated into one place so that it is easy to verify if all mandatory attributes are set. Also, the name of the object will be constructed from the attributes defined in the attributes.constructor[] config in the class definition and the unique attribute's value must be set from the value passed in the object instance. This patch create a temporary config for each object instance and populates its unique attribute value. The rest of the steps will be added in the following patches. Signed-off-by: Ranjani Sridharan Signed-off-by: Jaroslav Kysela --- topology/Makefile.am | 2 +- topology/pre-process-object.c | 229 ++++++++++++++++++++++++++++++++++ topology/pre-processor.c | 6 +- topology/pre-processor.h | 6 + 4 files changed, 241 insertions(+), 2 deletions(-) create mode 100644 topology/pre-process-object.c diff --git a/topology/Makefile.am b/topology/Makefile.am index f97851a7a..8efbf4f35 100644 --- a/topology/Makefile.am +++ b/topology/Makefile.am @@ -8,7 +8,7 @@ endif %.1: %.rst rst2man $< > $@ -alsatplg_SOURCES = topology.c pre-processor.c pre-process-class.c +alsatplg_SOURCES = topology.c pre-processor.c pre-process-class.c pre-process-object.c noinst_HEADERS = topology.h pre-processor.h diff --git a/topology/pre-process-object.c b/topology/pre-process-object.c new file mode 100644 index 000000000..efcf48f78 --- /dev/null +++ b/topology/pre-process-object.c @@ -0,0 +1,229 @@ +/* + 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" + +/* 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 */ +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 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; +} + +/* 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 index 3d50d8ce2..0458c3cce 100644 --- a/topology/pre-processor.c +++ b/topology/pre-processor.c @@ -125,6 +125,7 @@ static int pre_process_config(struct tplg_pre_processor *tplg_pp, snd_config_t * 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"); @@ -156,7 +157,10 @@ static int pre_process_config(struct tplg_pre_processor *tplg_pp, snd_config_t * return -EINVAL; } - /* TODO: Add support for parsing objects */ + /* pre-process Object instance. Top-level object have no parent */ + err = tplg_pre_process_objects(tplg_pp, n2, NULL); + if (err < 0) + return err; } } diff --git a/topology/pre-processor.h b/topology/pre-processor.h index ce72e096b..5cb1727d1 100644 --- a/topology/pre-processor.h +++ b/topology/pre-processor.h @@ -28,6 +28,12 @@ void tplg_pp_debug(char *fmt, ...); void tplg_pp_config_debug(struct tplg_pre_processor *tplg_pp, snd_config_t *cfg); +/* 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); + /* 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, From 624c814c6565b4df942d1429eb3e2e86bea91ef4 Mon Sep 17 00:00:00 2001 From: Ranjani Sridharan Date: Mon, 26 Apr 2021 09:58:44 -0700 Subject: [PATCH 68/88] topology/pre-process-object: update object config with attributes Object attributes can be set in multiple places. Search for the attribute value in the following order: 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 Mandatory attributes must be found in one of the above, resulting in an error if not found. Signed-off-by: Ranjani Sridharan Signed-off-by: Jaroslav Kysela --- topology/pre-process-object.c | 194 +++++++++++++++++++++++++++++++++- 1 file changed, 193 insertions(+), 1 deletion(-) diff --git a/topology/pre-process-object.c b/topology/pre-process-object.c index efcf48f78..c7b1a32ac 100644 --- a/topology/pre-process-object.c +++ b/topology/pre-process-object.c @@ -30,6 +30,191 @@ #include "topology.h" #include "pre-processor.h" +/* 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; +} + +/* 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; + continue; +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; + continue; +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; + continue; +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; + continue; +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; + continue; +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; + } + } + + return 0; +} + /* set the attribute value by type */ static int tplg_set_attribute_value(snd_config_t *attr, const char *value) { @@ -158,8 +343,15 @@ static int tplg_build_object(struct tplg_pre_processor *tplg_pp, snd_config_t *n /* set unique attribute value */ ret = tplg_object_set_unique_attribute(tplg_pp, obj_local, class_cfg, id); - if (ret < 0) + 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; } From b2e85be668294e77d60055ecd4a077393cb2cf7f Mon Sep 17 00:00:00 2001 From: Ranjani Sridharan Date: Mon, 26 Apr 2021 10:04:54 -0700 Subject: [PATCH 69/88] topology: pre-process-object: check attribute validity Attributes can have constraints set for valid values, min or max values. If the attribute value is set in an object, the value must be validated against the set constraints. An example for attribute constraint would be: DefineAttribute."direction" { constraints { valid_values [ "playback" "capture" ] tuple_values [ 0 1 ] } } where the tuple_values array would translate the valid_values of playback as 0 and capture as 1. Signed-off-by: Ranjani Sridharan Signed-off-by: Jaroslav Kysela --- topology/pre-process-object.c | 205 +++++++++++++++++++++++++++++++++- 1 file changed, 200 insertions(+), 5 deletions(-) diff --git a/topology/pre-process-object.c b/topology/pre-process-object.c index c7b1a32ac..f738f72fa 100644 --- a/topology/pre-process-object.c +++ b/topology/pre-process-object.c @@ -30,6 +30,197 @@ #include "topology.h" #include "pre-processor.h" +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); +} + /* 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, @@ -140,7 +331,7 @@ static int tplg_object_update(struct tplg_pre_processor *tplg_pp, snd_config_t * ret = snd_config_search(obj_cfg, id, &attr); if (ret < 0) goto class; - continue; + goto validate; class: /* search for attributes value in class */ ret = tplg_object_copy_and_add_param(tplg_pp, obj_cfg, n, class_cfg); @@ -154,7 +345,7 @@ static int tplg_object_update(struct tplg_pre_processor *tplg_pp, snd_config_t * } else if (ret < 0) return ret; - continue; + goto validate; parent: /* search for attribute value in parent */ if (!parent) @@ -170,7 +361,7 @@ static int tplg_object_update(struct tplg_pre_processor *tplg_pp, snd_config_t * goto parent_object; else if (ret < 0) return ret; - continue; + goto validate; parent_object: if (!parent) goto parent_class; @@ -185,7 +376,7 @@ static int tplg_object_update(struct tplg_pre_processor *tplg_pp, snd_config_t * goto parent_class; else if (ret < 0) return ret; - continue; + goto validate; parent_class: if (!parent) goto check; @@ -204,12 +395,16 @@ static int tplg_object_update(struct tplg_pre_processor *tplg_pp, snd_config_t * goto check; else if (ret < 0) return ret; - continue; + 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; From 1ca07d8dbca3194e9835e547476c0574b583b169 Mon Sep 17 00:00:00 2001 From: Ranjani Sridharan Date: Mon, 26 Apr 2021 10:10:03 -0700 Subject: [PATCH 70/88] topology: pre-process-object: construct object name from its constructor attributes An object's name is derived from its constructor attribute values separated by '.'. For example, the name for the host widget objects is derived from its index and direction attribute values as follows: Object.Widget.host."playback" { index 2 } The name for the host widget object would be host.2.playback. Alternatively, if the object has a name attribute, the class definition may skip the constructor attributes and the name attribute value will be used instead. Signed-off-by: Ranjani Sridharan Signed-off-by: Jaroslav Kysela --- topology/pre-process-object.c | 109 +++++++++++++++++++++++++++++++++- 1 file changed, 108 insertions(+), 1 deletion(-) diff --git a/topology/pre-process-object.c b/topology/pre-process-object.c index f738f72fa..3793ab5e9 100644 --- a/topology/pre-process-object.c +++ b/topology/pre-process-object.c @@ -410,6 +410,106 @@ static int tplg_object_update(struct tplg_pre_processor *tplg_pp, snd_config_t * 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) { @@ -545,8 +645,15 @@ static int tplg_build_object(struct tplg_pre_processor *tplg_pp, snd_config_t *n /* update object attributes and validate them */ ret = tplg_object_update(tplg_pp, new_obj, parent); - if (ret < 0) + 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; } From d999c267d3acf08211e10416ee3dfcb66619304b Mon Sep 17 00:00:00 2001 From: Ranjani Sridharan Date: Mon, 26 Apr 2021 11:19:31 -0700 Subject: [PATCH 71/88] topology: pre-process-object: Add support for processing Manifest object The pre-processor converts the Topology2.0 objects into the relevant sections by looking for attributes defined in the template config for the section and reading the attribute values from the object instance config. The structure struct build_function_map contains the mapping of the build function to use for each object based on the type and name for the class that the object belongs to. The manifest object is the simplest with no attributes. So, the build function simply creates a new Section called SectionManifest which will be populated with the data section in the following patches. Signed-off-by: Ranjani Sridharan Signed-off-by: Jaroslav Kysela --- topology/pre-process-object.c | 262 +++++++++++++++++++++++++++++++++- topology/pre-processor.h | 28 ++++ 2 files changed, 288 insertions(+), 2 deletions(-) diff --git a/topology/pre-process-object.c b/topology/pre-process-object.c index 3793ab5e9..609e1ec12 100644 --- a/topology/pre-process-object.c +++ b/topology/pre-process-object.c @@ -30,6 +30,58 @@ #include "topology.h" #include "pre-processor.h" +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; @@ -221,6 +273,25 @@ static bool tplg_object_is_attribute_valid(struct tplg_pre_processor *tplg_pp, 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, @@ -238,6 +309,180 @@ static snd_config_t *tplg_object_lookup_in_config(struct tplg_pre_processor *tpl return obj_cfg; } +/* 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; + + return tplg_build_object_from_template(tplg_pp, obj_cfg, &wtop, NULL, false); +} + +const struct build_function_map object_build_map[] = { + {"Base", "manifest", "SectionManifest", &tplg_build_generic_object, NULL}, +}; + +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; +} + /* 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, @@ -619,6 +864,8 @@ static int tplg_build_object(struct tplg_pre_processor *tplg_pp, snd_config_t *n 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; @@ -652,10 +899,21 @@ static int tplg_build_object(struct tplg_pre_processor *tplg_pp, snd_config_t *n /* construct object name using class constructor */ ret = tplg_construct_object_name(tplg_pp, obj_local, class_cfg); - if (ret < 0) + if (ret < 0) { SNDERR("Failed to construct object name for %s\n", id); + return ret; + } - return ret; + tplg_pp_config_debug(tplg_pp, obj_local); + + /* nothing to do if object is not supported */ + map = tplg_object_get_map(tplg_pp, new_obj); + if (!map) + return 0; + + /* build the object and save the sections to the output config */ + builder = map->builder; + return builder(tplg_pp, new_obj, parent); } /* create top-level topology objects */ diff --git a/topology/pre-processor.h b/topology/pre-processor.h index 5cb1727d1..5599ca2f0 100644 --- a/topology/pre-processor.h +++ b/topology/pre-processor.h @@ -23,16 +23,44 @@ #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); + /* 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); /* class helpers */ snd_config_t *tplg_class_lookup(struct tplg_pre_processor *tplg_pp, snd_config_t *cfg); From 571d1fe8718b4b08273d452ccca22415207b2879 Mon Sep 17 00:00:00 2001 From: Ranjani Sridharan Date: Mon, 26 Apr 2021 11:30:07 -0700 Subject: [PATCH 72/88] topology: pre-process-object: add data section for attributes with token reference Objects that have attributes with token_ref need to have separate SectionData and SectionVendorTuples for each unique token_ref based on the attribute_sets in the object's attribute_set_list. Add the tplg_pp_add_object_data() function to add the data[] refs in the object and also the respective SectionData and SectionVendorTuples. For example for the pga object: Object.Widget.pga."0" { pipeline_id 2 format s24le type pga no_pm 1 uuid "7e:67:7e:b7:f4:5f:88:41:af:14:fb:a8:bd:bf:8" period_sink_count 2 period_source_count 2 ramp_step_ms 250 ramp_step_type "linear" mixer.0 { .... } mixer.0.name "2 Master Playback Volume" } The following sections will be added: SectionWidget.'pga.2.0' { index 2 type pga no_pm 1 mixer [ "2 Master Playback Volume" ] bytes [ ] data [ "pga.2.0.sof_tkn_comp.word" "pga.2.0.sof_tkn_comp.string" "pga.2.0.sof_tkn_comp.uuid" "pga.2.0.sof_tkn_volume.word" ] } SectionData."pga.2.0.sof_tkn_comp.word" { tuples "pga.2.0.sof_tkn_comp.word" } SectionData."pga.2.0.sof_tkn_comp.string" { tuples "pga.2.0.sof_tkn_comp.string" } SectionData."pga.2.0.sof_tkn_comp.uuid" { tuples "pga.2.0.sof_tkn_comp.uuid" } SectionData."pga.2.0.sof_tkn_volume.word" { tuples "pga.2.0.sof_tkn_volume.word" } SectionVendorTuples."pga.2.0.sof_tkn_comp.word" { tokens "sof_tkn_comp" tuples."word" { period_source_count "2" period_sink_count "2" } } SectionVendorTuples."pga.2.0.sof_tkn_comp.string" { tokens "sof_tkn_comp" tuples."string" { format "s24le" } } SectionVendorTuples."pga.2.0.sof_tkn_comp.uuid" { tokens "sof_tkn_comp" tuples."uuid" { uuid "7e:67:7e:b7:f4:5f:88:41:af:14:fb:a8:bd:bf:8" } } SectionVendorTuples."pga.2.0.sof_tkn_volume.word" { tokens "sof_tkn_volume" tuples."word" { ramp_step_ms "250" ramp_step_type "0" } } Note that the ramp_step_type of "linear" is converted to the tuple value 0. Signed-off-by: Ranjani Sridharan Signed-off-by: Jaroslav Kysela --- topology/pre-process-object.c | 259 +++++++++++++++++++++++++++++++++- 1 file changed, 256 insertions(+), 3 deletions(-) diff --git a/topology/pre-process-object.c b/topology/pre-process-object.c index 609e1ec12..249bd7dfc 100644 --- a/topology/pre-process-object.c +++ b/topology/pre-process-object.c @@ -309,6 +309,247 @@ static snd_config_t *tplg_object_lookup_in_config(struct tplg_pre_processor *tpl 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) @@ -440,8 +681,22 @@ static int tplg_build_generic_object(struct tplg_pre_processor *tplg_pp, snd_con 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; - return tplg_build_object_from_template(tplg_pp, obj_cfg, &wtop, NULL, false); + 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 build_function_map object_build_map[] = { @@ -904,8 +1159,6 @@ static int tplg_build_object(struct tplg_pre_processor *tplg_pp, snd_config_t *n return ret; } - tplg_pp_config_debug(tplg_pp, obj_local); - /* nothing to do if object is not supported */ map = tplg_object_get_map(tplg_pp, new_obj); if (!map) From 48fef7a811e5e95a405a80ed9c256b489f605bc2 Mon Sep 17 00:00:00 2001 From: Ranjani Sridharan Date: Mon, 26 Apr 2021 11:33:12 -0700 Subject: [PATCH 73/88] topology: pre-process-obejct: add helper function to get the section config Add a helper function to retrieve the config node pointing to the section name for a given object. For ex: for the object, Object.Widget.pga.1{}, the function returns the config with id, "SectionWidget" in the output config. Signed-off-by: Ranjani Sridharan Signed-off-by: Jaroslav Kysela --- topology/pre-process-object.c | 18 ++++++++++++++++++ topology/pre-processor.h | 1 + 2 files changed, 19 insertions(+) diff --git a/topology/pre-process-object.c b/topology/pre-process-object.c index 249bd7dfc..66116b42e 100644 --- a/topology/pre-process-object.c +++ b/topology/pre-process-object.c @@ -738,6 +738,24 @@ static const struct build_function_map *tplg_object_get_map(struct tplg_pre_proc 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, diff --git a/topology/pre-processor.h b/topology/pre-processor.h index 5599ca2f0..8934e2f60 100644 --- a/topology/pre-processor.h +++ b/topology/pre-processor.h @@ -61,6 +61,7 @@ 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); From cb65ce0195ed30f2f7ce18c1c38fd27f12a9fcda Mon Sep 17 00:00:00 2001 From: Ranjani Sridharan Date: Mon, 26 Apr 2021 11:48:43 -0700 Subject: [PATCH 74/88] topology: pre-process-object: Add support for data objects Pre-process data objects, create the SectionData and update the parent object with the reference to the object. For example, the following object instance: Object.Base.data."SOF_ABI" { bytes "0x03,0x12,0x01" } would update the SectionManifest as follows: SectionManifest."sof_manifest" { data [ "SOF_ABI" ] } Signed-off-by: Ranjani Sridharan Signed-off-by: Jaroslav Kysela --- topology/pre-process-object.c | 104 ++++++++++++++++++++++++++++++++++ topology/pre-processor.h | 3 + 2 files changed, 107 insertions(+) diff --git a/topology/pre-process-object.c b/topology/pre-process-object.c index 66116b42e..0777e8a58 100644 --- a/topology/pre-process-object.c +++ b/topology/pre-process-object.c @@ -30,6 +30,105 @@ #include "topology.h" #include "pre-processor.h" +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 */ + 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) @@ -699,8 +798,13 @@ static int tplg_build_generic_object(struct tplg_pre_processor *tplg_pp, snd_con return ret; } +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}, }; static const struct build_function_map *tplg_object_get_map(struct tplg_pre_processor *tplg_pp, diff --git a/topology/pre-processor.h b/topology/pre-processor.h index 8934e2f60..c6d2df812 100644 --- a/topology/pre-processor.h +++ b/topology/pre-processor.h @@ -53,6 +53,8 @@ void tplg_pp_config_debug(struct tplg_pre_processor *tplg_pp, snd_config_t *cfg) 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_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, @@ -83,5 +85,6 @@ long tplg_class_attribute_valid_tuple_value(struct tplg_pre_processor *tplg_pp, 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 From d271972177947b6c7b4ad4bc3d58c4b36255063a Mon Sep 17 00:00:00 2001 From: Ranjani Sridharan Date: Mon, 26 Apr 2021 11:55:22 -0700 Subject: [PATCH 75/88] topology: pre-process-base: add support for VendorToken objects Add support for pre-processing VendorToken objects. For ex: Object.Base.VendorToken."sof_tkn_dai" { dmac_config 153 dai_type 154 index 155 direction 156 } would be converted to: SectionVendorTokens."sof_tkn_dai" { dmac_config 153 dai_type 154 index 155 direction 156 } Signed-off-by: Ranjani Sridharan Signed-off-by: Jaroslav Kysela --- topology/pre-process-object.c | 49 +++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/topology/pre-process-object.c b/topology/pre-process-object.c index 0777e8a58..6988f8edf 100644 --- a/topology/pre-process-object.c +++ b/topology/pre-process-object.c @@ -30,6 +30,54 @@ #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) { @@ -805,6 +853,7 @@ const struct config_template_items data_config = { 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", "VendorToken", "SectionVendorTokens", &tplg_build_vendor_token_object, NULL}, }; static const struct build_function_map *tplg_object_get_map(struct tplg_pre_processor *tplg_pp, From 032d1250c7225ec2723f598b1d61d37dba0527ee Mon Sep 17 00:00:00 2001 From: Ranjani Sridharan Date: Mon, 26 Apr 2021 12:45:01 -0700 Subject: [PATCH 76/88] topology: pre-process-dapm: Add support for DAPM Widget objects Add support for pre-processing DAPM widget opbects. For ex: Object.Widget.pga."0" { pipeline_id 1 no_pm true type pga } will be converted to: SectionWidget.'pga.0' { index 1 type pga no_pm 1 } Signed-off-by: Ranjani Sridharan Signed-off-by: Jaroslav Kysela --- topology/pre-process-object.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/topology/pre-process-object.c b/topology/pre-process-object.c index 6988f8edf..22f4d484a 100644 --- a/topology/pre-process-object.c +++ b/topology/pre-process-object.c @@ -846,6 +846,12 @@ static int tplg_build_generic_object(struct tplg_pre_processor *tplg_pp, snd_con return ret; } +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"} }; @@ -854,6 +860,7 @@ 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", "VendorToken", "SectionVendorTokens", &tplg_build_vendor_token_object, NULL}, + {"Widget", "", "SectionWidget", &tplg_build_generic_object, &widget_config}, }; static const struct build_function_map *tplg_object_get_map(struct tplg_pre_processor *tplg_pp, From 96b5e5a87536f57566fd59d89789a243f82e890e Mon Sep 17 00:00:00 2001 From: Ranjani Sridharan Date: Mon, 26 Apr 2021 12:49:51 -0700 Subject: [PATCH 77/88] topology: pre-process-dapm: Add support for tlv objects Add support for pre-processing TLV objects For example: Object.Base.tlv."vtlv_m64s2" {} will be converted to: SectionTLV.'vtlv_m64s2' {} And the mixer controle section will be updated to add the reference to the tlv object. Signed-off-by: Ranjani Sridharan Signed-off-by: Jaroslav Kysela --- topology/Makefile.am | 3 ++- topology/pre-process-dapm.c | 47 +++++++++++++++++++++++++++++++++++ topology/pre-process-object.c | 13 ++++++++++ topology/pre-processor.h | 2 ++ 4 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 topology/pre-process-dapm.c diff --git a/topology/Makefile.am b/topology/Makefile.am index 8efbf4f35..7c9392cc3 100644 --- a/topology/Makefile.am +++ b/topology/Makefile.am @@ -8,7 +8,8 @@ endif %.1: %.rst rst2man $< > $@ -alsatplg_SOURCES = topology.c pre-processor.c pre-process-class.c pre-process-object.c +alsatplg_SOURCES = topology.c pre-processor.c pre-process-class.c pre-process-object.c \ + pre-process-dapm.c noinst_HEADERS = topology.h pre-processor.h diff --git a/topology/pre-process-dapm.c b/topology/pre-process-dapm.c new file mode 100644 index 000000000..372ac6561 --- /dev/null +++ b/topology/pre-process-dapm.c @@ -0,0 +1,47 @@ +/* + 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_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); +} diff --git a/topology/pre-process-object.c b/topology/pre-process-object.c index 22f4d484a..64e1689f9 100644 --- a/topology/pre-process-object.c +++ b/topology/pre-process-object.c @@ -113,6 +113,18 @@ int tplg_parent_update(struct tplg_pre_processor *tplg_pp, snd_config_t *parent, 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, @@ -859,6 +871,7 @@ const struct config_template_items data_config = { 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", "VendorToken", "SectionVendorTokens", &tplg_build_vendor_token_object, NULL}, {"Widget", "", "SectionWidget", &tplg_build_generic_object, &widget_config}, }; diff --git a/topology/pre-processor.h b/topology/pre-processor.h index c6d2df812..34fb72362 100644 --- a/topology/pre-processor.h +++ b/topology/pre-processor.h @@ -53,6 +53,8 @@ void tplg_pp_config_debug(struct tplg_pre_processor *tplg_pp, snd_config_t *cfg) 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_parent_update(struct tplg_pre_processor *tplg_pp, snd_config_t *parent, const char *section_name, const char *item_name); From 082015dc95b1122e86ec13c3d0ded6eb99b022e6 Mon Sep 17 00:00:00 2001 From: Ranjani Sridharan Date: Mon, 26 Apr 2021 12:55:26 -0700 Subject: [PATCH 78/88] topology: pre-process-dapm: add support for scale/ops/channel objects Add support for pre-processing scale/ops/channel objects and adding the converted config to the relevant sections. For ex: Object.Base.channel."fl" { shift 0 reg 1 } Object.Base.channel."fr" { reg 1 shift 1 } Will be converted to: channel { fl { reg 1 shift 0 } fr { reg 1 shift 1 } } And added to the SectionControlMixer that this object belongs to. Signed-off-by: Ranjani Sridharan Signed-off-by: Jaroslav Kysela --- topology/pre-process-dapm.c | 47 +++++++++++++++++++++++++++++++++++ topology/pre-process-object.c | 17 +++++++++++++ topology/pre-processor.h | 6 +++++ 3 files changed, 70 insertions(+) diff --git a/topology/pre-process-dapm.c b/topology/pre-process-dapm.c index 372ac6561..fa1b64cf0 100644 --- a/topology/pre-process-dapm.c +++ b/topology/pre-process-dapm.c @@ -26,6 +26,53 @@ #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) { diff --git a/topology/pre-process-object.c b/topology/pre-process-object.c index 64e1689f9..c7c23ff65 100644 --- a/topology/pre-process-object.c +++ b/topology/pre-process-object.c @@ -858,6 +858,19 @@ static int tplg_build_generic_object(struct tplg_pre_processor *tplg_pp, snd_con return ret; } +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"}, @@ -872,6 +885,10 @@ 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}, {"Widget", "", "SectionWidget", &tplg_build_generic_object, &widget_config}, }; diff --git a/topology/pre-processor.h b/topology/pre-processor.h index 34fb72362..1e1bca5c8 100644 --- a/topology/pre-processor.h +++ b/topology/pre-processor.h @@ -55,6 +55,12 @@ int tplg_build_object_from_template(struct tplg_pre_processor *tplg_pp, snd_conf 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_parent_update(struct tplg_pre_processor *tplg_pp, snd_config_t *parent, const char *section_name, const char *item_name); From 79033ceae4c513b837a30a72bfb297697b2fa68e Mon Sep 17 00:00:00 2001 From: Ranjani Sridharan Date: Mon, 26 Apr 2021 13:03:57 -0700 Subject: [PATCH 79/88] topology: pre-process-dapm: add support for widget control objects Add support for pre-processing mixer and byte control objects. For ex: a pga widget with a mixer control as follows: Object.pga"0" { ... mixer.0 { index 2 max 32 name "2 MasterPlaybackControl" Object.Base.channel."fl" { shift 0 } Object.Base.channel."fr" { } Object.Base.tlv."vtlv_m64s2" { Object.Base.scale."m64s2" { mute 1 } } Object.Base.ops."ctl" { info "volsw" #256 binds the mixer control to volume get/put handlers get 256 put 256 } access [ read_write tlv_read ] } } Would be converted to: SectionControlMixer.'2 Master Playback Volume' { index 2 max 32 channel { fl { reg 1 } fr { reg 1 shift 1 } } tlv "vtlv_m64s2" ops.0 { info volsw get 256 put 256 } access [ read_write tlv_read ] } and the SectionWidget for pga.2.0 would be updated to add the mixer references as follows: SectionWidget.'pga.2.0' { ... mixer [ "2 Master Playback Volume" ] } Signed-off-by: Ranjani Sridharan Signed-off-by: Jaroslav Kysela --- topology/pre-process-dapm.c | 37 +++++++++++++++++++++++++++++++++++ topology/pre-process-object.c | 13 ++++++++++++ topology/pre-processor.h | 4 ++++ 3 files changed, 54 insertions(+) diff --git a/topology/pre-process-dapm.c b/topology/pre-process-dapm.c index fa1b64cf0..63dd1fba4 100644 --- a/topology/pre-process-dapm.c +++ b/topology/pre-process-dapm.c @@ -92,3 +92,40 @@ int tplg_build_tlv_object(struct tplg_pre_processor *tplg_pp, snd_config_t *obj_ 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"); +} diff --git a/topology/pre-process-object.c b/topology/pre-process-object.c index c7c23ff65..8e49be8f0 100644 --- a/topology/pre-process-object.c +++ b/topology/pre-process-object.c @@ -858,6 +858,15 @@ static int tplg_build_generic_object(struct tplg_pre_processor *tplg_pp, snd_con return ret; } +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"}, }; @@ -891,6 +900,10 @@ const struct build_function_map object_build_map[] = { {"Base", "channel", "channel", &tplg_build_channel_object, &channel_config}, {"Base", "VendorToken", "SectionVendorTokens", &tplg_build_vendor_token_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}, }; static const struct build_function_map *tplg_object_get_map(struct tplg_pre_processor *tplg_pp, diff --git a/topology/pre-processor.h b/topology/pre-processor.h index 1e1bca5c8..e89fad6bc 100644 --- a/topology/pre-processor.h +++ b/topology/pre-processor.h @@ -61,6 +61,10 @@ int tplg_build_ops_object(struct tplg_pre_processor *tplg_pp, snd_config_t *obj_ 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_parent_update(struct tplg_pre_processor *tplg_pp, snd_config_t *parent, const char *section_name, const char *item_name); From 758e4dba8130fe7824c04bbdfd3c9bfb3668803c Mon Sep 17 00:00:00 2001 From: Ranjani Sridharan Date: Mon, 26 Apr 2021 13:07:00 -0700 Subject: [PATCH 80/88] topology: pre-process-dapm: add support for route objects DAPM route objects such as: Object.Base.route."1" { source "dai.SSP.0.dai.capture" sink "buffer.2.1" } will be converted to: SectionGraph."Endpoint.route.1" { index 0 lines [ "dai.SSP.0.capture, , buffer.2.1" ] } If the source/sink names are references to objects within a parent pipeline object, the index attribute value can be skipped and it will be populated when the object is pre-processed Object.Pipeline.volume-capture."1" { Object.Base.route."1" { source "pga..0" sink "buffer..0" } } The reference pga..0 will need to be resolved to get the widget name pga.1.0 and buffer..0 will be resolved to buffer.1.0 before creating the SectionGraph as follows: SectionGraph."volume-capture.1.route.1" { index 2 lines [ "pga.1.0, , buffer.1.0" ] } Signed-off-by: Ranjani Sridharan Signed-off-by: Jaroslav Kysela --- topology/pre-process-dapm.c | 287 ++++++++++++++++++++++++++++++++++ topology/pre-process-object.c | 1 + topology/pre-processor.h | 2 + 3 files changed, 290 insertions(+) diff --git a/topology/pre-process-dapm.c b/topology/pre-process-dapm.c index 63dd1fba4..d2b3b7aa3 100644 --- a/topology/pre-process-dapm.c +++ b/topology/pre-process-dapm.c @@ -129,3 +129,290 @@ int tplg_build_bytes_control(struct tplg_pre_processor *tplg_pp, snd_config_t *o { 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 index 8e49be8f0..1c8988d4c 100644 --- a/topology/pre-process-object.c +++ b/topology/pre-process-object.c @@ -899,6 +899,7 @@ const struct build_function_map object_build_map[] = { {"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", "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}, diff --git a/topology/pre-processor.h b/topology/pre-processor.h index e89fad6bc..9c311075f 100644 --- a/topology/pre-processor.h +++ b/topology/pre-processor.h @@ -65,6 +65,8 @@ int tplg_build_mixer_control(struct tplg_pre_processor *tplg_pp, snd_config_t *o 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_parent_update(struct tplg_pre_processor *tplg_pp, snd_config_t *parent, const char *section_name, const char *item_name); From b0be2350244bd427aef448bf79336ee0f70648e0 Mon Sep 17 00:00:00 2001 From: Ranjani Sridharan Date: Mon, 26 Apr 2021 13:12:43 -0700 Subject: [PATCH 81/88] topology: pre-process-dai: add support for PCM and BE DAI objects Add support for pre-processing PCM and BE DAI objects: Object.PCM.pcm."0" { name "Port0" direction "duplex" } will be converted to: SectionPCM.'Port0' {} The capabilities and dai configs will be added those objects are pre-processed. An ex of DAI object would be: Object.Dai.SSP."0" { direction "duplex" stream_name "NoCodec-0" id 0 default_hw_conf_id 0 format "s24le" quirks "lbm_mode" sample_bits 24 } converted to: SectionBE { 'SSP.0.duplex' { id 0 stream_name NoCodec-0 default_hw_conf_id 0 data [ 'SSP.0.duplex.sof_tkn_intel_ssp.word' 'SSP.0.duplex.sof_tkn_dai.word' 'SSP.0.duplex.sof_tkn_dai.string' 'SSP.0.duplex.sof_tkn_intel_ssp.short' ] } Signed-off-by: Ranjani Sridharan Signed-off-by: Jaroslav Kysela --- topology/pre-process-object.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/topology/pre-process-object.c b/topology/pre-process-object.c index 1c8988d4c..5a8f472db 100644 --- a/topology/pre-process-object.c +++ b/topology/pre-process-object.c @@ -858,6 +858,17 @@ static int tplg_build_generic_object(struct tplg_pre_processor *tplg_pp, snd_con return ret; } +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"} @@ -905,6 +916,8 @@ const struct build_function_map object_build_map[] = { &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}, }; static const struct build_function_map *tplg_object_get_map(struct tplg_pre_processor *tplg_pp, From 4bc386bb715ea303dfffe46d1da4d242ca1d3914 Mon Sep 17 00:00:00 2001 From: Ranjani Sridharan Date: Mon, 26 Apr 2021 13:26:08 -0700 Subject: [PATCH 82/88] topology: pre-process-dai: add support for hwcfg objects Add supprt for hwcfg objects: For ex: Object.Base.hw_config."SSP0 hw_config 0" { id 0 mclk_freq 24000000 bclk_freq 4800000 tdm_slot_width 25 } would get converted to: SectionHWConfig { 'SSP0 hw_config 0' { id 0 format I2S bclk codec_consumer bclk_freq 4800000 fsync codec_consumer fsync_freq 48000 mclk codec_mclk_in mclk_freq 24000000 tdm_slots 2 tdm_slot_width 25 tx_slots 3 rx_slots 3 } } and the corresponding SectionBE will be updated with the hwcfgs reference as: hw_configs [ 'SSP0 hw_config 0' ] Signed-off-by: Ranjani Sridharan Signed-off-by: Jaroslav Kysela --- topology/Makefile.am | 2 +- topology/pre-process-dai.c | 47 +++++++++++++++++++++++++++++++++++ topology/pre-process-object.c | 9 +++++++ topology/pre-processor.h | 2 ++ 4 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 topology/pre-process-dai.c diff --git a/topology/Makefile.am b/topology/Makefile.am index 7c9392cc3..7f77fdd27 100644 --- a/topology/Makefile.am +++ b/topology/Makefile.am @@ -9,7 +9,7 @@ endif rst2man $< > $@ alsatplg_SOURCES = topology.c pre-processor.c pre-process-class.c pre-process-object.c \ - pre-process-dapm.c + pre-process-dapm.c pre-process-dai.c noinst_HEADERS = topology.h pre-processor.h diff --git a/topology/pre-process-dai.c b/topology/pre-process-dai.c new file mode 100644 index 000000000..a8e790194 --- /dev/null +++ b/topology/pre-process-dai.c @@ -0,0 +1,47 @@ +/* + 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); +} diff --git a/topology/pre-process-object.c b/topology/pre-process-object.c index 5a8f472db..cbbdd6347 100644 --- a/topology/pre-process-object.c +++ b/topology/pre-process-object.c @@ -858,6 +858,13 @@ static int tplg_build_generic_object(struct tplg_pre_processor *tplg_pp, snd_con return ret; } +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"}, @@ -910,6 +917,8 @@ const struct build_function_map object_build_map[] = { {"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", "route", "SectionGraph", &tplg_build_dapm_route_object, NULL}, {"Widget", "", "SectionWidget", &tplg_build_generic_object, &widget_config}, {"Control", "mixer", "SectionControlMixer", &tplg_build_mixer_control, diff --git a/topology/pre-processor.h b/topology/pre-processor.h index 9c311075f..fd6e11531 100644 --- a/topology/pre-processor.h +++ b/topology/pre-processor.h @@ -67,6 +67,8 @@ int tplg_build_bytes_control(struct tplg_pre_processor *tplg_pp, snd_config_t *o 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_parent_update(struct tplg_pre_processor *tplg_pp, snd_config_t *parent, const char *section_name, const char *item_name); From 3719c80a4dd33fe4b01beb69e85a33fb7cea5d6b Mon Sep 17 00:00:00 2001 From: Ranjani Sridharan Date: Mon, 26 Apr 2021 13:31:00 -0700 Subject: [PATCH 83/88] topology: pre-process-dai: add support for fe_dai objects Add support for fe_dai objects: For ex: Object.PCM.pcm."0" { name "Port0" direction "duplex" Object.Base.fe_dai."Port 0" {} } will be converted to update the SectionPCM as follows: SectionPCM { Port0 { id 0 dai { 'Port 0' { id 0 } } } Signed-off-by: Ranjani Sridharan Signed-off-by: Jaroslav Kysela --- topology/pre-process-dai.c | 6 ++++++ topology/pre-process-object.c | 5 +++++ topology/pre-processor.h | 4 ++++ 3 files changed, 15 insertions(+) diff --git a/topology/pre-process-dai.c b/topology/pre-process-dai.c index a8e790194..6ff378d3e 100644 --- a/topology/pre-process-dai.c +++ b/topology/pre-process-dai.c @@ -45,3 +45,9 @@ int tplg_build_hw_cfg_object(struct tplg_pre_processor *tplg_pp, 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); +} diff --git a/topology/pre-process-object.c b/topology/pre-process-object.c index cbbdd6347..d5359cb3c 100644 --- a/topology/pre-process-object.c +++ b/topology/pre-process-object.c @@ -858,6 +858,10 @@ static int tplg_build_generic_object(struct tplg_pre_processor *tplg_pp, snd_con return ret; } +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", @@ -919,6 +923,7 @@ const struct build_function_map object_build_map[] = { {"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, diff --git a/topology/pre-processor.h b/topology/pre-processor.h index fd6e11531..6f4eb4c5e 100644 --- a/topology/pre-processor.h +++ b/topology/pre-processor.h @@ -69,6 +69,10 @@ int tplg_build_dapm_route_object(struct tplg_pre_processor *tplg_pp, snd_config_ 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_parent_update(struct tplg_pre_processor *tplg_pp, snd_config_t *parent, const char *section_name, const char *item_name); From df6cfa77e374971162e032f224fab0dc2cec5c08 Mon Sep 17 00:00:00 2001 From: Ranjani Sridharan Date: Mon, 26 Apr 2021 13:34:26 -0700 Subject: [PATCH 84/88] topology: pre-process-dai: add support for pcm_caps objects Add support for processing pcm_caps objects. For ex: Object.PCM.pcm."0" { name "Port0" direction "duplex" Object.Base.fe_dai."Port 0" {} Object.PCM.pcm_caps."playback" { name "Port0 Playback" } Object.PCM.pcm_caps."capture" { name "Port0 Capture" } } Would convert into: SectionPCMCapabilities { 'Port0 Playback' { formats 'S32_LE,S24_LE,S16_LE' rate_min 48000 rate_max 48000 channels_min 2 channels_max 2 periods_min 2 periods_max 16 period_size_min 192 period_size_max 16384 buffer_size_min 65536 buffer_size_max 65536 } 'Port0 Capture' { formats 'S32_LE,S24_LE,S16_LE' rate_min 48000 rate_max 48000 channels_min 2 channels_max 2 periods_min 2 periods_max 16 period_size_min 192 period_size_max 16384 buffer_size_min 65536 buffer_size_max 65536 } } and the SectionPCM updated as follows: SectionPCM { Port0 { id 0 dai { 'Port 0' { id 0 } } pcm { playback { capabilities 'Port0 Playback' } capture { capabilities 'Port0 Capture' } } } } Signed-off-by: Ranjani Sridharan Signed-off-by: Jaroslav Kysela --- topology/pre-process-dai.c | 87 +++++++++++++++++++++++++++++++++++ topology/pre-process-object.c | 9 ++++ topology/pre-processor.h | 2 + 3 files changed, 98 insertions(+) diff --git a/topology/pre-process-dai.c b/topology/pre-process-dai.c index 6ff378d3e..1158afd39 100644 --- a/topology/pre-process-dai.c +++ b/topology/pre-process-dai.c @@ -51,3 +51,90 @@ int tplg_build_fe_dai_object(struct tplg_pre_processor *tplg_pp, snd_config_t *o { 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-object.c b/topology/pre-process-object.c index d5359cb3c..0117577b2 100644 --- a/topology/pre-process-object.c +++ b/topology/pre-process-object.c @@ -858,6 +858,13 @@ static int tplg_build_generic_object(struct tplg_pre_processor *tplg_pp, snd_con 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"}, }; @@ -932,6 +939,8 @@ const struct build_function_map object_build_map[] = { &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, diff --git a/topology/pre-processor.h b/topology/pre-processor.h index 6f4eb4c5e..0c54a835b 100644 --- a/topology/pre-processor.h +++ b/topology/pre-processor.h @@ -73,6 +73,8 @@ int tplg_build_fe_dai_object(struct tplg_pre_processor *tplg_pp, snd_config_t *o 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); From 1832f6f25439b9215d15cc6a0efd58ed08a15969 Mon Sep 17 00:00:00 2001 From: Ranjani Sridharan Date: Wed, 21 Apr 2021 11:18:04 -0700 Subject: [PATCH 85/88] topology: pre-process-object: add support for prepocessing child objects Add support for processing object instances embedded within objects and classes. For example: Object.Control.mixer."0" { #Channel register and shift for Front Left/Right Object.Base.channel."fl" { shift 0 } Object.Base.channel."fr" { } Object.Base.tlv."vtlv_m64s2" { Object.Base.scale."m64s2" { mute 1 } } Object.Base.ops."ctl" { info "volsw" #256 binds the mixer control to volume get/put handlers get 256 put 256 } } and pga class embeds the mixer objects as follows: Class.Widget."pga" { ... Object.Control { mixer."0" {...} mixer."1" {...} } The pre-processor starts with the top-pevel PGA widget object and processes the mixer objects in the class definition. This will recursively pre-processes its child objects to add the channels, tlv and ops. Signed-off-by: Ranjani Sridharan Signed-off-by: Jaroslav Kysela --- topology/pre-process-object.c | 53 ++++++++++++++++++++++++++++++++--- 1 file changed, 49 insertions(+), 4 deletions(-) diff --git a/topology/pre-process-object.c b/topology/pre-process-object.c index 0117577b2..fbf4b6a60 100644 --- a/topology/pre-process-object.c +++ b/topology/pre-process-object.c @@ -1168,6 +1168,33 @@ static int tplg_object_update(struct tplg_pre_processor *tplg_pp, snd_config_t * 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) { @@ -1306,6 +1333,7 @@ static int tplg_set_attribute_value(snd_config_t *attr, const char *value) 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. @@ -1372,7 +1400,7 @@ snd_config_t *tplg_object_get_instance_config(struct tplg_pre_processor *tplg_pp return snd_config_iterator_entry(first); } -/* build object config */ +/* 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) { @@ -1417,14 +1445,31 @@ static int tplg_build_object(struct tplg_pre_processor *tplg_pp, snd_config_t *n return ret; } - /* nothing to do if object is not supported */ + /* skip object if not supported and pre-process its child objects */ map = tplg_object_get_map(tplg_pp, new_obj); if (!map) - return 0; + goto child; /* build the object and save the sections to the output config */ builder = map->builder; - return builder(tplg_pp, new_obj, parent); + 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 */ From 8037d4812e6636f9ff0b8f835d6ff5a01e61578c Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Tue, 25 May 2021 18:28:00 +0200 Subject: [PATCH 86/88] topology: some whitespace fixups Signed-off-by: Jaroslav Kysela --- topology/pre-process-class.c | 6 +++--- topology/pre-process-dai.c | 2 +- topology/pre-process-dapm.c | 2 +- topology/pre-process-object.c | 6 +++--- topology/pre-processor.h | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/topology/pre-process-class.c b/topology/pre-process-class.c index f4f3b01fd..a328693d6 100644 --- a/topology/pre-process-class.c +++ b/topology/pre-process-class.c @@ -40,7 +40,7 @@ bool tplg_class_is_attribute_check(const char *attr, snd_config_t *class_cfg, ch 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; @@ -211,7 +211,7 @@ snd_config_type_t tplg_class_get_attribute_type(struct tplg_pre_processor *tplg_ /* 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 *class, const char *attr_name) { snd_config_t *attributes, *attr, *token_ref; const char *token; @@ -238,7 +238,7 @@ const char *tplg_class_get_attribute_token_ref(struct tplg_pre_processor *tplg_p /* 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 *class, snd_config_t *attr) { snd_config_t *attributes, *cfg, *valid, *tuples, *n; diff --git a/topology/pre-process-dai.c b/topology/pre-process-dai.c index 1158afd39..72aeefa72 100644 --- a/topology/pre-process-dai.c +++ b/topology/pre-process-dai.c @@ -64,7 +64,7 @@ static int tplg_update_pcm_object(struct tplg_pre_processor *tplg_pp, 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) { diff --git a/topology/pre-process-dapm.c b/topology/pre-process-dapm.c index d2b3b7aa3..450ca7173 100644 --- a/topology/pre-process-dapm.c +++ b/topology/pre-process-dapm.c @@ -229,7 +229,7 @@ static int tplg_pp_get_widget_name(struct tplg_pre_processor *tplg_pp, goto err; } } - + temp = tplg_snprintf("%s.%s", widget_name, arg); if (!temp) { ret = -ENOMEM; diff --git a/topology/pre-process-object.c b/topology/pre-process-object.c index fbf4b6a60..09aa37585 100644 --- a/topology/pre-process-object.c +++ b/topology/pre-process-object.c @@ -32,7 +32,7 @@ /* 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_t *obj_cfg, snd_config_t *parent) { snd_config_iterator_t i, next; snd_config_t *vtop, *n, *obj; @@ -358,7 +358,7 @@ static bool tplg_object_is_attribute_min_max_valid(snd_config_t *attr, snd_confi return false; } } - + return true; } default: @@ -720,7 +720,7 @@ static int tplg_object_add_attributes(snd_config_t *dst, snd_config_t *template, 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; diff --git a/topology/pre-processor.h b/topology/pre-processor.h index 0c54a835b..390037212 100644 --- a/topology/pre-processor.h +++ b/topology/pre-processor.h @@ -99,9 +99,9 @@ const char *tplg_class_get_unique_attribute_name(struct tplg_pre_processor *tplg 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); + 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); + snd_config_t *class, snd_config_t *attr); /* config helpers */ snd_config_t *tplg_find_config(snd_config_t *config, const char *name); From dc20c4b51225b108a23e3a519d1bc15a828c9c1f Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Tue, 25 May 2021 18:47:57 +0200 Subject: [PATCH 87/88] amixer: add the volume_mapping.h link Fixes: 75e644d ("amixer: link volume_mapping.c from alsamixer to amixer") Signed-off-by: Jaroslav Kysela --- amixer/volume_mapping.h | 1 + 1 file changed, 1 insertion(+) create mode 120000 amixer/volume_mapping.h 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 From 62f97bd3a7e536eefe872e8bc1ecd69b287c5514 Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Tue, 25 May 2021 18:58:34 +0200 Subject: [PATCH 88/88] amixer: Makefile - add volume_mapping.h to noinst_HEADERS Fixes: dc20c4b ("amixer: add the volume_mapping.h link") Signed-off-by: Jaroslav Kysela --- amixer/Makefile.am | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/amixer/Makefile.am b/amixer/Makefile.am index d92434ee0..46ec7369f 100644 --- a/amixer/Makefile.am +++ b/amixer/Makefile.am @@ -6,6 +6,6 @@ LDADD = -lm bin_PROGRAMS = amixer amixer_SOURCES = amixer.c volume_mapping.c -noinst_HEADERS = amixer.h +noinst_HEADERS = amixer.h volume_mapping.h man_MANS = amixer.1 EXTRA_DIST = amixer.1