From bdf5c137786319f92ba85409a9899b676b817674 Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Fri, 21 Nov 2025 12:25:42 +0100 Subject: [PATCH] alsactl: ucm: implement boot parameters and card group sync infrastructure Implement boot parameter management for multi-card synchronization. Add boot_params.c with read/write functions for Boot control element, card group configuration file support, and validity checking. Extend init_ucm.c with boot/restore logic and card state handling. Add -G (group-file) and -m (force-ucm-restore) command line options. Signed-off-by: Jaroslav Kysela --- alsactl/Makefile.am | 3 +- alsactl/alsactl.c | 12 + alsactl/alsactl.h | 26 + alsactl/boot_params.c | 1083 +++++++++++++++++++++++++++++++++++++++++ alsactl/init_parse.c | 2 +- alsactl/init_ucm.c | 260 +++++++++- alsactl/lock.c | 34 ++ alsactl/state.c | 6 +- 8 files changed, 1407 insertions(+), 19 deletions(-) create mode 100644 alsactl/boot_params.c diff --git a/alsactl/Makefile.am b/alsactl/Makefile.am index e771717..1f6b712 100644 --- a/alsactl/Makefile.am +++ b/alsactl/Makefile.am @@ -11,7 +11,8 @@ 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 \ +alsactl_SOURCES=alsactl.c state.c lock.c utils.c \ + init_parse.c init_ucm.c boot_params.c \ daemon.c monitor.c clean.c info.c alsactl_CFLAGS=$(AM_CFLAGS) -D__USE_GNU \ diff --git a/alsactl/alsactl.c b/alsactl/alsactl.c index 5614ea7..adde783 100644 --- a/alsactl/alsactl.c +++ b/alsactl/alsactl.c @@ -44,6 +44,9 @@ #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; @@ -52,6 +55,7 @@ int do_lock = 0; int use_syslog = 0; char *command; char *statefile = NULL; +char *groupfile = SYS_CARD_GROUP; char *lockpath = SYS_LOCKPATH; char *lockfile = SYS_LOCKFILE; @@ -78,6 +82,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" }, @@ -105,6 +110,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 @@ -301,6 +307,9 @@ int main(int argc, char *argv[]) case 'f': cfgfile = optarg; break; + case 'G': + groupfile = optarg; + break; case 'a': cfgdir = optarg; break; @@ -341,6 +350,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; diff --git a/alsactl/alsactl.h b/alsactl/alsactl.h index f86f8d9..6f7b3a2 100644 --- a/alsactl/alsactl.h +++ b/alsactl/alsactl.h @@ -10,6 +10,7 @@ extern int do_lock; extern int use_syslog; extern char *command; extern char *statefile; +extern char *groupfile; extern char *lockpath; extern char *lockfile; @@ -44,6 +45,18 @@ void log_handler(int prio, int interface, const char *file, int line, const char #define FLAG_UCM_BOOT (1<<2) #define FLAG_UCM_DEFAULTS (1<<3) #define FLAG_UCM_NODEV (1<<4) +#define FLAG_UCM_RESTORE (1<<5) + +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); @@ -53,14 +66,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); diff --git a/alsactl/boot_params.c b/alsactl/boot_params.c new file mode 100644 index 0000000..736597e --- /dev/null +++ b/alsactl/boot_params.c @@ -0,0 +1,1083 @@ +/* + * 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", group_id, cardno, 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; + } + +_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/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..386f415 100644 --- a/alsactl/init_ucm.c +++ b/alsactl/init_ucm.c @@ -21,52 +21,280 @@ #include "aconfig.h" #include +#include +#include #include "alsactl.h" #ifdef HAVE_ALSA_USE_CASE_H #include +#define DEFAULT_SYNC_TIME 20 + /* - * 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 */ -int init_ucm(int flags, int cardno) +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) { - snd_use_case_mgr_t *uc_mgr; - char id[32], *nodev; int err; - if (flags & FLAG_UCM_DISABLED) { - dbg("ucm disabled"); - return -ENXIO; + 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) ? "" : "-"; - snprintf(id, sizeof(id), "%shw:%d", nodev, cardno); - err = snd_use_case_mgr_open(&uc_mgr, id); + 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); - if (err < 0) - return err; - if (flags & FLAG_UCM_FBOOT) { + + 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) { - /* nothing */ + /* _fboot not found but _boot requested - continue */ + err = 0; } else if (err < 0) { - goto _error; + return err; } } + if (flags & FLAG_UCM_BOOT) { err = snd_use_case_set(uc_mgr, "_boot", NULL); dbg("ucm _boot: %d", err); if (err < 0) - goto _error; + 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[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; + } + + 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; + } + + 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; + } + +_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; + } + + 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/state.c b/alsactl/state.c index 3ccaab6..f9b9260 100644 --- a/alsactl/state.c +++ b/alsactl/state.c @@ -1752,7 +1752,10 @@ 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)) + 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 +1769,7 @@ int load_state(const char *cfgdir, const char *file, finalerr = err; initfailed(iter.card, "restore", err); } +unlock_card: card_unlock(lock_fd, iter.card); } err = finalerr ? finalerr : snd_card_iterator_error(&iter); -- 2.47.3