--- /dev/null
+/*
+ * 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", ¤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;
+}
+
#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;
}