diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 71af32f..0cd46a6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,7 +11,7 @@ jobs: - name: Prepare environment run: | dnf -y upgrade - dnf -y install @development-tools libtool bzip2 gettext-devel ncurses-devel + dnf -y install @development-tools libtool bzip2 gettext-devel ncurses-devel awk - name: Checkout alsa-lib uses: actions/checkout@v4 diff --git a/.github/workflows/coverity.yml b/.github/workflows/coverity.yml new file mode 100644 index 0000000..449151d --- /dev/null +++ b/.github/workflows/coverity.yml @@ -0,0 +1,97 @@ +name: Coverity Scan + +on: + workflow_dispatch: +# push: +# branches: +# - master + schedule: + # Run once a week on Sunday at 4am UTC + - cron: '0 4 * * 0' + +jobs: + coverity: + runs-on: ubuntu-latest + container: + image: fedora:latest + steps: + - name: Prepare environment + run: | + dnf -y upgrade + dnf -y install @development-tools libtool bzip2 gettext-devel ncurses-devel awk curl wget tar file + + - name: Checkout alsa-lib + uses: actions/checkout@v4 + with: + repository: alsa-project/alsa-lib + ref: master + path: alsa-lib + + - name: Configure alsa-lib + run: | + cd alsa-lib + head -5 configure.ac + libtoolize --force --copy --automake + aclocal + autoheader + automake --foreign --copy --add-missing + autoconf + export CFLAGS="-O2 -Wall -W -Wunused-const-variable=0 -pipe -g" + ./configure + echo "Version: $(cat version)" + + - name: Build alsa-lib + run: | + cd alsa-lib + make + + - name: Install alsa-lib + run: | + cd alsa-lib + make install + + - name: Checkout alsa-utils + uses: actions/checkout@v4 + with: + path: alsa-utils + + - name: Configure alsa-utils + run: | + cd alsa-utils + mv configure.ac configure.ac.old + sed -e 's;po/Makefile.in;;' < configure.ac.old > configure.ac + aclocal + gettextize -c -f --no-changelog + libtoolize --force --copy --automake + aclocal -I m4 + autoheader + automake --foreign --copy --add-missing + autoconf + export CFLAGS="-O2 -Wall -W -Wunused-const-variable=0 -pipe -g" + ./configure + + - name: Download Coverity Build Tool + run: | + cd alsa-utils + wget -q https://scan.coverity.com/download/linux64 \ + --post-data "token=${{ secrets.COVERITY_SCAN_TOKEN }}&project=${{ secrets.COVERITY_SCAN_PROJECT }}" \ + -O coverity_tool.tar.gz + mkdir coverity-tool + tar xzf coverity_tool.tar.gz --strip-components=1 -C coverity-tool + + - name: Build with Coverity Build Tool + run: | + cd alsa-utils + export PATH="$PWD/coverity-tool/bin:$PATH" + cov-build --dir cov-int make + + - name: Submit results to Coverity Scan + run: | + cd alsa-utils + tar czvf alsa-utils.tgz cov-int + curl --form token=${{ secrets.COVERITY_SCAN_TOKEN }} \ + --form email=${{ secrets.COVERITY_SCAN_EMAIL }} \ + --form file=@alsa-utils.tgz \ + --form version="$(git describe --tags --always)" \ + --form description="ALSA Utils Coverity Scan" \ + https://scan.coverity.com/builds?project=alsa-project%2Falsa-utils diff --git a/Android.bp b/Android.bp index aa44652..7d78bfc 100644 --- a/Android.bp +++ b/Android.bp @@ -24,8 +24,10 @@ cc_binary { name: "alsactl", srcs: [ "alsactl/alsactl.c", + "alsactl/boot_params.c", "alsactl/clean.c", "alsactl/daemon.c", + "alsactl/export.c", "alsactl/info.c", "alsactl/init_parse.c", "alsactl/init_ucm.c", @@ -33,6 +35,7 @@ cc_binary { "alsactl/monitor.c", "alsactl/state.c", "alsactl/utils.c", + "alsactl/wait.c", ], cflags: [ "-DSYS_ASOUNDRC=\"/data/local/tmp/asound.state\"", diff --git a/alsactl/.gitignore b/alsactl/.gitignore index 56ab3a2..e8fe186 100644 --- a/alsactl/.gitignore +++ b/alsactl/.gitignore @@ -1,3 +1,5 @@ -alsa-store.service -alsa-restore.service -90-alsa-restore.rules +conf/90-alsa-restore.rules +conf/alsa-state.service +conf/alsa-store.service +conf/alsa-restore.service +conf/alsa-card-wait@.service diff --git a/alsactl/Makefile.am b/alsactl/Makefile.am index e771717..c8729e0 100644 --- a/alsactl/Makefile.am +++ b/alsactl/Makefile.am @@ -11,8 +11,9 @@ AM_CFLAGS = -D_GNU_SOURCE AM_CPPFLAGS = -I$(top_srcdir)/include -alsactl_SOURCES=alsactl.c state.c lock.c utils.c init_parse.c init_ucm.c \ - daemon.c monitor.c clean.c info.c +alsactl_SOURCES=alsactl.c state.c lock.c utils.c wait.c \ + init_parse.c init_ucm.c boot_params.c \ + daemon.c monitor.c clean.c info.c export.c alsactl_CFLAGS=$(AM_CFLAGS) -D__USE_GNU \ -DSYS_ASOUNDRC=\"$(ASOUND_STATE_DIR)/asound.state\" \ @@ -24,13 +25,14 @@ noinst_HEADERS=alsactl.h list.h init_sysdeps.c init_utils_string.c \ init_utils_run.c init_sysfs.c udevrules_DATA = \ - 90-alsa-restore.rules + conf/90-alsa-restore.rules if HAVE_SYSTEMD systemdsystemunit_DATA = \ - alsa-state.service \ - alsa-restore.service + conf/alsa-state.service \ + conf/alsa-restore.service \ + conf/alsa-card-wait@.service install-data-hook: $(MKDIR_P) -m 0755 \ @@ -45,6 +47,7 @@ endif edit = \ extratest=$$(echo ' $(ALSACTL_UDEV_EXTRATEST)' | sed -e 's/__/ /g' -e 's/^ $$//'); \ args=$$(echo ' $(ALSACTL_UDEV_ARGS)' | sed -e 's/__/ /g' -e 's/^ $$//'); \ + mkdir -p conf; \ $(SED) -r -e 's,@sbindir\@,$(sbindir),g' \ -e 's,@mydatadir\@,$(mydatadir),g' \ -e 's,@daemonswitch\@,$(ALSACTL_DAEMONSWITCH),g' \ @@ -53,24 +56,29 @@ edit = \ -e "s;@args\@;$${args};g" \ < $< > $@ || rm $@ -alsa-state.service: alsa-state.service.in +conf/alsa-state.service: conf/alsa-state.service.in $(edit) -alsa-restore.service: alsa-restore.service.in +conf/alsa-restore.service: conf/alsa-restore.service.in $(edit) -90-alsa-restore.rules: 90-alsa-restore.rules.in +conf/alsa-card-wait@.service: conf/alsa-card-wait@.service.in + $(edit) + +conf/90-alsa-restore.rules: conf/90-alsa-restore.rules.in $(edit) EXTRA_DIST += \ - alsa-state.service.in \ - alsa-restore.service.in \ - 90-alsa-restore.rules.in + conf/alsa-state.service.in \ + conf/alsa-restore.service.in \ + conf/alsa-card-wait@.service.in \ + conf/90-alsa-restore.rules.in CLEANFILES = \ - alsa-state.service \ - alsa-restore.service \ - 90-alsa-restore.rules + conf/alsa-state.service \ + conf/alsa-restore.service \ + conf/alsa-card-wait@.service \ + conf/90-alsa-restore.rules %.7: %.xml xmlto man $? diff --git a/alsactl/alsactl.c b/alsactl/alsactl.c index 8ad991a..a91462e 100644 --- a/alsactl/alsactl.c +++ b/alsactl/alsactl.c @@ -44,14 +44,19 @@ #ifndef SYS_LOCKPATH #define SYS_LOCKPATH "/var/lock" #endif +#ifndef SYS_CARD_GROUP +#define SYS_CARD_GROUP SYS_ASOUND_DIR "/card-group.state" +#endif int debugflag = 0; int force_restore = 1; int ignore_nocards = 0; int do_lock = 0; int use_syslog = 0; +int do_export = 0; char *command; char *statefile = NULL; +char *groupfile = SYS_CARD_GROUP; char *lockpath = SYS_LOCKPATH; char *lockfile = SYS_LOCKFILE; @@ -78,6 +83,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 | 'G', "group-file", "card group configuration file (default " SYS_CARD_GROUP ")" }, { 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" }, @@ -92,6 +98,7 @@ static struct arg args[] = { { FILEARG | 'r', "runstate", "save restore and init state to this file (only errors)" }, { 0, NULL, " default settings is 'no file set'" }, { 'R', "remove", "remove runstate file at first, otherwise append errors" }, +{ 'Y', "export", "export card state as key=value pairs (restore command only)" }, { INTARG | 'p', "period", "store period in seconds for the daemon command" }, { FILEARG | 'e', "pid-file", "pathname for the process id (daemon mode)" }, { HEADER, NULL, "Available init options:" }, @@ -105,6 +112,7 @@ static struct arg args[] = { #ifdef HAVE_ALSA_USE_CASE_H { 'D', "ucm-defaults", "execute also the UCM 'defaults' section" }, { 'U', "no-ucm", "don't init with UCM" }, +{ 'm', "force-ucm-restore", "force UCM restore for boot card groups" }, #if SND_LIB_VER(1, 2, 5) < SND_LIB_VERSION { 'X', "ucm-nodev", "show UCM no device errors" }, #endif @@ -115,6 +123,7 @@ static struct arg args[] = { { CARDCMD, "restore", "load current driver setup for one or each soundcards" }, { EMPCMD, NULL, " from configuration file" }, { CARDCMD, "nrestore", "like restore, but notify the daemon to rescan soundcards" }, +{ CARDCMD, "wrestore", "wait for card ready, then restore" }, { CARDCMD, "init", "initialize driver to a default state" }, { CARDCMD, "daemon", "store state periodically for one or each soundcards" }, { CARDCMD, "rdaemon", "like daemon but do the state restore at first" }, @@ -301,6 +310,9 @@ int main(int argc, char *argv[]) case 'f': cfgfile = optarg; break; + case 'G': + groupfile = optarg; + break; case 'a': cfgdir = optarg; break; @@ -341,6 +353,9 @@ int main(int argc, char *argv[]) case 'U': initflags |= FLAG_UCM_DISABLED; break; + case 'm': + initflags |= FLAG_UCM_RESTORE; + break; case 'X': initflags |= FLAG_UCM_NODEV; break; @@ -350,6 +365,9 @@ int main(int argc, char *argv[]) case 'R': removestate = 1; break; + case 'Y': + do_export = 1; + break; case 'P': force_restore = 0; break; @@ -441,7 +459,11 @@ int main(int argc, char *argv[]) syslog(LOG_INFO, "alsactl " SND_UTIL_VERSION_STR " daemon started"); } +#if SND_LIB_VER(1, 2, 15) < SND_LIB_VERSION snd_lib_error_set_handler(error_handler); +#else + snd_lib_log_set_handler(log_handler); +#endif if (!strcmp(cmd, "init")) { res = init(cfgdir, initfile, initflags | FLAG_UCM_FBOOT | FLAG_UCM_BOOT, cardname); @@ -450,10 +472,15 @@ int main(int argc, char *argv[]) res = save_state(cfgfile, cardname); } else if (!strcmp(cmd, "restore") || !strcmp(cmd, "rdaemon") || - !strcmp(cmd, "nrestore")) { + !strcmp(cmd, "nrestore") || + !strcmp(cmd, "wrestore")) { if (removestate) remove(statefile); + if (!strcmp(cmd, "wrestore")) + initflags |= FLAG_UCM_WAIT; res = load_state(cfgdir, cfgfile, initfile, initflags, cardname, init_fallback); + if (do_export && res >= 0) + res = export_cards(cardname); 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 6ae45e3..2aefb89 100644 --- a/alsactl/alsactl.h +++ b/alsactl/alsactl.h @@ -2,14 +2,17 @@ #include #define LOCK_TIMEOUT 10 +#define DEFAULT_SYNC_TIME 20 extern int debugflag; extern int force_restore; extern int ignore_nocards; extern int do_lock; extern int use_syslog; +extern int do_export; extern char *command; extern char *statefile; +extern char *groupfile; extern char *lockpath; extern char *lockfile; @@ -24,7 +27,8 @@ 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, ...); void dbg_(const char *fcn, long line, const char *fmt, ...); -void error_handler(const char *file, int line, const char *function, int err, const char *fmt, ...); +void error_handler(const char *file, int line, const char *function, int errcode, const char *fmt, ...); +void log_handler(int prio, int interface, const char *file, int line, const char *function, int errcode, const char *fmt, va_list arg); #if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 95) #define info(...) do { info_(__func__, __LINE__, __VA_ARGS__); } while (0) @@ -43,6 +47,19 @@ 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) #define FLAG_UCM_NODEV (1<<4) +#define FLAG_UCM_RESTORE (1<<5) +#define FLAG_UCM_WAIT (1<<6) + +enum { + CARD_STATE_WAIT = 1, /* skip configuration (wait for sync) */ + CARD_STATE_SKIP = 2, /* skip card */ + CARD_STATE_RESTORED = 3, /* card was restored */ +}; + +static inline bool card_state_is_okay(int state) +{ + return state >= CARD_STATE_WAIT && state <= CARD_STATE_RESTORED; +} 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); @@ -52,14 +69,27 @@ 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 *cfgdir, const char *file, int flags, const char *cardname); int init_ucm(int flags, int cardno); +bool validate_boot_time(long long boot_time, long long current_time, long long synctime); +int read_boot_params(snd_ctl_t *handle, long long *boot_time, long long *sync_time, long long *restore_time, long long *primary_card); +int write_boot_params(snd_ctl_t *handle, long long boot_time, long long sync_time, long long restore_time, long long primary_card); +int card_group_load(snd_config_t **config); +int card_group_save(snd_config_t *config); +int card_group_get_int64(snd_config_t *config_group, const char *id, long long *val); +int card_group_set_int64(snd_config_t *config_group, const char *id, long long val); +int check_boot_params_validity(snd_ctl_t *handle, int cardno, char **boot_card_group, bool *valid, bool *in_sync, bool *restored, int *primary_card, long long *synctime); +int update_boot_params(snd_ctl_t *handle, int cardno, const char *boot_card_group, bool valid, bool restored, long long synctime); +int boot_params_remove_card(int cardno); int state_lock(const char *file, int timeout); int state_unlock(int lock_fd, const char *file); int card_lock(int card_number, int timeout); int card_unlock(int lock_fd, int card_number); +int group_state_lock(const char *file, int timeout); +int group_state_unlock(int lock_fd, const char *file); int save_state(const char *file, const char *cardname); int load_state(const char *cfgdir, const char *file, const char *initfile, int initflags, const char *cardname, int do_init); +int wait_for_card(long long timeout, int cardno); int power(const char *argv[], int argc); int monitor(const char *name); int general_info(const char *name); @@ -68,6 +98,12 @@ int state_daemon(const char *file, const char *cardname, int period, 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); +void add_linked_card(int cardno); + +/* export */ + +int export_card_state_set(int card, int state); +int export_cards(const char *cardname); /* utils */ diff --git a/alsactl/boot_params.c b/alsactl/boot_params.c new file mode 100644 index 0000000..af0f4fd --- /dev/null +++ b/alsactl/boot_params.c @@ -0,0 +1,1085 @@ +/* + * Advanced Linux Sound Architecture Control Program - Boot Parameters + * 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 +#include +#include +#include +#include +#include "alsactl.h" + +/* + * Validate boot time + * Returns: true if boot_time is valid and within synchronization time, false otherwise + */ +bool validate_boot_time(long long boot_time, long long current_time, long long synctime) +{ + long long diff; + + if (boot_time <= 0) + return false; + + diff = current_time - boot_time; + if (diff < 0) { + /* boot_time is in the future - invalid */ + return false; + } + + if (synctime > 0 && diff >= synctime) { + /* boot_time has exceeded timeout - invalid */ + return false; + } + + return true; +} + +/* + * Read boot parameters from the '.Boot' control element + * Returns: 0 on success, negative error code on failure + */ +int read_boot_params(snd_ctl_t *handle, long long *boot_time, long long *sync_time, + long long *restore_time, long long *primary_card) +{ + snd_ctl_elem_id_t *id; + snd_ctl_elem_value_t *value; + snd_ctl_elem_info_t *info; + int err; + + if (boot_time) + *boot_time = -1; + if (sync_time) + *sync_time = -1; + if (restore_time) + *restore_time = -1; + if (primary_card) + *primary_card = -1; + + snd_ctl_elem_id_alloca(&id); + snd_ctl_elem_value_alloca(&value); + snd_ctl_elem_info_alloca(&info); + + snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_CARD); + snd_ctl_elem_id_set_name(id, ".Boot"); + snd_ctl_elem_id_set_index(id, 0); + + snd_ctl_elem_info_set_id(info, id); + err = snd_ctl_elem_info(handle, info); + if (err < 0) { + if (err == -ENOENT) + return 0; + error("Cannot read '.Boot' control info: %s", snd_strerror(err)); + return err; + } + + if (snd_ctl_elem_info_get_type(info) != SND_CTL_ELEM_TYPE_INTEGER64) { + error("'.Boot' control element is not of type INTEGER64"); + return -EINVAL; + } + + if (snd_ctl_elem_info_get_count(info) != 4) { + error("'.Boot' control element does not have 3 values"); + return -EINVAL; + } + + snd_ctl_elem_value_set_id(value, id); + err = snd_ctl_elem_read(handle, value); + if (err < 0) { + error("Cannot read '.Boot' control: %s", snd_strerror(err)); + return err; + } + + dbg("Read boot params: boot_time=%lld sync_time=%lld restore_time=%lld primary_card=%lld", + snd_ctl_elem_value_get_integer64(value, 0), + snd_ctl_elem_value_get_integer64(value, 1), + snd_ctl_elem_value_get_integer64(value, 2), + snd_ctl_elem_value_get_integer64(value, 3)); + + if (boot_time) + *boot_time = snd_ctl_elem_value_get_integer64(value, 0); + if (sync_time) + *sync_time = snd_ctl_elem_value_get_integer64(value, 1); + if (restore_time) + *restore_time = snd_ctl_elem_value_get_integer64(value, 2); + if (primary_card) + *primary_card = snd_ctl_elem_value_get_integer64(value, 3); + + return 0; +} + +/* + * Write boot parameters to the '.Boot' control element + * Returns: 0 on success, negative error code on failure + */ +int write_boot_params(snd_ctl_t *handle, long long boot_time, long long sync_time, + long long restore_time, long long primary_card) +{ + snd_ctl_elem_id_t *id; + snd_ctl_elem_value_t *value; + snd_ctl_elem_info_t *info; + int err; + + snd_ctl_elem_id_alloca(&id); + snd_ctl_elem_value_alloca(&value); + snd_ctl_elem_info_alloca(&info); + + snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_CARD); + snd_ctl_elem_id_set_name(id, ".Boot"); + snd_ctl_elem_id_set_index(id, 0); + + snd_ctl_elem_info_set_id(info, id); + err = snd_ctl_elem_info(handle, info); + if (err < 0) { + if (err == -ENOENT) { + /* Element not found, create a new user element with 3 integer64 values */ + dbg("'.Boot' control not found, creating new user element"); + /* Do not save this element to the state file */ + snd_ctl_elem_info_set_inactive(info, 1); + snd_ctl_elem_info_set_read_write(info, 1, 1); + err = snd_ctl_add_integer64_elem_set(handle, info, 1, 4, -1, LLONG_MAX, 0); + if (err < 0) { + error("Cannot create '.Boot' user element: %s", snd_strerror(err)); + return err; + } + /* Re-read the element info after creation */ + err = snd_ctl_elem_info(handle, info); + if (err < 0) { + error("Cannot read '.Boot' control info after creation: %s", snd_strerror(err)); + return err; + } + } else { + error("Cannot read '.Boot' control info: %s", snd_strerror(err)); + return err; + } + } + + dbg("Write boot params: boot_time=%lld sync_time=%lld restore_time=%lld primary_card=%lld", + boot_time, sync_time, restore_time, primary_card); + + if (snd_ctl_elem_info_get_type(info) != SND_CTL_ELEM_TYPE_INTEGER64) { + error("'.Boot' control element is not of type INTEGER64"); + return -EINVAL; + } + + if (snd_ctl_elem_info_get_count(info) != 4) { + error("'.Boot' control element does not have 3 values"); + return -EINVAL; + } + + snd_ctl_elem_value_set_id(value, id); + snd_ctl_elem_value_set_integer64(value, 0, boot_time); + snd_ctl_elem_value_set_integer64(value, 1, sync_time); + snd_ctl_elem_value_set_integer64(value, 2, restore_time); + snd_ctl_elem_value_set_integer64(value, 3, primary_card); + + err = snd_ctl_elem_write(handle, value); + if (err < 0) { + error("Cannot write '.Boot' control: %s", snd_strerror(err)); + return err; + } + + return 0; +} + +/* + * Structure for the group configuration file: + * + * { + * card.0 # primary card in group + * card.1 # optional - next card in group + * card.2 # optional - next card in group + * boot_realtime # boot time (CLOCK_REALTIME) in seconds + * boot_last_update # timestamp of last configuration update (CLOCK_REALTIME) in seconds + * boot_monotonic # boot time (CLOCK_MONOTONIC_RAW) in seconds + * boot_synctime # synchronization time window in seconds + * } + */ + +/* + * Read card group configuration from file + * Returns: 0 on success, negative error code on failure + */ +int card_group_load(snd_config_t **config) +{ + snd_input_t *in; + int err; + + if (!config) + return -EINVAL; + + *config = NULL; + + err = snd_config_top(config); + if (err < 0) { + error("Cannot create top config: %s", snd_strerror(err)); + return err; + } + + err = snd_input_stdio_open(&in, groupfile, "r"); + if (err < 0) { + if (err == -ENOENT) { + dbg("Card group file '%s' not found", groupfile); + return 0; + } + error("Cannot open card group file '%s' for reading: %s", groupfile, snd_strerror(err)); + goto _err; + } + + err = snd_config_load(*config, in); + snd_input_close(in); + if (err < 0) { + error("Cannot load card group file '%s': %s", groupfile, snd_strerror(err)); + goto _err; + } + + return 0; + +_err: + snd_config_delete(*config); + *config = NULL; + return err; +} + +/* + * Write card group configuration to file + * Returns: 0 on success, negative error code on failure + */ +int card_group_save(snd_config_t *config) +{ + snd_output_t *out; + char temp_file[PATH_MAX]; + int err; + + if (!config) + return -EINVAL; + + snprintf(temp_file, sizeof(temp_file), "%s.new", groupfile); + + err = snd_output_stdio_open(&out, temp_file, "w"); + if (err < 0) { + error("Cannot open temporary card group file '%s' for writing: %s", temp_file, snd_strerror(err)); + return err; + } + + err = snd_config_save(config, out); + snd_output_close(out); + if (err < 0) { + error("Cannot save temporary card group file '%s': %s", temp_file, snd_strerror(err)); + return err; + } + + err = rename(temp_file, groupfile); + if (err < 0) { + err = -errno; + error("Cannot rename temporary card group file '%s' to '%s': %s", temp_file, groupfile, strerror(errno)); + return err; + } + + return 0; +} + +/* + * Get int64 value from card group configuration + * Returns: 0 on success, negative error code on failure + */ +int card_group_get_int64(snd_config_t *config_group, const char *id, long long *val) +{ + snd_config_t *node; + int err; + + if (!config_group || !id || !val) + return -EINVAL; + + err = snd_config_search(config_group, id, &node); + if (err < 0) + return err; + + err = snd_config_get_integer64(node, val); + if (err < 0) { + long ival; + err = snd_config_get_integer(node, &ival); + if (err < 0) + return err; + *val = ival; + } + + return 0; +} + +/* + * Set int64 value in card group configuration + * Returns: 0 on success, negative error code on failure + */ +int card_group_set_int64(snd_config_t *config_group, const char *id, long long val) +{ + snd_config_t *node; + int err; + + if (!config_group || !id) + return -EINVAL; + + err = snd_config_search(config_group, id, &node); + if (err < 0) { +_create: + err = snd_config_make_integer64(&node, id); + if (err < 0) { + error("Cannot create int64 node for id '%s': %s", id, snd_strerror(err)); + return err; + } + err = snd_config_add(config_group, node); + if (err < 0) { + error("Cannot add int64 node for id '%s': %s", id, snd_strerror(err)); + snd_config_delete(node); + return err; + } + } else { + /* alsa-lib should implement automatic type conversion */ + if (snd_config_get_type(node) == SND_CONFIG_TYPE_INTEGER) { + snd_config_delete(node); + goto _create; + } + } + + err = snd_config_set_integer64(node, val); + if (err < 0) { + error("Cannot set int64 value for id '%s': %s", id, snd_strerror(err)); + return err; + } + + return 0; +} + +/* + * Helper: Find or create card compound within a group + * Returns: 0 on success, negative error code on failure + */ +static int card_group_get_or_create_card_compound(snd_config_t *config_group, snd_config_t **card_compound) +{ + int err; + + if (!config_group || !card_compound) + return -EINVAL; + + err = snd_config_search(config_group, "card", card_compound); + if (err < 0) { + /* Create card compound */ + err = snd_config_make_compound(card_compound, "card", 0); + if (err < 0) { + error("Cannot create card compound: %s", snd_strerror(err)); + return err; + } + err = snd_config_add(config_group, *card_compound); + if (err < 0) { + error("Cannot add card compound: %s", snd_strerror(err)); + snd_config_delete(*card_compound); + return err; + } + } + + return 0; +} + +/* + * Helper: Determine the primary card in the card compound + * Returns: card number, otherwise error code + */ +static long card_group_find_primary(snd_config_t *card_compound) +{ + snd_config_iterator_t i, next; + + if (!card_compound) + return -EINVAL; + + if (snd_config_get_type(card_compound) != SND_CONFIG_TYPE_COMPOUND) + return -EINVAL; + + snd_config_for_each(i, next, card_compound) { + snd_config_t *card_node = snd_config_iterator_entry(i); + long card_val; + int err; + + err = snd_config_get_integer(card_node, &card_val); + if (err < 0) + return -EINVAL; + + return card_val; + } + + return -ENOENT; +} + +/* + * Helper: Find card node in card compound + * Returns: card node if found, NULL otherwise + */ +static snd_config_t *card_group_find_card_node(snd_config_t *card_compound, int cardno) +{ + snd_config_iterator_t i, next; + + if (!card_compound) + return NULL; + + if (snd_config_get_type(card_compound) != SND_CONFIG_TYPE_COMPOUND) + return NULL; + + snd_config_for_each(i, next, card_compound) { + snd_config_t *card_node = snd_config_iterator_entry(i); + long card_val; + int err; + + err = snd_config_get_integer(card_node, &card_val); + if (err < 0) + continue; + + if ((int)card_val == cardno) + return card_node; + } + + return NULL; +} + +/* + * Helper: Add card to card compound + * Returns: 0 on success, negative error code on failure + */ +static int card_group_add_card(snd_config_t *card_compound, int cardno) +{ + snd_config_t *new_card_node; + char card_id[16]; + int card_index = 0; + int err; + + if (!card_compound) + return -EINVAL; + + /* Find next available card index */ + while (card_index < 100) { + snprintf(card_id, sizeof(card_id), "%d", card_index); + if (snd_config_search(card_compound, card_id, &new_card_node) < 0) + break; + card_index++; + } + + err = snd_config_make_integer(&new_card_node, card_id); + if (err < 0) { + error("Cannot create card node: %s", snd_strerror(err)); + return err; + } + + err = snd_config_set_integer(new_card_node, cardno); + if (err < 0) { + error("Cannot set card number: %s", snd_strerror(err)); + snd_config_delete(new_card_node); + return err; + } + + err = snd_config_add(card_compound, new_card_node); + if (err < 0) { + error("Cannot add card node: %s", snd_strerror(err)); + snd_config_delete(new_card_node); + return err; + } + + return 0; +} + +/* + * Check boot parameters validity + * Returns: 0 on success, negative error code on failure + */ +int check_boot_params_validity(snd_ctl_t *handle, int cardno, char **boot_card_group, bool *valid, bool *in_sync, bool *restored, int *primary_card, long long *synctime) +{ + long long boot_time = -1; + long long restore_time = -1; + long long primary_card_val = -1; + long long boot_synctime = -1; + snd_config_t *config = NULL; + snd_config_t *config_group = NULL; + const char *card_group_name = NULL; + long long group_boot_realtime = -1; + long long group_boot_monotonic = -1; + long long group_boot_synctime = -1; + struct timespec ts_realtime, ts_monotonic; + long long diff_realtime, diff_monotonic, diff; + snd_config_iterator_t i, next; + int err = 0; + bool is_valid = false; + + if (valid) + *valid = false; + if (in_sync) + *in_sync = false; + if (restored) + *restored = false; + if (primary_card) + *primary_card = -1; + if (boot_card_group) + *boot_card_group = NULL; + + err = read_boot_params(handle, &boot_time, &boot_synctime, &restore_time, &primary_card_val); + if (err < 0) { + dbg("Boot element not present or error reading: %s", snd_strerror(err)); + err = 0; + goto out; + } + + if (boot_time <= 0) { + dbg("boot_time is not greater than zero: %lld", boot_time); + goto out; + } + + err = card_group_load(&config); + if (err < 0) { + dbg("Error loading card group configuration: %s", snd_strerror(err)); + err = 0; + goto out; + } + + if (!config) { + dbg("No group configuration found"); + goto out; + } + + /* Find the card number in card groups - prefer group with newest boot_realtime */ + snd_config_for_each(i, next, config) { + snd_config_t *n = snd_config_iterator_entry(i); + snd_config_t *card_compound; + const char *group_id; + long long current_boot_realtime; + + if (snd_config_get_id(n, &group_id) < 0) + continue; + + if (snd_config_get_type(n) != SND_CONFIG_TYPE_COMPOUND) + continue; + + err = snd_config_search(n, "card", &card_compound); + if (err < 0) + continue; + + if (!card_group_find_card_node(card_compound, cardno)) + continue; + + err = card_group_get_int64(n, "boot_realtime", ¤t_boot_realtime); + if (err < 0) { + dbg("boot_realtime not found in group '%s', skipping", group_id); + continue; + } + + if (current_boot_realtime > group_boot_realtime) { + group_boot_realtime = current_boot_realtime; + config_group = n; + card_group_name = group_id; + } + } + + if (!card_group_name) { + dbg("Card %d not found in any group configuration", cardno); + err = 0; + goto out; + } + + err = card_group_get_int64(config_group, "boot_monotonic", &group_boot_monotonic); + if (err < 0) { + dbg("boot_monotonic not found in group '%s'", card_group_name); + err = 0; + goto out; + } + + err = card_group_get_int64(config_group, "boot_synctime", &group_boot_synctime); + if (err < 0) { + dbg("boot_synctime not found in group '%s'", card_group_name); + err = 0; + goto out; + } + + if (*synctime > 0 && group_boot_synctime != *synctime) { + err = -EINVAL; + error("Synchronization time window does not match (%lld != %lld)", *synctime, group_boot_synctime); + goto out; + } + + if (boot_synctime > 0 && group_boot_synctime != boot_synctime) { + err = -EINVAL; + error("Element synchronization time window does not match (%lld != %lld)", boot_synctime, group_boot_synctime); + goto out; + } + + if (clock_gettime(CLOCK_REALTIME, &ts_realtime) < 0) { + err = -errno; + error("Failed to get CLOCK_REALTIME: %s", strerror(errno)); + goto out; + } + + if (clock_gettime(CLOCK_MONOTONIC_RAW, &ts_monotonic) < 0) { + err = -errno; + error("Failed to get CLOCK_MONOTONIC_RAW: %s", strerror(errno)); + goto out; + } + + diff_monotonic = ts_monotonic.tv_sec - group_boot_monotonic; + diff_realtime = ts_realtime.tv_sec - group_boot_realtime; + diff = diff_realtime - diff_monotonic; + dbg("Card group '%s' sync diffs - %lld, %lld, %lld", + card_group_name, (long long)diff_monotonic, (long long)diff_realtime, (long long)diff); + /* if the time difference is too big (30 seconds) - obsolete configuration */ + is_valid = diff < 30 || diff > -30; + + if (valid) + *valid = is_valid; + + if (is_valid) { + if (boot_card_group) { + *boot_card_group = strdup(card_group_name); + if (!*boot_card_group) { + err = -ENOMEM; + goto out; + } + } + if (primary_card) { + *primary_card = (int)primary_card_val; + dbg("Card group '%s' primary_card %d", card_group_name, *primary_card); + } + if (restored) { + *restored = restore_time > 0; + dbg("Card group '%s' restored %d", card_group_name, *restored); + } + if (in_sync) { + *in_sync = ts_realtime.tv_sec - group_boot_realtime < group_boot_synctime; + dbg("Card group '%s' in_sync %d - %lld, %lld, %lld", + card_group_name, *in_sync, (long long)ts_realtime.tv_sec, + (long long)group_boot_realtime, (long long)group_boot_synctime); + } + + if (synctime) + *synctime = group_boot_synctime; + } + +out: + if (config) + snd_config_delete(config); + return err; +} + +/* + * Remove card from boot parameters - scans all groups and removes all invalid + * cards in group containing the card. + * Returns: 0 = no change, 1 = change (card(s) removed), negative error code on failure + */ +static int boot_params_remove_card_config(snd_config_t *group_config, int cardno) +{ + snd_config_t *card_compound, *card_node; + snd_config_iterator_t i, next; + const char *group_id; + struct timespec ts_monotonic = {0}; + long primary_card, card_val; + int err, changes; + bool valid; + + if (snd_config_get_id(group_config, &group_id) < 0) + return -EINVAL; + + err = snd_config_search(group_config, "card", &card_compound); + if (err < 0) + return 0; + + primary_card = card_group_find_primary(card_compound); + if (primary_card == cardno) { +_primary: + dbg("Removing group '%s' (primary card %d)", group_id, cardno); + snd_config_delete(group_config); + return 1; + } + + card_node = card_group_find_card_node(card_compound, cardno); + if (card_node == NULL) + return 0; + + dbg("Removing card %d in group '%s'", cardno, group_id); + + changes = 1; + snd_config_delete(card_node); + +_retry: + snd_config_for_each(i, next, card_compound) { + snd_config_t *card_node = snd_config_iterator_entry(i); + snd_ctl_t *handle = NULL; + char name[32]; + long long boot_time = -1; + + err = snd_config_get_integer(card_node, &card_val); + if (err < 0) + continue; + + valid = false; + + sprintf(name, "hw:%ld", card_val); + err = snd_ctl_open(&handle, name, SND_CTL_READONLY); + if (err >= 0) { + err = read_boot_params(handle, &boot_time, NULL, NULL, NULL); + snd_ctl_close(handle); + if (err < 0) { + dbg("Unable to read boot params for card %ld: %s", card_val, snd_strerror(err)); + continue; + } + + if (ts_monotonic.tv_sec == 0) { + if (clock_gettime(CLOCK_MONOTONIC_RAW, &ts_monotonic) < 0) { + dbg("Failed to get CLOCK_MONOTONIC_RAW: %s", strerror(errno)); + return changes; + } + } + valid = validate_boot_time(boot_time, ts_monotonic.tv_sec, 0); + } else { + dbg("Unable to open ctl handle for card %ld: %s", card_val, snd_strerror(err)); + } + + if (!valid) { + if (card_val == primary_card) { + dbg("Primary card %ld is invalid in group '%s'", card_val, group_id); + goto _primary; + } + changes++; + dbg("Removing another card %ld in group '%s'", card_val, group_id); + snd_config_delete(card_node); + goto _retry; + } + } + if (snd_config_is_empty(card_compound)) { + dbg("No other cards in group '%s', removing", group_id); + snd_config_delete(group_config); + } + + return changes > 0; +} + +/* + * Remove card from boot parameters - scans all groups and removes all invalid + * cards in group containing the card. + * Returns: 0 on success, negative error code on failure + */ +int boot_params_remove_card(int cardno) +{ + snd_config_t *config = NULL; + snd_config_iterator_t i, next; + const char *group_id = NULL; + int groups_changed = 0; + int err = 0; + + /* Load the group configuration */ + err = card_group_load(&config); + if (err < 0) { + error("Error loading card group configuration: %s", snd_strerror(err)); + goto out; + } + + if (!config) { + dbg("No group configuration found"); + err = 0; + goto out; + } + + /* Scan all groups and remove any that contain this card */ +restart_scan: + snd_config_for_each(i, next, config) { + snd_config_t *group = snd_config_iterator_entry(i); + + if (snd_config_get_id(group, &group_id) < 0) + continue; + + if (snd_config_get_type(group) != SND_CONFIG_TYPE_COMPOUND) + continue; + + err = boot_params_remove_card_config(group, cardno); + if (err < 0) { + error("Unable to remove card %d from group '%s': %s", cardno, group_id, snd_strerror(err)); + continue; + } + if (err > 0) + groups_changed++; + goto restart_scan; + } + + if (groups_changed == 0) { + dbg("Card %d not found in any group", cardno); + err = 0; + goto out; + } + + dbg("Update %d group(s) containing card %d", groups_changed, cardno); + + /* Save the updated configuration */ + err = card_group_save(config); + if (err < 0) { + error("Cannot save card group configuration: %s", snd_strerror(err)); + goto out; + } + +out: + if (config) + snd_config_delete(config); + return err; +} + +/* + * Update restored time for all cards in boot group + * cards in group containing the card. + */ +static void boot_params_update_restored(snd_config_t *card_compound, int skip_cardno, + long long boot_time, long long restored, long long primary_cardno) +{ + snd_config_iterator_t i, next; + int err; + + /* Scan all groups and remove any that contain this card */ + snd_config_for_each(i, next, card_compound) { + snd_config_t *card_node = snd_config_iterator_entry(i); + long long boot_time_val, boot_synctime, boot_primary; + snd_ctl_t *handle; + char name[32]; + long card_val; + + err = snd_config_get_integer(card_node, &card_val); + if (err < 0) + continue; + + if (skip_cardno == (long)card_val) + continue; + + sprintf(name, "hw:%ld", card_val); + err = snd_ctl_open(&handle, name, SND_CTL_READONLY); + if (err < 0) { + dbg("Unable to open ctl handle for card %ld: %s", card_val, snd_strerror(err)); + continue; + } + + err = read_boot_params(handle, &boot_time_val, &boot_synctime, NULL, &boot_primary); + if (err < 0) { + dbg("Unable to read boot params for card %ld: %s", card_val, snd_strerror(err)); + goto _next; + } + + if (boot_time_val != boot_time) { + dbg("Boot time mismatch (%lld != %lld)", boot_time, boot_time_val); + goto _next; + } + + if (boot_primary != primary_cardno) { + dbg("Primary card mismatch (%lld != %lld)", boot_primary, primary_cardno); + goto _next; + } + + err = write_boot_params(handle, boot_time_val, boot_synctime, restored, boot_primary); + if (err < 0) { + dbg("Unable to save boot params: %s", snd_strerror(err)); + goto _next; + } + + add_linked_card(card_val); + +_next: + snd_ctl_close(handle); + } +} + +/* + * Update boot parameters + * Returns: 0 on success, negative error code on failure + */ +int update_boot_params(snd_ctl_t *handle, int cardno, const char *boot_card_group, + bool valid, bool restored, long long synctime) +{ + snd_config_t *config = NULL; + snd_config_t *config_group = NULL; + snd_config_t *card_compound = NULL; + struct timespec ts_realtime, ts_monotonic; + long long value; + long long boot_time = 0; + long long restore_time; + long long primary_card; + int err = 0; + + if (!boot_card_group) { + error("boot_card_group parameter is required"); + return -EINVAL; + } + + if (synctime <= 0) { + error("synchronization time window is required"); + return -EINVAL; + } + + err = card_group_load(&config); + if (err < 0) { + error("Error loading card group configuration: %s", snd_strerror(err)); + goto out; + } + + if (!config) { + err = snd_config_top(&config); + if (err < 0) { + error("Cannot create top config: %s", snd_strerror(err)); + goto out; + } + } + + /* If valid is false, remove the boot_card_group from configuration */ + if (!valid) { + err = snd_config_search(config, boot_card_group, &config_group); + if (err == 0 && config_group) { + err = boot_params_remove_card_config(config_group, cardno); + if (err < 0) { + error("Cannot manage group '%s': %s", boot_card_group, snd_strerror(err)); + goto out; + } + dbg("Updated group '%s' in configuration", boot_card_group); + } + } + + if (clock_gettime(CLOCK_REALTIME, &ts_realtime) < 0) { + err = -errno; + error("Failed to get CLOCK_REALTIME: %s", strerror(errno)); + goto out; + } + ts_realtime.tv_nsec = 0; + + if (clock_gettime(CLOCK_MONOTONIC_RAW, &ts_monotonic) < 0) { + err = -errno; + error("Failed to get CLOCK_MONOTONIC_RAW: %s", strerror(errno)); + goto out; + } + ts_monotonic.tv_nsec = 0; + restore_time = ts_monotonic.tv_sec; + + err = snd_config_search(config, boot_card_group, &config_group); + if (err < 0) { + err = snd_config_make_compound(&config_group, boot_card_group, 0); + if (err < 0) { + error("Cannot create group '%s': %s", boot_card_group, snd_strerror(err)); + goto out; + } + err = snd_config_add(config, config_group); + if (err < 0) { + error("Cannot add group '%s': %s", boot_card_group, snd_strerror(err)); + snd_config_delete(config_group); + goto out; + } + } + + err = card_group_get_or_create_card_compound(config_group, &card_compound); + if (err < 0) + goto out; + + primary_card = card_group_find_primary(card_compound); + if (primary_card < 0) { + dbg("Primary card not found, using %d", cardno); + primary_card = cardno; + } + + if (!card_group_find_card_node(card_compound, cardno)) { + err = card_group_add_card(card_compound, cardno); + if (err < 0) + goto out; + } + + if (primary_card != cardno || valid) { + err = card_group_get_int64(config_group, "boot_realtime", &value); + if (err < 0) { + err = card_group_set_int64(config_group, "boot_realtime", ts_realtime.tv_sec); + if (err < 0) { + error("Cannot set boot_realtime: %s", snd_strerror(err)); + goto out; + } + dbg("Set boot_realtime to %lld", (long long)ts_realtime.tv_sec); + } else { + dbg("Preserving existing boot_realtime: %lld", value); + ts_realtime.tv_sec = value; + } + + err = card_group_get_int64(config_group, "boot_monotonic", &value); + if (err < 0) { + err = card_group_set_int64(config_group, "boot_monotonic", ts_monotonic.tv_sec); + if (err < 0) { + error("Cannot set boot_monotonic: %s", snd_strerror(err)); + goto out; + } + dbg("Set boot_monotonic to %lld", (long long)ts_monotonic.tv_sec); + } else { + dbg("Preserving existing boot_monotonic: %lld", value); + ts_monotonic.tv_sec = value; + } + } else { + err = card_group_set_int64(config_group, "boot_realtime", ts_realtime.tv_sec); + if (err < 0) { + error("Cannot set boot_realtime: %s", snd_strerror(err)); + goto out; + } + dbg("Set boot_realtime to %lld", (long long)ts_realtime.tv_sec); + err = card_group_set_int64(config_group, "boot_monotonic", ts_monotonic.tv_sec); + if (err < 0) { + error("Cannot set boot_monotonic: %s", snd_strerror(err)); + goto out; + } + dbg("Set boot_monotonic to %lld", (long long)ts_monotonic.tv_sec); + } + + if (synctime > 0) { + err = card_group_set_int64(config_group, "boot_synctime", synctime); + if (err < 0) { + error("Cannot set boot_synctime: %s", snd_strerror(err)); + goto out; + } + } + + err = card_group_set_int64(config_group, "boot_last_update", ts_realtime.tv_sec); + if (err < 0) { + error("Cannot set boot_last_update: %s", snd_strerror(err)); + goto out; + } + + err = card_group_save(config); + if (err < 0) { + error("Cannot save card group configuration: %s", snd_strerror(err)); + goto out; + } + + /* Update '.Boot' control element on the card */ + boot_time = ts_monotonic.tv_sec; + if (!restored) + restore_time = -1; + + err = write_boot_params(handle, boot_time, synctime, restore_time, primary_card); + if (err < 0) { + error("Cannot write boot parameters: %s", snd_strerror(err)); + goto out; + } + + if (primary_card == cardno) + boot_params_update_restored(card_compound, primary_card, boot_time, restore_time, primary_card); + + dbg("Updated boot parameters for card %d in group '%s'", cardno, boot_card_group); + +out: + if (config) + snd_config_delete(config); + return err; +} + diff --git a/alsactl/90-alsa-restore.rules.in b/alsactl/conf/90-alsa-restore.rules.in similarity index 74% rename from alsactl/90-alsa-restore.rules.in rename to alsactl/conf/90-alsa-restore.rules.in index 5db6080..6edf97a 100644 --- a/alsactl/90-alsa-restore.rules.in +++ b/alsactl/conf/90-alsa-restore.rules.in @@ -8,7 +8,7 @@ LABEL="alsa_restore_go" ENV{ALSA_CARD_NUMBER}="$attr{device/number}" # mark HDA analog card; HDMI/DP card does not have capture devices -DRIVERS=="snd_hda_intel", TEST=="device/pcmC$env{ALSA_CARD_NUMBER}D0p", RUN+="/bin/sh -c 'echo ALSA_CARD_HDA_ANALOG=$env{ALSA_CARD_NUMBER} >> /run/udev/alsa-hda-analog-card'" +DRIVERS=="snd_hda_intel", TEST=="device/pcmC$env{ALSA_CARD_NUMBER}D0c", RUN+="/bin/sh -c 'echo ALSA_CARD_HDA_ANALOG=$env{ALSA_CARD_NUMBER} >> /run/udev/alsa-hda-analog-card'" # check for ACP hardware TEST=="device/device/acp3x-dmic-capture", GOTO="alsa_hda_analog" @@ -24,7 +24,8 @@ IMPORT{program}="/usr/bin/cat /run/udev/alsa-hda-analog-card" ENV{ALSA_CARD_HDA_ANALOG}!="", ENV{ALSA_CARD_NUMBER}="$env{ALSA_CARD_HDA_ANALOG}" LABEL="alsa_restore_std" -TEST!="@daemonswitch@", RUN+="@sbindir@/alsactl@args@ restore $env{ALSA_CARD_NUMBER}" -TEST=="@daemonswitch@", RUN+="@sbindir@/alsactl@args@ nrestore $env{ALSA_CARD_NUMBER}" +TEST!="@daemonswitch@", IMPORT{program}="@sbindir@/alsactl@args@ --export restore $env{ALSA_CARD_NUMBER}" +TEST=="@daemonswitch@", IMPORT{program}="@sbindir@/alsactl@args@ --export nrestore $env{ALSA_CARD_NUMBER}" +ENV{ALSA_CARD_STATE}=="waiting", ENV{SYSTEMD_WANTS}="alsa-card-wait@$env{ALSA_CARD_NUMBER}.service" LABEL="alsa_restore_end" diff --git a/alsactl/conf/alsa-card-wait@.service.in b/alsactl/conf/alsa-card-wait@.service.in new file mode 100644 index 0000000..816074f --- /dev/null +++ b/alsactl/conf/alsa-card-wait@.service.in @@ -0,0 +1,12 @@ +# +# ALSA card initialization handler for cards in waiting state +# This service is triggered by udev when ALSA_CARD_STATE=waiting +# + +[Unit] +Description=ALSA Card Initialization for card %I + +[Service] +Type=oneshot +RemainAfterExit=no +ExecStart=@sbindir@/alsactl@args@ wrestore %i diff --git a/alsactl/alsa-restore.service.in b/alsactl/conf/alsa-restore.service.in similarity index 100% rename from alsactl/alsa-restore.service.in rename to alsactl/conf/alsa-restore.service.in diff --git a/alsactl/alsa-state.service.in b/alsactl/conf/alsa-state.service.in similarity index 100% rename from alsactl/alsa-state.service.in rename to alsactl/conf/alsa-state.service.in diff --git a/alsactl/export.c b/alsactl/export.c new file mode 100644 index 0000000..422a92c --- /dev/null +++ b/alsactl/export.c @@ -0,0 +1,133 @@ +/* + * Advanced Linux Sound Architecture Control Program - Export + * 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 + * + */ + +/** + * Export variable syntax: + * + * For single card: + * + * ALSA_CARD_NUMBER= + * ALSA_CARD_STATE= + * + * For multiple cards: + * + * ALSA_CARD#_STATE= # where # is replaced with the card number + * + * State list: + * + * active # card was initialized and active + * skip # card was skipped (other card in group) + * waiting # card is waiting for the initial configuration + */ + +#include "aconfig.h" +#include "version.h" +#include +#include +#include +#include "alsactl.h" + +/* Global array to store card states (0 = active, 1 = waiting) */ +static int export_card_state[32]; + +/** + * export_card_state_set - Set card state + * @card: Card number + * @state: Card state (0 = active, 1 = waiting) + * + * Returns 0 on success, negative error code on failure + */ +int export_card_state_set(int card, int state) +{ + /* Check bounds */ + if (card < 0 || (unsigned long)card >= ARRAY_SIZE(export_card_state)) + return -EINVAL; + + export_card_state[card] = state; + return 0; +} + +/** + * export_card_state_print - Export and print card state information + * @iter: Card iterator containing current card information + * + * Prints key=value pairs based on iterator's single flag and export_card_state array. + * Returns 0 on success, negative error code on failure + */ +static int export_card_state_print(struct snd_card_iterator *iter) +{ + const char *state; + int istate; + + if (!iter) + return -EINVAL; + + /* Check bounds */ + if (iter->card < 0 || (unsigned long)iter->card >= ARRAY_SIZE(export_card_state)) + return -EINVAL; + + /* Determine state from export_card_state array */ + istate = export_card_state[iter->card]; + switch (istate) { + case CARD_STATE_WAIT: state = "waiting"; break; + case CARD_STATE_SKIP: state = "skip"; break; + default: state = "active"; break; + } + + if (iter->single) { + /* Single card export format */ + printf("ALSA_CARD_NUMBER=%d\n", iter->card); + printf("ALSA_CARD_STATE=%s\n", state); + } else { + /* Multiple cards export format */ + printf("ALSA_CARD%d_STATE=%s\n", iter->card, state); + } + + return 0; +} + +/** + * export_cards - Export state for all cards + * @cardname: Card name or NULL for all cards + * + * Returns 0 on success, negative error code on failure + */ +int export_cards(const char *cardname) +{ + struct snd_card_iterator iter; + const char *name; + int ret; + + ret = snd_card_iterator_sinit(&iter, cardname); + if (ret < 0) + return ret; + + while ((name = snd_card_iterator_next(&iter)) != NULL) { + ret = export_card_state_print(&iter); + if (ret < 0) + break; + } + + if (ret == 0) + ret = snd_card_iterator_error(&iter); + + return ret; +} diff --git a/alsactl/init_parse.c b/alsactl/init_parse.c index 267db4b..a34cb0e 100644 --- a/alsactl/init_parse.c +++ b/alsactl/init_parse.c @@ -1762,7 +1762,7 @@ int init(const char *cfgdir, const char *filename, int flags, const char *cardna continue; } err = init_ucm(flags, iter.card); - if (err == 0) + if (err == 0 || card_state_is_okay(err)) continue; err = init_space(&space, iter.card); if (err != 0) diff --git a/alsactl/init_ucm.c b/alsactl/init_ucm.c index 31e8daf..60967c4 100644 --- a/alsactl/init_ucm.c +++ b/alsactl/init_ucm.c @@ -21,6 +21,8 @@ #include "aconfig.h" #include +#include +#include #include "alsactl.h" #ifdef HAVE_ALSA_USE_CASE_H @@ -28,45 +30,269 @@ #include /* - * Keep it as simple as possible. Execute commands from the - * FixedBootSequence and BootSequence only. + * Helper: Check if card should skip initialization based on boot parameters + * Returns: 1 if should skip, 2 if should skip other card, 0 if should continue, negative on error + * If check_restored is true, also checks if card state is already restored + */ +static int should_skip_initialization(snd_ctl_t *ctl, int cardno, int flags, + char **boot_card_group, bool *valid, + bool *in_sync, bool *restored, int *primary_card, + long long *synctime) +{ + int err; + + err = check_boot_params_validity(ctl, cardno, boot_card_group, valid, in_sync, restored, primary_card, synctime); + if (err < 0) { + dbg("boot parameters validity failed: %s", snd_strerror(err)); + return err; + } + + /* do nothing for other cards in group */ + if (*valid && *primary_card != cardno) { + dbg("Skipping card %d - not primary (primary is %d)", cardno, *primary_card); + return CARD_STATE_SKIP; + } + + /* for immediate initialization, caller must set UCM force-restore flag */ + if (*valid && *in_sync && (flags & FLAG_UCM_RESTORE) == 0) { + dbg("Skipping card %d - in sync and no force-restore flag", cardno); + return CARD_STATE_WAIT; + } + return 0; +} + +/* + * Helper: Get boot card group configuration from UCM + * Returns: 0 on success, negative on error + */ +static int get_boot_card_group_config(snd_use_case_mgr_t *uc_mgr, char **boot_card_group, long long *synctime) +{ + char *sync_time = NULL; + int err; + + err = snd_use_case_get(uc_mgr, "=BootCardGroup", (const char **)boot_card_group); + if (err != 0 || *boot_card_group == NULL) { + return -ENOENT; + } + + dbg("BootCardGroup found: %s", *boot_card_group); + + /* Get optional sync time */ + err = snd_use_case_get(uc_mgr, "=BootCardSyncTime", (const char **)&sync_time); + if (err == 0 && sync_time != NULL) { + char *endptr; + errno = 0; + *synctime = strtoll(sync_time, &endptr, 10); + if (errno != 0 || *endptr != '\0' || endptr == sync_time) { + error("Invalid BootCardSyncTime value '%s'", sync_time); + *synctime = DEFAULT_SYNC_TIME; + } + free(sync_time); + } + + return 0; +} + +/* + * Helper: Open UCM manager with appropriate flags + * Returns: 0 on success, negative on error + */ +static int open_ucm_manager(snd_use_case_mgr_t **uc_mgr, int cardno, int flags, + bool valid, bool fixed_boot) +{ + char id[64], *nodev, *in_boot; + int err; + + nodev = (flags & FLAG_UCM_NODEV) ? "" : "-"; + in_boot = (valid || !fixed_boot) ? "" : "<<>>"; + snprintf(id, sizeof(id), "%s%shw:%d", nodev, in_boot, cardno); + + err = snd_use_case_mgr_open(uc_mgr, id); + dbg("ucm open '%s': %d", id, err); + + return err; +} + +/* + * Helper: Reopen UCM manager without InBoot flag + * Returns: 0 on success, negative on error + */ +static int reopen_ucm_manager(snd_use_case_mgr_t **uc_mgr, int cardno, int flags) +{ + char id[64], *nodev; + int err; + + snd_use_case_mgr_close(*uc_mgr); + + nodev = (flags & FLAG_UCM_NODEV) ? "" : "-"; + snprintf(id, sizeof(id), "%shw:%d", nodev, cardno); + + err = snd_use_case_mgr_open(uc_mgr, id); + dbg("ucm reopen '%s': %d", id, err); + + return err; +} + +/* + * Helper: Execute boot sequences + * Returns: 0 on success, negative on error + */ +static int execute_boot_sequences(snd_use_case_mgr_t *uc_mgr, int flags, bool fixed_boot) +{ + int err = 0; + + if (fixed_boot) { + err = snd_use_case_set(uc_mgr, "_fboot", NULL); + dbg("ucm _fboot: %d", err); + if (err == -ENOENT && (flags & FLAG_UCM_BOOT) != 0) { + /* _fboot not found but _boot requested - continue */ + err = 0; + } else if (err < 0) { + return err; + } + } + + if (flags & FLAG_UCM_BOOT) { + err = snd_use_case_set(uc_mgr, "_boot", NULL); + dbg("ucm _boot: %d", err); + if (err < 0) + return err; + + if ((flags & FLAG_UCM_DEFAULTS) != 0) + err = snd_use_case_set(uc_mgr, "_defaults", NULL); + } + + return err; +} + +/* + * Execute commands from the FixedBootSequence and BootSequence. + * Handle also card groups. + * Returns: 0 = success, 1 = skip this card (e.g. linked or in-sync), negative on error */ int init_ucm(int flags, int cardno) { snd_use_case_mgr_t *uc_mgr; - char id[32], *nodev; - int err; + char id[64]; + char *boot_card_group = NULL, *boot_card_group_verify = NULL; + bool fixed_boot, valid = false, in_sync = false, restored = false; + snd_ctl_t *ctl = NULL; + int err, primary_card = -1, lock_fd = -1; + long long synctime = -1; if (flags & FLAG_UCM_DISABLED) { dbg("ucm disabled"); return -ENXIO; } - nodev = (flags & FLAG_UCM_NODEV) ? "" : "-"; - snprintf(id, sizeof(id), "%shw:%d", nodev, cardno); - err = snd_use_case_mgr_open(&uc_mgr, id); - dbg("ucm open '%s': %d", id, err); - if (err < 0) + fixed_boot = (flags & FLAG_UCM_FBOOT) != 0; + + snprintf(id, sizeof(id), "hw:%d", cardno); + err = snd_ctl_open(&ctl, id, 0); + if (err < 0) { + dbg("UCM: unable to open control device '%s': %s", id, snd_strerror(err)); return err; - if (flags & FLAG_UCM_FBOOT) { - err = snd_use_case_set(uc_mgr, "_fboot", NULL); - dbg("ucm _fboot: %d", err); - if (err == -ENOENT && (flags & FLAG_UCM_BOOT) != 0) { - /* nothing */ + } + + err = should_skip_initialization(ctl, cardno, flags, &boot_card_group, &valid, + &in_sync, &restored, &primary_card, &synctime); + if (err != 0) + goto _fin; + + if (valid) { + if (restored) { + err = CARD_STATE_RESTORED; + goto _fin; + } + lock_fd = group_state_lock(groupfile, LOCK_TIMEOUT); + if (lock_fd < 0) { + err = lock_fd; + goto _fin; + } + } + + err = open_ucm_manager(&uc_mgr, cardno, flags, valid, fixed_boot); + if (err < 0) + goto _fin; + + if (!fixed_boot) + goto _execute_boot; + + if (!valid) { + err = get_boot_card_group_config(uc_mgr, &boot_card_group, &synctime); + if (err == -ENOENT) { + /* No BootCardGroup - remove any existing boot params */ + err = boot_params_remove_card(cardno); + if (err < 0) + goto _error; + goto _execute_boot; } else if (err < 0) { goto _error; } + + if (lock_fd < 0) { + lock_fd = group_state_lock(groupfile, LOCK_TIMEOUT); + if (lock_fd < 0) { + err = lock_fd; + goto _error; + } + } + + err = should_skip_initialization(ctl, cardno, flags, &boot_card_group_verify, + &valid, &in_sync, &restored, &primary_card, &synctime); + if (err != 0) + goto _error; + + if (valid && (boot_card_group_verify == NULL || strcmp(boot_card_group_verify, boot_card_group) != 0)) { + dbg("expected different boot card group (got '%s', expected '%s')", boot_card_group_verify, boot_card_group); + err = -EINVAL; + goto _error; + } + + if ((flags & FLAG_UCM_RESTORE) == 0 && (!valid || restored)) { + dbg("Skipping card %d (group '%s') - %s and no force-restore flag", cardno, boot_card_group, + !valid ? "validity not passed" : "already restored"); + if (!valid) { + /* create initial 'Boot' element */ + err = update_boot_params(ctl, cardno, boot_card_group, 0, restored, synctime); + if (err < 0) + goto _error; + } + err = restored ? CARD_STATE_RESTORED : CARD_STATE_WAIT; + goto _error; + } + + err = reopen_ucm_manager(&uc_mgr, cardno, flags); + if (err < 0) + goto _fin; } - if (flags & FLAG_UCM_BOOT) { - err = snd_use_case_set(uc_mgr, "_boot", NULL); - dbg("ucm _boot: %d", err); + +_execute_boot: + if (flags & FLAG_UCM_FBOOT) + restored = true; + + if (boot_card_group) { + err = update_boot_params(ctl, cardno, boot_card_group, valid, restored, synctime); if (err < 0) goto _error; - if ((flags & FLAG_UCM_DEFAULTS) != 0) - err = snd_use_case_set(uc_mgr, "_defaults", NULL); } + + err = execute_boot_sequences(uc_mgr, flags, fixed_boot); + if (err < 0) + goto _error; + + err = 0; + _error: snd_use_case_mgr_close(uc_mgr); +_fin: + if (lock_fd >= 0) + group_state_unlock(lock_fd, groupfile); + if (ctl) + snd_ctl_close(ctl); + free(boot_card_group); + free(boot_card_group_verify); + dbg("ucm init complete %d", err); return err; } diff --git a/alsactl/lock.c b/alsactl/lock.c index 4927b70..e3df982 100644 --- a/alsactl/lock.c +++ b/alsactl/lock.c @@ -181,6 +181,40 @@ int state_unlock(int _fd, const char *file) return err; } +static void group_state_lock_file(char *buf, size_t buflen) +{ + const char *name = strrchr(groupfile, '/'); + if (name && name[0]) + name++; + else + name = "card-group.state"; + snprintf(buf, buflen, "%s/%s.lock", lockpath, name); +} + +int group_state_lock(const char *file, int timeout) +{ + char fn[PATH_SIZE]; + int err; + + group_state_lock_file(fn, sizeof(fn)); + err = state_lock_(fn, 1, timeout, -1); + if (err < 0) + error("file %s lock error: %s", file, strerror(-err)); + return err; +} + +int group_state_unlock(int _fd, const char *file) +{ + char fn[PATH_SIZE]; + int err; + + group_state_lock_file(fn, sizeof(fn)); + err = state_lock_(fn, 0, 10, _fd); + if (err < 0) + error("file %s unlock error: %s", file, strerror(-err)); + return err; +} + static void card_lock_file(char *buf, size_t buflen, int card_number) { snprintf(buf, buflen, "%s/card%i.lock", lockpath, card_number); diff --git a/alsactl/monitor.c b/alsactl/monitor.c index 36e9398..e830ba5 100644 --- a/alsactl/monitor.c +++ b/alsactl/monitor.c @@ -176,7 +176,7 @@ static int check_control_cdev(int infd, bool *retry) ssize_t len = read(infd, buf, sizeof(*ev) + NAME_MAX); if (len < 0) { if (errno != EAGAIN) - err = errno; + err = -errno; break; } else if (len == 0) { break; diff --git a/alsactl/state.c b/alsactl/state.c index 3ccaab6..2942f3f 100644 --- a/alsactl/state.c +++ b/alsactl/state.c @@ -29,6 +29,33 @@ #include #include "alsactl.h" +static int linked_cards[16]; + +static void init_linked_cards(void) +{ + size_t index; + + for (index = 0; index < ARRAY_SIZE(linked_cards); index++) + linked_cards[index] = -1; +} + +void add_linked_card(int cardno) +{ + size_t index; + + for (index = 0; index < ARRAY_SIZE(linked_cards); index++) { + if (linked_cards[index] == cardno) + return; + } + + for (index = 0; index < ARRAY_SIZE(linked_cards); index++) { + if (linked_cards[index] < 0) { + linked_cards[index] = cardno; + return; + } + } + error("Too many linked cards!"); +} static char *id_str(snd_ctl_elem_id_t *id) { @@ -1511,7 +1538,7 @@ static int set_controls(int card, snd_config_t *top, int doit) snd_ctl_elem_id_t *elem_id; snd_config_t *control; snd_config_iterator_t i, next; - int err, controls1 = -1, controls2 = -1; + int err, controls1 = -1, controls2 = -1, ucontrols = -1, diff; unsigned int idx, count = 0; char name[32], tmpid[16]; const char *id; @@ -1571,7 +1598,7 @@ static int set_controls(int card, snd_config_t *top, int doit) count = snd_ctl_elem_list_get_count(list); dbg("list count: %u", count); if (count == 0) - goto _check; + goto _free; snd_ctl_elem_list_set_offset(list, 0); if (snd_ctl_elem_list_alloc_space(list, count) < 0) { error("No enough memory..."); @@ -1581,13 +1608,15 @@ static int set_controls(int card, snd_config_t *top, int doit) error("Cannot determine controls (2): %s", snd_strerror(err)); goto _free; } - controls2 = 0; - /* skip non-readable elements */ + controls2 = ucontrols = 0; + /* skip non-readable and count user elements */ for (idx = 0; idx < count; ++idx) { snd_ctl_elem_info_clear(elem_info); snd_ctl_elem_list_get_id(list, idx, elem_id); snd_ctl_elem_info_set_id(elem_info, elem_id); if (snd_ctl_elem_info(handle, elem_info) == 0) { + if (snd_ctl_elem_info_is_user(elem_info)) + ucontrols++; if (!snd_ctl_elem_info_is_readable(elem_info)) continue; controls2++; @@ -1596,9 +1625,9 @@ static int set_controls(int card, snd_config_t *top, int doit) /* check if we have additional controls in driver */ /* in this case we should go through init procedure */ - _check: - dbg("controls1=%i controls2=%i", controls1, controls2); - if (controls1 >= 0 && controls1 != controls2) { + diff = controls2 - controls1; + dbg("controls1=%i controls2=%i ucontrols=%i diff=%i", controls1, controls2, ucontrols, diff); + if (controls1 >= 0 && (-diff > ucontrols || diff > ucontrols)) { /* not very informative */ /* but value is used for check only */ err = -EAGAIN; @@ -1701,10 +1730,11 @@ 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, lock_fd; + int err, finalerr = 0, open_failed, lock_fd, cardno; struct snd_card_iterator iter; snd_config_t *config; const char *cardname1; + size_t index; config = NULL; err = load_configuration(file, &config, &open_failed); @@ -1721,6 +1751,9 @@ int load_state(const char *cfgdir, const char *file, while ((cardname1 = snd_card_iterator_next(&iter)) != NULL) { if (!do_init) break; + init_linked_cards(); + if (initflags & FLAG_UCM_WAIT) + wait_for_card(-1, iter.card); lock_fd = card_lock(iter.card, LOCK_TIMEOUT); if (lock_fd < 0) { finalerr = lock_fd; @@ -1729,9 +1762,12 @@ int load_state(const char *cfgdir, const char *file, } err = init(cfgdir, initfile, initflags | FLAG_UCM_FBOOT | FLAG_UCM_BOOT, cardname1); card_unlock(lock_fd, iter.card); + if (card_state_is_okay(err)) + export_card_state_set(iter.card, err); if (err < 0) { finalerr = err; initfailed(iter.card, "init", err); + continue; } initfailed(iter.card, "restore", -ENOENT); } @@ -1745,6 +1781,9 @@ int load_state(const char *cfgdir, const char *file, if (err < 0) goto out; while ((cardname1 = snd_card_iterator_next(&iter)) != NULL) { + init_linked_cards(); + if (initflags & FLAG_UCM_WAIT) + wait_for_card(-1, iter.card); lock_fd = card_lock(iter.card, LOCK_TIMEOUT); if (lock_fd < 0) { initfailed(iter.card, "lock", lock_fd); @@ -1752,7 +1791,12 @@ int load_state(const char *cfgdir, const char *file, continue; } /* error is ignored */ - init_ucm(initflags | FLAG_UCM_FBOOT, iter.card); + err = init_ucm(initflags | FLAG_UCM_FBOOT, iter.card); + /* return code 1 and 2 -> postpone initialization */ + if (card_state_is_okay(err)) { + export_card_state_set(iter.card, err); + goto unlock_card; + } /* do a check if controls matches state file */ if (do_init && set_controls(iter.card, config, 0)) { err = init(cfgdir, initfile, initflags | FLAG_UCM_BOOT, cardname1); @@ -1766,6 +1810,18 @@ int load_state(const char *cfgdir, const char *file, finalerr = err; initfailed(iter.card, "restore", err); } + /* for linked cards, restore controls, too */ + for (index = 0; index < ARRAY_SIZE(linked_cards); index++) { + if ((cardno = linked_cards[index]) < 0) + break; + dbg("Restore for linked card %d", cardno); + if ((err = set_controls(cardno, config, 1))) { + if (!force_restore) + finalerr = err; + initfailed(cardno, "restore", err); + } + } +unlock_card: card_unlock(lock_fd, iter.card); } err = finalerr ? finalerr : snd_card_iterator_error(&iter); diff --git a/alsactl/utils.c b/alsactl/utils.c index d8cbf53..d0f8940 100644 --- a/alsactl/utils.c +++ b/alsactl/utils.c @@ -177,7 +177,7 @@ void dbg_(const char *fcn, long line, const char *fmt, ...) va_end(ap); } -void error_handler(const char *file, int line, const char *function, int err, const char *fmt, ...) +void error_handler(const char *file, int line, const char *function, int errcode, const char *fmt, ...) { char buf[2048]; va_list arg; @@ -187,12 +187,36 @@ void error_handler(const char *file, int line, const char *function, int err, co va_end(arg); if (use_syslog) syslog(LOG_ERR, "alsa-lib %s:%i:(%s) %s%s%s\n", file, line, function, - buf, err ? ": " : "", err ? snd_strerror(err) : ""); + buf, errcode ? ": " : "", errcode ? snd_strerror(errcode) : ""); else fprintf(stderr, "alsa-lib %s:%i:(%s) %s%s%s\n", file, line, function, - buf, err ? ": " : "", err ? snd_strerror(err) : ""); + buf, errcode ? ": " : "", errcode ? snd_strerror(errcode) : ""); } +#if SND_LIB_VER(1, 2, 15) >= SND_LIB_VERSION +void log_handler(int prio, int interface, const char *file, int line, const char *function, int errcode, const char *fmt, va_list arg) +{ + char buf[2048], level[50] = ""; + const char *text1, *text2; + + if (!snd_lib_log_filter(prio, interface, NULL)) + return; + + text1 = snd_lib_log_priority(prio); + text2 = snd_lib_log_interface(interface); + if (text1 || text2) + snprintf(level, sizeof(level), "[%s.%s] ", text1 ? text1 : "", text2 ? text2 : ""); + + vsnprintf(buf, sizeof(buf), fmt, arg); + if (use_syslog) + syslog(LOG_ERR, "alsa-lib %s:%i:(%s) %s%s%s%s\n", file, line, function, level, + buf, errcode ? ": " : "", errcode ? snd_strerror(errcode) : ""); + else + fprintf(stderr, "alsa-lib %s:%i:(%s) %s%s%s%s\n", file, line, function, level, + buf, errcode ? ": " : "", errcode ? snd_strerror(errcode) : ""); +} +#endif + int load_configuration(const char *file, snd_config_t **top, int *open_failed) { snd_config_t *config; @@ -329,6 +353,7 @@ int snd_card_clean_cfgdir(const char *cfgdir, int cardno) lasterr = -errno; } } + free(list); return lasterr; } diff --git a/alsactl/wait.c b/alsactl/wait.c new file mode 100644 index 0000000..bebcc3c --- /dev/null +++ b/alsactl/wait.c @@ -0,0 +1,200 @@ +/* + * Advanced Linux Sound Architecture Control Program - Wait for Boot + * Copyright (c) by Jaroslav Kysela + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "aconfig.h" +#include "version.h" +#include +#include +#include +#include +#include +#include "alsactl.h" + +/** + * \brief Wait for card boot synchronization using Boot control element + * \param timeout Maximum wait time in seconds + * \param cardno Card number + * \return 0 on success, negative error code on failure + * + * This function waits until the card releases the 'waiting' state (UCM). + * It monitors the '.Boot' control element and uses snd_ctl_wait() and + * snd_ctl_read() for event-based waiting, similar to boot_wait() in + * ../alsa-lib/alsa-lib/src/ucm/main.c + */ +int wait_for_card(long long timeout, int cardno) +{ + snd_ctl_t *handle; + snd_ctl_event_t *event; + snd_ctl_elem_id_t *id; + snd_ctl_elem_info_t *info; + snd_ctl_elem_value_t *value; + long long boot_time_val, restore_time_val; + long long synctime = -1; + struct timespec start_time, now; + char name[32]; + int err; + + sprintf(name, "hw:%d", cardno); + err = snd_ctl_open(&handle, name, SND_CTL_READONLY); + if (err < 0) { + error("snd_ctl_open error for %s: %s", name, snd_strerror(err)); + return err; + } + + /* Try to get synctime from boot_synctime in group configuration */ + if (timeout <= 0) { + bool valid = false, restored = false; + err = check_boot_params_validity(handle, cardno, NULL, &valid, NULL, &restored, NULL, &synctime); + if (err == 0 && synctime > 0) { + timeout = synctime; + dbg("Using boot_synctime value: %lld seconds", timeout); + } else { + timeout = DEFAULT_SYNC_TIME; + } + /* Break early if boot params are invalid or already restored */ + if (!valid || restored) { + dbg("Boot params check: valid=%d, restored=%d - skipping wait", valid, restored); + snd_ctl_close(handle); + return 0; + } + } + + snd_ctl_event_alloca(&event); + snd_ctl_elem_id_alloca(&id); + snd_ctl_elem_info_alloca(&info); + snd_ctl_elem_value_alloca(&value); + + snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_CARD); + snd_ctl_elem_id_set_name(id, ".Boot"); + + snd_ctl_elem_info_set_id(info, id); + err = snd_ctl_elem_info(handle, info); + if (err < 0) { + dbg("Boot control element not present on card %d, skipping wait", cardno); + snd_ctl_close(handle); + return 0; /* No Boot element, no wait needed */ + } + + if (snd_ctl_elem_info_get_type(info) != SND_CTL_ELEM_TYPE_INTEGER64) { + error("Boot control element is not INTEGER64 type on card %d", cardno); + snd_ctl_close(handle); + return -EINVAL; + } + + if (snd_ctl_elem_info_get_count(info) < 3) { + error("Boot control element does not have count >= 3 on card %d", cardno); + snd_ctl_close(handle); + return -EINVAL; + } + + err = snd_ctl_subscribe_events(handle, 1); + if (err < 0) { + error("Cannot subscribe to control events: %s", snd_strerror(err)); + snd_ctl_close(handle); + return err; + } + + clock_gettime(CLOCK_MONOTONIC_RAW, &start_time); + + dbg("Waiting for card %d to become ready (timeout=%lld seconds)", cardno, timeout); + + while (1) { + long long diff, remaining = 0; + long long sync_time_val = -1; + + clock_gettime(CLOCK_MONOTONIC_RAW, &now); + + /* Read current Boot control values */ + err = read_boot_params(handle, &boot_time_val, &sync_time_val, &restore_time_val, NULL); + if (err < 0) { + error("Failed to read Boot control element: %s", snd_strerror(err)); + goto _fin; + } + + dbg("Boot info: boot_time=%lld, sync_time=%lld, restore_time=%lld", boot_time_val, sync_time_val, restore_time_val); + + if (restore_time_val > 0) { + diff = now.tv_sec - restore_time_val; + dbg("Controls already restored (diff=%lld seconds), card is ready", diff); + err = 0; + goto _fin; + } + + /* note that realtime may differ from monotonic time, add one second for safety */ + diff = now.tv_sec - start_time.tv_sec; + if (diff > timeout + 1) { + dbg("Maximum wait time exceeded (%lld >= %lld seconds), proceeding", diff, timeout); + break; + } + + remaining = timeout - diff; + + /* Use synctime from element to limit timeout if available */ + if (sync_time_val > 0 && sync_time_val < timeout) { + dbg("Limiting timeout from %lld to sync_time %lld seconds", timeout, sync_time_val); + timeout = sync_time_val; + } + + if (!validate_boot_time(boot_time_val, now.tv_sec, timeout)) { + if (boot_time_val > 0) { + diff = now.tv_sec - boot_time_val; + dbg("Boot timeout reached (%lld >= %lld seconds), proceeding", diff, timeout); + } + break; + } + + diff = now.tv_sec - boot_time_val; + if (timeout - diff < remaining) + remaining = timeout - diff; + if (remaining <= 0) + remaining = 1; + + dbg("Waiting %lld seconds", remaining); + err = snd_ctl_wait(handle, remaining * 1000); + if (err < 0) { + error("snd_ctl_wait failed: %s", snd_strerror(err)); + goto _fin; + } + + if (err == 0) + continue; /* Timeout, no events */ + + /* Read and check events */ + while (snd_ctl_read(handle, event) > 0) { + if (!(snd_ctl_event_elem_get_mask(event) & SND_CTL_EVENT_MASK_VALUE)) + continue; /* Not a value change event */ + + if (snd_ctl_event_elem_get_interface(event) != SND_CTL_ELEM_IFACE_CARD || + snd_ctl_event_elem_get_index(event) != 0 || + strcmp(snd_ctl_event_elem_get_name(event), ".Boot") != 0) + continue; + + dbg("Boot control element value changed"); + break; + } + } + + err = 0; +_fin: + snd_ctl_subscribe_events(handle, 0); + snd_ctl_close(handle); + return err; +} diff --git a/alsamixer/mainloop.c b/alsamixer/mainloop.c index 7ca785b..3cb51b4 100644 --- a/alsamixer/mainloop.c +++ b/alsamixer/mainloop.c @@ -34,6 +34,7 @@ static WINDOW *curses_initialized; +#if SND_LIB_VER(1, 2, 15) < SND_LIB_VERSION static void black_hole_error_handler(const char *file ATTRIBUTE_UNUSED, int line ATTRIBUTE_UNUSED, const char *function ATTRIBUTE_UNUSED, @@ -41,6 +42,18 @@ static void black_hole_error_handler(const char *file ATTRIBUTE_UNUSED, const char *fmt ATTRIBUTE_UNUSED, ...) { } +#else +static void black_hole_log_handler(int prio ATTRIBUTE_UNUSED, + int interface ATTRIBUTE_UNUSED, + const char *file ATTRIBUTE_UNUSED, + int line ATTRIBUTE_UNUSED, + const char *function ATTRIBUTE_UNUSED, + int errcode ATTRIBUTE_UNUSED, + const char *fmt ATTRIBUTE_UNUSED, + va_list arg ATTRIBUTE_UNUSED) +{ +} +#endif void initialize_curses(bool use_color, bool use_mouse) { @@ -55,7 +68,11 @@ void initialize_curses(bool use_color, bool use_mouse) if (use_mouse) mousemask(ALL_MOUSE_EVENTS, NULL); +#if SND_LIB_VER(1, 2, 15) < SND_LIB_VERSION snd_lib_error_set_handler(black_hole_error_handler); +#else + snd_lib_log_set_handler(black_hole_log_handler); +#endif } void app_shutdown(void) diff --git a/amidi/amidi.1 b/amidi/amidi.1 index 5bc24ba..d3d321f 100644 --- a/amidi/amidi.1 +++ b/amidi/amidi.1 @@ -52,6 +52,11 @@ Prints the current version. .I \-l, \-\-list\-devices Prints a list of all hardware MIDI ports. +.TP +.I \-x, \-\-list\-inactive +Use together with \fI\-l\fP option. +Print all MIDI ports including inactive ports. + .TP .I \-L, \-\-list\-rawmidis Prints all RawMIDI definitions. diff --git a/amidi/amidi.c b/amidi/amidi.c index 75fb8c0..9a2aaf7 100644 --- a/amidi/amidi.c +++ b/amidi/amidi.c @@ -57,6 +57,7 @@ static int stop; static int sysex_interval; static snd_rawmidi_t *input, **inputp; static snd_rawmidi_t *output, **outputp; +static int list_all; static void error(const char *format, ...) { @@ -76,6 +77,7 @@ static void usage(void) "-h, --help this help\n" "-V, --version print current version\n" "-l, --list-devices list all hardware ports\n" + "-x, --list-inactive list inactive ports, too\n" "-L, --list-rawmidis list all RawMIDI definitions\n" "-p, --port=name select port by name\n" "-s, --send=file send the contents of a (.syx) file\n" @@ -151,6 +153,9 @@ static void list_device(snd_ctl_t *ctl, int card, int device) card, device, sub, snd_strerror(err)); return; } + if (!list_all && + (snd_rawmidi_info_get_flags(info) & SNDRV_RAWMIDI_INFO_STREAM_INACTIVE)) + continue; name = snd_rawmidi_info_get_name(info); sub_name = snd_rawmidi_info_get_subdevice_name(info); if (sub == 0 && sub_name[0] == '\0') { @@ -471,11 +476,12 @@ static void add_send_hex_data(const char *str) int main(int argc, char *argv[]) { - static const char short_options[] = "hVlLp:s:r:S::dt:aci:T:"; + static const char short_options[] = "hVlxLp:s:r:S::dt:aci:T:"; static const struct option long_options[] = { {"help", 0, NULL, 'h'}, {"version", 0, NULL, 'V'}, {"list-devices", 0, NULL, 'l'}, + {"list-inactive", 0, NULL, 'x'}, {"list-rawmidis", 0, NULL, 'L'}, {"port", 1, NULL, 'p'}, {"send", 1, NULL, 's'}, @@ -508,6 +514,9 @@ int main(int argc, char *argv[]) case 'l': do_device_list = 1; break; + case 'x': + list_all = 1; + break; case 'L': do_rawmidi_list = 1; break; diff --git a/aplay/aplay.c b/aplay/aplay.c index c58c2e9..737d087 100644 --- a/aplay/aplay.c +++ b/aplay/aplay.c @@ -1464,6 +1464,13 @@ static void set_params(void) chunk_size, buffer_size); prg_exit(EXIT_FAILURE); } + if (dump_hw_params) { + fprintf(stderr, _("HW Params of device \"%s\":\n"), + snd_pcm_name(handle)); + fprintf(stderr, "--------------------\n"); + snd_pcm_hw_params_dump(params, log); + fprintf(stderr, "--------------------\n"); + } err = snd_pcm_sw_params_current(handle, swparams); if (err < 0) { error(_("Unable to get current sw params.")); @@ -2703,7 +2710,7 @@ static void begin_wave(int fd, size_t cnt) WaveHeader h; WaveFmtBody f; WaveChunkHeader cf, cd; - int bits; + int width, physical_width; uint32_t tmp; uint16_t tmp2; @@ -2711,23 +2718,22 @@ static void begin_wave(int fd, size_t cnt) if (cnt == (size_t)-2) cnt = 0x7fffff00; - bits = 8; + width = snd_pcm_format_physical_width(hwparams.format); + physical_width = snd_pcm_format_width(hwparams.format); + + if (width < 0 || physical_width < 0) + goto _format; + switch ((unsigned long) hwparams.format) { case SND_PCM_FORMAT_U8: - bits = 8; - break; case SND_PCM_FORMAT_S16_LE: - bits = 16; - break; case SND_PCM_FORMAT_S24_LE: /* S24_LE is 24 bits stored in 32 bit width with 8 bit padding */ case SND_PCM_FORMAT_S32_LE: case SND_PCM_FORMAT_FLOAT_LE: - bits = 32; - break; case SND_PCM_FORMAT_S24_3LE: - bits = 24; break; default: +_format: error(_("Wave doesn't support %s format..."), snd_pcm_format_name(hwparams.format)); prg_exit(EXIT_FAILURE); } @@ -2745,17 +2751,11 @@ static void begin_wave(int fd, size_t cnt) f.format = LE_SHORT(WAV_FMT_PCM); f.channels = LE_SHORT(hwparams.channels); f.sample_fq = LE_INT(hwparams.rate); -#if 0 - tmp2 = (samplesize == 8) ? 1 : 2; - f.byte_p_spl = LE_SHORT(tmp2); - tmp = dsp_speed * hwparams.channels * (uint32_t) tmp2; -#else - tmp2 = hwparams.channels * snd_pcm_format_physical_width(hwparams.format) / 8; + tmp2 = hwparams.channels * physical_width / 8; f.byte_p_spl = LE_SHORT(tmp2); tmp = (uint32_t) tmp2 * hwparams.rate; -#endif f.byte_p_sec = LE_INT(tmp); - f.bit_p_spl = LE_SHORT(bits); + f.bit_p_spl = LE_SHORT(width); cd.type = WAV_DATA; cd.length = LE_INT(cnt); diff --git a/bat/alsa.c b/bat/alsa.c index 67489c4..457e79a 100644 --- a/bat/alsa.c +++ b/bat/alsa.c @@ -221,7 +221,17 @@ static int set_snd_pcm_params(struct bat *bat, struct pcm_container *sndpcm) period_time = buffer_time / DIV_BUFFERTIME; - /* Set buffer time and period time */ + /* Set period time and buffer time */ + err = snd_pcm_hw_params_set_period_time_near(sndpcm->handle, + params, &period_time, 0); + if (err < 0) { + fprintf(bat->err, _("Set parameter to device error: ")); + fprintf(bat->err, _("period time: %d %s: %s(%d)\n"), + period_time, + device_name, snd_strerror(err), err); + return err; + } + err = snd_pcm_hw_params_set_buffer_time_near(sndpcm->handle, params, &buffer_time, 0); if (err < 0) { @@ -231,16 +241,6 @@ static int set_snd_pcm_params(struct bat *bat, struct pcm_container *sndpcm) device_name, snd_strerror(err), err); return err; } - - err = snd_pcm_hw_params_set_period_time_near(sndpcm->handle, - params, &period_time, 0); - if (err < 0) { - fprintf(bat->err, _("Set parameter to device error: ")); - fprintf(bat->err, _("period time: %d %s: %s(%d)\n"), - period_time, - device_name, snd_strerror(err), err); - return err; - } } /* Write the parameters to the driver */ diff --git a/configure.ac b/configure.ac index 4532778..6f2705c 100644 --- a/configure.ac +++ b/configure.ac @@ -1,6 +1,6 @@ dnl Process this file with autoconf to produce a configure script. AC_PREREQ(2.59) -AC_INIT(alsa-utils, 1.2.14) +AC_INIT(alsa-utils, 1.2.15.1) AC_CONFIG_SRCDIR([aplay/aplay.c]) AC_PREFIX_DEFAULT(/usr) AM_INIT_AUTOMAKE([subdir-objects]) @@ -419,8 +419,8 @@ AC_ARG_WITH([systemdsystemunitdir], if test "x$with_systemdsystemunitdir" != xno; then AC_SUBST([systemdsystemunitdir], [$with_systemdsystemunitdir]) fi -AM_CONDITIONAL(HAVE_SYSTEMD, [test "$have_min_systemd" = "yes" \ - -a -n "$with_systemdsystemunitdir" -a "x$with_systemdsystemunitdir" != xno ]) +AM_CONDITIONAL(HAVE_SYSTEMD, [test -n "$with_systemdsystemunitdir" \ + -a "x$with_systemdsystemunitdir" != xno ]) AC_ARG_WITH([asound-state-dir], AS_HELP_STRING([--with-asound-state-dir=DIR], [Directory to place asound.state file in]), diff --git a/include/aconfig.h b/include/aconfig.h index 2a3157e..19afc88 100644 --- a/include/aconfig.h +++ b/include/aconfig.h @@ -141,7 +141,7 @@ #define PACKAGE_NAME "alsa-utils" /* Define to the full name and version of this package. */ -#define PACKAGE_STRING "alsa-utils 1.2.14" +#define PACKAGE_STRING "alsa-utils 1.2.15.1" /* Define to the one symbol short name of this package. */ #define PACKAGE_TARNAME "alsa-utils" @@ -150,7 +150,7 @@ #define PACKAGE_URL "" /* Define to the version of this package. */ -#define PACKAGE_VERSION "1.2.14" +#define PACKAGE_VERSION "1.2.15.1" /* directory containing sample data */ #define SOUNDSDIR "/vendor/usr/share/sounds/alsa" @@ -165,7 +165,7 @@ #define TIME_WITH_SYS_TIME 1 /* ALSA util version */ -#define VERSION "1.2.14" +#define VERSION "1.2.15.1" /* Define if FFADO library is available */ /* #undef WITH_FFADO */ diff --git a/include/version.h b/include/version.h index 506bbca..90a5760 100644 --- a/include/version.h +++ b/include/version.h @@ -4,6 +4,6 @@ #define SND_UTIL_MAJOR 1 #define SND_UTIL_MINOR 2 -#define SND_UTIL_SUBMINOR 14 +#define SND_UTIL_SUBMINOR 15 #define SND_UTIL_VERSION ((SND_UTIL_MAJOR<<16)|(SND_UTIL_MINOR<<8)|SND_UTIL_SUBMINOR) -#define SND_UTIL_VERSION_STR "1.2.14" +#define SND_UTIL_VERSION_STR "1.2.15.1" diff --git a/seq/aconnect/aconnect.c b/seq/aconnect/aconnect.c index edd028f..d782ef0 100644 --- a/seq/aconnect/aconnect.c +++ b/seq/aconnect/aconnect.c @@ -31,11 +31,13 @@ static int show_all; -static void error_handler(const char *file, int line, const char *function, int err, const char *fmt, ...) + +#if SND_LIB_VER(1, 2, 15) < SND_LIB_VERSION +static void error_handler(const char *file, int line, const char *function, int errcode, const char *fmt, ...) { va_list arg; - if (err == ENOENT) /* Ignore those misleading "warnings" */ + if (errcode == ENOENT) /* Ignore those misleading "warnings" */ return; va_start(arg, fmt); fprintf(stderr, "ALSA lib %s:%i:(%s) ", file, line, function); @@ -45,6 +47,16 @@ static void error_handler(const char *file, int line, const char *function, int putc('\n', stderr); va_end(arg); } +#else +static snd_lib_log_handler_t original_log_handler; +static void log_handler(int prio, int interface, const char *file, int line, const char *function, int errcode, const char *fmt, va_list arg) +{ + if (prio == SND_LOG_ERROR && errcode == ENOENT) /* Ignore those misleading "warnings" */ + return; + if (original_log_handler) + original_log_handler(prio, interface, file, line, function, errcode, fmt, arg); +} +#endif static void usage(void) { @@ -356,7 +368,11 @@ int main(int argc, char **argv) return 1; } +#if SND_LIB_VER(1, 2, 15) < SND_LIB_VERSION snd_lib_error_set_handler(error_handler); +#else + original_log_handler = snd_lib_log_set_handler(log_handler); +#endif switch (command) { case LIST: