]> git.alsa-project.org Git - alsa-utils.git/commitdiff
alsactl: ucm: implement boot parameters and card group sync infrastructure
authorJaroslav Kysela <perex@perex.cz>
Fri, 21 Nov 2025 11:25:42 +0000 (12:25 +0100)
committerJaroslav Kysela <perex@perex.cz>
Thu, 4 Dec 2025 14:08:14 +0000 (15:08 +0100)
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 <perex@perex.cz>
alsactl/Makefile.am
alsactl/alsactl.c
alsactl/alsactl.h
alsactl/boot_params.c [new file with mode: 0644]
alsactl/init_parse.c
alsactl/init_ucm.c
alsactl/lock.c
alsactl/state.c

index e7717173955cd91ca9e5e929d0a2163b7b341951..1f6b712786ddc94013411f96eb7d8c21402c14e0 100644 (file)
@@ -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 \
index 5614ea75e3391e3ce1aaab7a4cacc00c631fa9c8..adde783637e7aab3498140c3ba051f0666207baa 100644 (file)
@@ -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;
index f86f8d9c9cc1c4f4901c704c896c5921bd801b5f..6f7b3a254214c3c77164aca3766a680657a0a6e8 100644 (file)
@@ -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 (file)
index 0000000..736597e
--- /dev/null
@@ -0,0 +1,1083 @@
+/*
+ *  Advanced Linux Sound Architecture Control Program - Boot Parameters
+ *  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 <stddef.h>
+#include <limits.h>
+#include <errno.h>
+#include <string.h>
+#include <time.h>
+#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:
+ *
+ * <GROUP_NAME> {
+ *     card.0 <int>             # primary card in group
+ *     card.1 <int>             # optional - next card in group
+ *     card.2 <int>             # optional - next card in group
+ *     boot_realtime <int64>    # boot time (CLOCK_REALTIME) in seconds
+ *     boot_last_update <int64> # timestamp of last configuration update (CLOCK_REALTIME) in seconds
+ *     boot_monotonic <int64>   # boot time (CLOCK_MONOTONIC_RAW) in seconds
+ *     boot_synctime <int64>    # 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", &current_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;
+}
+
index 267db4b3c53aa1b83afc155d243724e5f2ecfd33..a34cb0e3f9c82874052704fb979dba340ff517e7 100644 (file)
@@ -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)
index 31e8daff229fdf86b9ad80603df4ab9c4f3be2bb..386f415811b685dc07e66ed13213588c70d33634 100644 (file)
 
 #include "aconfig.h"
 #include <stddef.h>
+#include <stdlib.h>
+#include <errno.h>
 #include "alsactl.h"
 
 #ifdef HAVE_ALSA_USE_CASE_H
 
 #include <alsa/use-case.h>
 
+#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) ? "" : "<<<InBoot=1>>>";
+       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;
 }
 
index 4927b70cdcb1201fb144a18e55bed9631b86476c..e3df982daa7d2784dd4f9fab015918b4f7d9b422 100644 (file)
@@ -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);
index 3ccaab6fceccee099bae313483eb5eb33feef8c9..f9b926079892dc883e479f056250a2c3f0d65a7c 100644 (file)
@@ -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);