mirror of
https://github.com/alsa-project/alsa-lib.git
synced 2026-02-05 04:06:34 -05:00
ucm: implement ValueDefaults.BootCardGroup and define use
We need a boot synchronization for multiple UCM cards where linking is expected like AMD ACP or Intel AVS drivers. This method is using a timestamp file which can be created and modified during the boot process (e.g. from the alsactl tool). The goal is to return a valid UCM configuration for standard applications combining multiple ALSA cards into one UCM configuration and cover the time window when all cards have not been probed yet. Signed-off-by: Jaroslav Kysela <perex@perex.cz>
This commit is contained in:
parent
5c4a683bd0
commit
554efca497
5 changed files with 356 additions and 12 deletions
279
src/ucm/main.c
279
src/ucm/main.c
|
|
@ -1039,6 +1039,224 @@ static int set_defaults(snd_use_case_mgr_t *uc_mgr, bool force)
|
|||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Read boot information from 'Boot' control element
|
||||
* \param uc_mgr Use case manager
|
||||
* \param boot_time Pointer to boot time output (or NULL)
|
||||
* \param sync_time Pointer to synchronization time window output (or NULL)
|
||||
* \param restore_time Pointer to restore time output (or NULL)
|
||||
* \param primary Pointer to primary card flag output (or NULL)
|
||||
* \return 0 on success, otherwise a negative error code
|
||||
*
|
||||
* Reads the 'Boot' control element with SND_CTL_ELEM_TYPE_INTEGER64 (count=3):
|
||||
* - index 0 = boot time in CLOCK_MONOTONIC_RAW (only seconds)
|
||||
* - index 1 = restore time in CLOCK_MONOTONIC_RAW (only seconds)
|
||||
* - index 2 = primary card number (identifies group)
|
||||
* Returns -1 for all parameters when the control element is not present
|
||||
*/
|
||||
static int boot_info(snd_use_case_mgr_t *uc_mgr, long long *boot_time, long long *sync_time,
|
||||
long long *restore_time, long long *primary)
|
||||
{
|
||||
struct ctl_list *ctl_list;
|
||||
snd_ctl_elem_id_t *id;
|
||||
snd_ctl_elem_info_t *info;
|
||||
snd_ctl_elem_value_t *value;
|
||||
int err;
|
||||
|
||||
if (boot_time)
|
||||
*boot_time = -1;
|
||||
if (sync_time)
|
||||
*sync_time = -1;
|
||||
if (restore_time)
|
||||
*restore_time = -1;
|
||||
if (primary)
|
||||
*primary = -1;
|
||||
|
||||
ctl_list = uc_mgr_get_master_ctl(uc_mgr);
|
||||
if (ctl_list == NULL || ctl_list->ctl == NULL)
|
||||
return 0;
|
||||
|
||||
snd_ctl_elem_id_alloca(&id);
|
||||
snd_ctl_elem_info_alloca(&info);
|
||||
snd_ctl_elem_value_alloca(&value);
|
||||
|
||||
snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_CARD);
|
||||
snd_ctl_elem_id_set_name(id, ".Boot");
|
||||
|
||||
snd_ctl_elem_info_set_id(info, id);
|
||||
err = snd_ctl_elem_info(ctl_list->ctl, info);
|
||||
if (err < 0)
|
||||
return 0;
|
||||
|
||||
if (snd_ctl_elem_info_get_type(info) != SND_CTL_ELEM_TYPE_INTEGER64) {
|
||||
snd_error(UCM, "Boot control element is not INTEGER64 type");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (snd_ctl_elem_info_get_count(info) != 4) {
|
||||
snd_error(UCM, "Boot control element does not have count=4");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
snd_ctl_elem_value_set_id(value, id);
|
||||
err = snd_ctl_elem_read(ctl_list->ctl, value);
|
||||
if (err < 0) {
|
||||
snd_error(UCM, "failed to read Boot control element: %s",
|
||||
snd_strerror(err));
|
||||
return err;
|
||||
}
|
||||
|
||||
if (boot_time)
|
||||
*boot_time = snd_ctl_elem_value_get_integer64(value, 0);
|
||||
if (sync_time)
|
||||
*restore_time = snd_ctl_elem_value_get_integer64(value, 1);
|
||||
if (restore_time)
|
||||
*restore_time = snd_ctl_elem_value_get_integer64(value, 2);
|
||||
if (primary)
|
||||
*primary = snd_ctl_elem_value_get_integer64(value, 3);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Wait using snd_ctl_read() and snd_ctl_wait() for boot synchronization
|
||||
* \param uc_mgr Use case manager
|
||||
* \param primary_card Primary ALSA card number
|
||||
* \return 0 on success, 1 if reparse is required, negative error code on failure
|
||||
*
|
||||
* This function uses boot_info() to read the Boot control element and waits
|
||||
* until the timeout has passed using snd_ctl_read() and snd_ctl_wait().
|
||||
* No file synchronization is used.
|
||||
*/
|
||||
static int boot_wait(snd_use_case_mgr_t *uc_mgr, int *_primary_card)
|
||||
{
|
||||
char *boot_card_sync_time = NULL;
|
||||
struct ctl_list *ctl_list;
|
||||
snd_ctl_event_t *event;
|
||||
long long boot_time_val, boot_synctime_val, restore_time_val, primary_card;
|
||||
long long timeout = 20; /* default timeout in seconds */
|
||||
long long timeout_guard = 5; /* guard time in seconds */
|
||||
struct timespec start_time, now;
|
||||
int err;
|
||||
|
||||
snd_ctl_event_alloca(&event);
|
||||
|
||||
if (_primary_card)
|
||||
*_primary_card = -1;
|
||||
|
||||
err = get_value1(uc_mgr, &boot_card_sync_time, &uc_mgr->value_list, "BootCardSyncTime");
|
||||
if (err == 0 && boot_card_sync_time != NULL) {
|
||||
long sync_time;
|
||||
if (safe_strtol(boot_card_sync_time, &sync_time) == 0 && sync_time > 0 && sync_time <= 240) {
|
||||
timeout = (time_t)sync_time;
|
||||
snd_trace(UCM, "BootCardSyncTime set to %ld seconds", (long)timeout);
|
||||
} else {
|
||||
snd_error(UCM, "Invalid BootCardSyncTime '%s', using default %ld seconds", boot_card_sync_time, (long)timeout);
|
||||
}
|
||||
free(boot_card_sync_time);
|
||||
}
|
||||
|
||||
ctl_list = uc_mgr_get_master_ctl(uc_mgr);
|
||||
if (ctl_list == NULL || ctl_list->ctl == NULL)
|
||||
return -ENODEV;
|
||||
|
||||
err = snd_ctl_subscribe_events(ctl_list->ctl, 1);
|
||||
if (err < 0) {
|
||||
snd_error(UCM, "cannot subscribe to control events: %s", snd_strerror(err));
|
||||
return err;
|
||||
}
|
||||
|
||||
clock_gettime(CLOCK_MONOTONIC_RAW, &start_time);
|
||||
|
||||
/* increase timeout to allow restore controls using udev/systemd */
|
||||
/* when timeout limit exceeds */
|
||||
timeout += timeout_guard;
|
||||
|
||||
while (1) {
|
||||
long long diff, remaining = 0;
|
||||
|
||||
clock_gettime(CLOCK_MONOTONIC_RAW, &now);
|
||||
|
||||
err = boot_info(uc_mgr, &boot_time_val, &boot_synctime_val, &restore_time_val, &primary_card);
|
||||
if (err < 0)
|
||||
goto _fin;
|
||||
|
||||
if (primary_card < INT_MIN || primary_card > INT_MAX) {
|
||||
err = -EINVAL;
|
||||
goto _fin;
|
||||
}
|
||||
|
||||
if (_primary_card)
|
||||
*_primary_card = primary_card;
|
||||
|
||||
snd_trace(UCM, "Boot info: boot_time=%lld, restore_time=%lld, primary=%lld",
|
||||
boot_time_val, restore_time_val, primary_card);
|
||||
|
||||
if (boot_time_val == -1) {
|
||||
snd_trace(UCM, "Boot control element not present, skipping boot wait");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (timeout > boot_synctime_val + timeout_guard) {
|
||||
timeout = boot_synctime_val + timeout_guard;
|
||||
snd_trace(UCM, "Boot sychronization time reduced from boot element to %lld", timeout);
|
||||
}
|
||||
|
||||
diff = now.tv_sec - restore_time_val;
|
||||
if (restore_time_val > 0) {
|
||||
snd_trace(UCM, "Controls restored, skipping boot wait");
|
||||
/* if restore was done before short time window, reparse */
|
||||
return diff < timeout_guard;
|
||||
}
|
||||
|
||||
diff = now.tv_sec - start_time.tv_sec;
|
||||
if (diff < 0 || diff >= timeout) {
|
||||
snd_trace(UCM, "Maximum wait time exceeded, proceeding");
|
||||
break;
|
||||
} else {
|
||||
remaining = timeout - diff;
|
||||
}
|
||||
|
||||
diff = now.tv_sec - boot_time_val;
|
||||
snd_trace(UCM, "Boot time diff %lld now %lld", diff, now.tv_sec, boot_time_val);
|
||||
if (diff < 0 || diff >= timeout) {
|
||||
snd_trace(UCM, "Boot timeout reached, proceeding");
|
||||
break;
|
||||
} else {
|
||||
remaining = timeout - diff;
|
||||
}
|
||||
|
||||
snd_trace(UCM, "Boot waiting %lld secs", remaining);
|
||||
err = snd_ctl_wait(ctl_list->ctl, remaining * 1000);
|
||||
if (err < 0) {
|
||||
snd_error(UCM, "snd_ctl_wait failed: %s", snd_strerror(err));
|
||||
goto _fin;
|
||||
}
|
||||
|
||||
if (err == 0)
|
||||
continue; /* Timeout, no events */
|
||||
|
||||
while (snd_ctl_read(ctl_list->ctl, event) > 0) {
|
||||
|
||||
if (!(snd_ctl_event_elem_get_mask(event) & SND_CTL_EVENT_MASK_VALUE))
|
||||
continue; /* Not a value change event */
|
||||
|
||||
if (snd_ctl_event_elem_get_interface(event) != SND_CTL_ELEM_IFACE_CARD ||
|
||||
snd_ctl_event_elem_get_index(event) != 0 ||
|
||||
strcmp(snd_ctl_event_elem_get_name(event), ".Boot") != 0)
|
||||
continue;
|
||||
|
||||
snd_trace(UCM, "Boot control element value changed");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
err = 0;
|
||||
_fin:
|
||||
snd_ctl_subscribe_events(ctl_list->ctl, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Import master config and execute the default sequence
|
||||
* \param uc_mgr Use case manager
|
||||
|
|
@ -1529,7 +1747,8 @@ int snd_use_case_mgr_open(snd_use_case_mgr_t **uc_mgr,
|
|||
const char *card_name)
|
||||
{
|
||||
snd_use_case_mgr_t *mgr;
|
||||
int err;
|
||||
int err, boot_result = 0, ucm_card, primary_card;
|
||||
char *s;
|
||||
|
||||
snd_trace(UCM, "{API call} open '%s'", card_name);
|
||||
|
||||
|
|
@ -1559,6 +1778,10 @@ int snd_use_case_mgr_open(snd_use_case_mgr_t **uc_mgr,
|
|||
if (card_name[0] == '<' && card_name[1] == '<' && card_name[2] == '<')
|
||||
card_name = parse_open_variables(mgr, card_name);
|
||||
|
||||
/* Application developers: This argument is not supposed to be set for standard applications. */
|
||||
if (uc_mgr_get_variable(mgr, "@InBoot"))
|
||||
mgr->in_boot = true;
|
||||
|
||||
err = uc_mgr_card_open(mgr);
|
||||
if (err < 0) {
|
||||
uc_mgr_free(mgr);
|
||||
|
|
@ -1571,6 +1794,7 @@ int snd_use_case_mgr_open(snd_use_case_mgr_t **uc_mgr,
|
|||
goto _err;
|
||||
}
|
||||
|
||||
_reparse:
|
||||
/* get info on use_cases and verify against card */
|
||||
err = import_master_config(mgr);
|
||||
if (err < 0) {
|
||||
|
|
@ -1582,12 +1806,57 @@ int snd_use_case_mgr_open(snd_use_case_mgr_t **uc_mgr,
|
|||
goto _err;
|
||||
}
|
||||
|
||||
err = check_empty_configuration(mgr);
|
||||
if (err < 0) {
|
||||
snd_error(UCM, "failed to import %s (empty configuration)", card_name);
|
||||
goto _err;
|
||||
if (!mgr->card_group) {
|
||||
err = check_empty_configuration(mgr);
|
||||
if (err < 0) {
|
||||
snd_error(UCM, "failed to import %s (empty configuration)", card_name);
|
||||
goto _err;
|
||||
}
|
||||
}
|
||||
|
||||
/* Handle BootCardGroup timestamp file logic (conf version 8+) */
|
||||
if (mgr->conf_format < 8 || !mgr->card_group)
|
||||
goto _std;
|
||||
|
||||
/* Skip if guard time passed (boot_result == 1) */
|
||||
ucm_card = uc_mgr_card_number(uc_mgr_get_master_ctl(mgr));
|
||||
if (ucm_card >= 0 && boot_result == 0) {
|
||||
/* If InBoot open argument is present, skip this wait loop */
|
||||
if (mgr->in_boot)
|
||||
goto _std;
|
||||
|
||||
boot_result = boot_wait(mgr, &primary_card);
|
||||
if (boot_result < 0) {
|
||||
snd_error(UCM, "boot_wait failed");
|
||||
err = boot_result;
|
||||
goto _err;
|
||||
}
|
||||
|
||||
/* Check if this card is marked as primary (primary_card == 1) */
|
||||
if (primary_card != ucm_card) {
|
||||
/* Not the primary card, mark as linked */
|
||||
uc_mgr_free_verb(mgr);
|
||||
uc_mgr_free_ctl_list(mgr);
|
||||
s = strdup("1");
|
||||
if (s == NULL) {
|
||||
err = -ENOMEM;
|
||||
goto _err;
|
||||
}
|
||||
err = uc_mgr_add_value(&mgr->value_list, "Linked", s);
|
||||
if (err < 0)
|
||||
goto _err;
|
||||
goto _std;
|
||||
}
|
||||
|
||||
/* Reparse, if the restore time window is short */
|
||||
if (boot_result > 0) {
|
||||
uc_mgr_free_verb(mgr);
|
||||
uc_mgr_free_ctl_list(mgr);
|
||||
goto _reparse;
|
||||
}
|
||||
}
|
||||
|
||||
_std:
|
||||
*uc_mgr = mgr;
|
||||
snd_trace(UCM, "{API call} open '%s' succeed", card_name);
|
||||
return 0;
|
||||
|
|
|
|||
|
|
@ -466,6 +466,7 @@ static int evaluate_define_macro(snd_use_case_mgr_t *uc_mgr,
|
|||
err = snd_config_merge(uc_mgr->macros, d, 0);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -2919,6 +2920,24 @@ static int parse_master_file(snd_use_case_mgr_t *uc_mgr, snd_config_t *cfg)
|
|||
if (err < 0)
|
||||
return err;
|
||||
|
||||
/* parse ValueDefaults first */
|
||||
err = snd_config_search(cfg, "ValueDefaults", &n);
|
||||
if (err == 0) {
|
||||
err = parse_value(uc_mgr, &uc_mgr->value_list, n);
|
||||
if (err < 0) {
|
||||
snd_error(UCM, "failed to parse ValueDefaults");
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
err = uc_mgr_check_value(&uc_mgr->value_list, "BootCardGroup");
|
||||
if (err == 0) {
|
||||
uc_mgr->card_group = true;
|
||||
/* if we are in boot, skip the main parsing loop */
|
||||
if (uc_mgr->in_boot)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* parse master config sections */
|
||||
snd_config_for_each(i, next, cfg) {
|
||||
|
||||
|
|
@ -2969,13 +2988,8 @@ static int parse_master_file(snd_use_case_mgr_t *uc_mgr, snd_config_t *cfg)
|
|||
continue;
|
||||
}
|
||||
|
||||
/* get the default values */
|
||||
/* ValueDefaults is now parsed at the top of this function */
|
||||
if (strcmp(id, "ValueDefaults") == 0) {
|
||||
err = parse_value(uc_mgr, &uc_mgr->value_list, n);
|
||||
if (err < 0) {
|
||||
snd_error(UCM, "failed to parse ValueDefaults");
|
||||
return err;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -462,6 +462,38 @@ boot).
|
|||
|
||||
\image html ucm-seq-boot.svg
|
||||
|
||||
#### Boot Synchronization (Syntax 8+)
|
||||
|
||||
The *BootCardGroup* value in *ValueDefaults* allows multiple sound cards to coordinate
|
||||
their boot sequences. This value is detected at boot (alsactl/udev/systemd) time. Boot
|
||||
tools can provide boot synchronization information through a control element named
|
||||
'Boot' with 64-bit integer type. When present, the UCM library uses this control element
|
||||
to coordinate initialization timing.
|
||||
|
||||
The 'Boot' control element contains:
|
||||
- **index 0**: Boot time in CLOCK_MONOTONIC_RAW (seconds)
|
||||
- **index 1**: Restore time in CLOCK_MONOTONIC_RAW (seconds)
|
||||
- **index 2**: Primary card number (identifies also group)
|
||||
|
||||
The UCM open call waits until the boot timeout has passed or until restore state
|
||||
is notified through the synchronization Boot element. The timeout defaults to 30 seconds
|
||||
and can be customized using 'BootCardSyncTime' in 'ValueDefaults' (maximum 240 seconds).
|
||||
|
||||
If the 'Boot' control element is not present, no boot synchronization is performed.
|
||||
|
||||
Other cards in the group (primary card number is different) will have the "Linked"
|
||||
value set to "1", allowing UCM configuration files to detect and handle secondary
|
||||
cards appropriately.
|
||||
|
||||
Example configuration:
|
||||
|
||||
~~~{.html}
|
||||
ValueDefaults {
|
||||
BootCardGroup "amd-acp"
|
||||
BootCardSyncTime 10 # seconds
|
||||
}
|
||||
~~~
|
||||
|
||||
### Device volume
|
||||
|
||||
It is expected that the applications handle the volume settings. It is not recommended
|
||||
|
|
|
|||
|
|
@ -234,6 +234,8 @@ struct snd_use_case_mgr {
|
|||
const char *parse_variant;
|
||||
int parse_master_section;
|
||||
int sequence_hops;
|
||||
bool in_boot;
|
||||
bool card_group;
|
||||
|
||||
/* UCM cards list */
|
||||
struct list_head cards_list;
|
||||
|
|
@ -308,7 +310,8 @@ void uc_mgr_free(snd_use_case_mgr_t *uc_mgr);
|
|||
|
||||
static inline int uc_mgr_has_local_config(snd_use_case_mgr_t *uc_mgr)
|
||||
{
|
||||
return uc_mgr && snd_config_iterator_first(uc_mgr->local_config) !=
|
||||
return uc_mgr && uc_mgr->local_config &&
|
||||
snd_config_iterator_first(uc_mgr->local_config) !=
|
||||
snd_config_iterator_end(uc_mgr->local_config);
|
||||
}
|
||||
|
||||
|
|
@ -331,11 +334,14 @@ struct ctl_list *uc_mgr_get_ctl_by_name(snd_use_case_mgr_t *uc_mgr,
|
|||
const char *name, int idx);
|
||||
snd_ctl_t *uc_mgr_get_ctl(snd_use_case_mgr_t *uc_mgr);
|
||||
void uc_mgr_free_ctl_list(snd_use_case_mgr_t *uc_mgr);
|
||||
int uc_mgr_card_number(struct ctl_list *list);
|
||||
|
||||
void uc_mgr_free_value(struct list_head *base);
|
||||
|
||||
int uc_mgr_add_value(struct list_head *base, const char *key, char *val);
|
||||
|
||||
int uc_mgr_check_value(struct list_head *value_list, const char *identifier);
|
||||
|
||||
const char *uc_mgr_get_variable(snd_use_case_mgr_t *uc_mgr,
|
||||
const char *name);
|
||||
|
||||
|
|
|
|||
|
|
@ -334,6 +334,13 @@ __nomem:
|
|||
return -ENOMEM;
|
||||
}
|
||||
|
||||
int uc_mgr_card_number(struct ctl_list *ctl_list)
|
||||
{
|
||||
if (ctl_list == NULL)
|
||||
return -ENOENT;
|
||||
return snd_ctl_card_info_get_card(ctl_list->ctl_info);
|
||||
}
|
||||
|
||||
const char *uc_mgr_config_dir(int format)
|
||||
{
|
||||
const char *path;
|
||||
|
|
@ -908,3 +915,19 @@ const char *uc_mgr_alibcfg_by_device(snd_config_t **top, const char *name)
|
|||
*top = config;
|
||||
return name + 9;
|
||||
}
|
||||
|
||||
int uc_mgr_check_value(struct list_head *value_list, const char *identifier)
|
||||
{
|
||||
struct ucm_value *val;
|
||||
struct list_head *pos;
|
||||
|
||||
if (!value_list)
|
||||
return -ENOENT;
|
||||
|
||||
list_for_each(pos, value_list) {
|
||||
val = list_entry(pos, struct ucm_value, list);
|
||||
if (strcmp(identifier, val->name) == 0)
|
||||
return 0;
|
||||
}
|
||||
return -ENOENT;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue