From 8d46253ac3716bd6f7e6bac78aed6955dba0b21b Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Thu, 27 Nov 2025 18:39:17 +0100 Subject: [PATCH] alsactl: ucm: add wrestore command and wait_for_card() for boot synchronization 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 --- alsactl/Makefile.am | 2 +- alsactl/alsactl.c | 6 +- alsactl/alsactl.h | 2 + alsactl/init_ucm.c | 2 - alsactl/state.c | 4 + alsactl/wait.c | 200 ++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 212 insertions(+), 4 deletions(-) create mode 100644 alsactl/wait.c diff --git a/alsactl/Makefile.am b/alsactl/Makefile.am index 6725c9c..26d80ba 100644 --- a/alsactl/Makefile.am +++ b/alsactl/Makefile.am @@ -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 diff --git a/alsactl/alsactl.c b/alsactl/alsactl.c index 8ea4a84..a91462e 100644 --- a/alsactl/alsactl.c +++ b/alsactl/alsactl.c @@ -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); diff --git a/alsactl/alsactl.h b/alsactl/alsactl.h index aeee114..994485a 100644 --- a/alsactl/alsactl.h +++ b/alsactl/alsactl.h @@ -2,6 +2,7 @@ #include #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) */ diff --git a/alsactl/init_ucm.c b/alsactl/init_ucm.c index 386f415..60967c4 100644 --- a/alsactl/init_ucm.c +++ b/alsactl/init_ucm.c @@ -29,8 +29,6 @@ #include -#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 diff --git a/alsactl/state.c b/alsactl/state.c index 4d326a1..2ce9b62 100644 --- a/alsactl/state.c +++ b/alsactl/state.c @@ -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 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; +} -- 2.47.3