]> git.alsa-project.org Git - alsa-utils.git/commitdiff
alsactl: ucm: add wrestore command and wait_for_card() for boot synchronization
authorJaroslav Kysela <perex@perex.cz>
Thu, 27 Nov 2025 17:39:17 +0000 (18:39 +0100)
committerJaroslav Kysela <perex@perex.cz>
Thu, 4 Dec 2025 14:08:14 +0000 (15:08 +0100)
Implement wait.c with wait_for_card() function to monitor Boot control
element and wait for card readiness using event-based polling.

Add "wrestore" command to wait for card ready state before restore.
Add FLAG_UCM_WAIT flag and move DEFAULT_SYNC_TIME to alsactl.h.

Signed-off-by: Jaroslav Kysela <perex@perex.cz>
alsactl/Makefile.am
alsactl/alsactl.c
alsactl/alsactl.h
alsactl/init_ucm.c
alsactl/state.c
alsactl/wait.c [new file with mode: 0644]

index 6725c9c50c325c1108ea676330e60c07d1ad2c5e..26d80ba075e2ece8760be10ceeddd15bae3eb7ce 100644 (file)
@@ -11,7 +11,7 @@ AM_CFLAGS = -D_GNU_SOURCE
 
 AM_CPPFLAGS = -I$(top_srcdir)/include
 
-alsactl_SOURCES=alsactl.c state.c lock.c utils.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
 
index 8ea4a84cd00e27f177f7d2a59080ab560e95c42f..a91462e9749c3c0cdf82263cfcdda5c639fc3aeb 100644 (file)
@@ -123,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" },
@@ -471,9 +472,12 @@ 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);
index aeee1143210c539e5cb38103f4725db640891d55..994485a47c3a237a611279152531747552d9b94a 100644 (file)
@@ -2,6 +2,7 @@
 #include <alsa/asoundlib.h>
 
 #define LOCK_TIMEOUT 10
+#define DEFAULT_SYNC_TIME 20
 
 extern int debugflag;
 extern int force_restore;
@@ -47,6 +48,7 @@ void log_handler(int prio, int interface, const char *file, int line, const char
 #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) */
index 386f415811b685dc07e66ed13213588c70d33634..60967c407be17cce9e407ca0ecaa90dca4ae92d1 100644 (file)
@@ -29,8 +29,6 @@
 
 #include <alsa/use-case.h>
 
-#define DEFAULT_SYNC_TIME 20
-
 /*
  * 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
index 4d326a19eabab99ec95e5faea7daed71c827327d..2ce9b625f139de6725043e3e07ecbb26e3b7940f 100644 (file)
@@ -1721,6 +1721,8 @@ int load_state(const char *cfgdir, const char *file,
                while ((cardname1 = snd_card_iterator_next(&iter)) != NULL) {
                        if (!do_init)
                                break;
+                       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;
@@ -1747,6 +1749,8 @@ int load_state(const char *cfgdir, const char *file,
        if (err < 0)
                goto out;
        while ((cardname1 = snd_card_iterator_next(&iter)) != NULL) {
+               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);
diff --git a/alsactl/wait.c b/alsactl/wait.c
new file mode 100644 (file)
index 0000000..bebcc3c
--- /dev/null
@@ -0,0 +1,200 @@
+/*
+ *  Advanced Linux Sound Architecture Control Program - Wait for Boot
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   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 <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <time.h>
+#include <alsa/asoundlib.h>
+#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;
+}