diff --git a/src/modules/meson.build b/src/modules/meson.build index 11b29a117..900968e5e 100644 --- a/src/modules/meson.build +++ b/src/modules/meson.build @@ -806,6 +806,7 @@ if build_module_avb 'module-avb/aecp-aem-cmds-resps/cmd-get-set-configuration.c', 'module-avb/aecp-aem-cmds-resps/cmd-lock-entity.c', 'module-avb/aecp-aem-cmds-resps/cmd-register-unsolicited-notifications.c', + 'module-avb/aecp-aem-cmds-resps/cmd-get-dynamic-info.c', 'module-avb/aecp-aem-cmds-resps/reply-unsol-helpers.c', 'module-avb/es-builder.c', 'module-avb/avdecc.c', diff --git a/src/modules/module-avb/aecp-aem-cmds-resps/cmd-get-dynamic-info.c b/src/modules/module-avb/aecp-aem-cmds-resps/cmd-get-dynamic-info.c new file mode 100644 index 000000000..6382638d8 --- /dev/null +++ b/src/modules/module-avb/aecp-aem-cmds-resps/cmd-get-dynamic-info.c @@ -0,0 +1,187 @@ +/* SPDX-FileCopyrightText: Copyright © 2025 Alexandre Malki */ +/* SPDX-License-Identifier: MIT */ + +#include "../aecp.h" +#include "../aecp-aem.h" +#include "../aecp-aem-descriptors.h" +#include "../aecp-aem-state.h" +#include "../internal.h" + +#include "cmd-get-dynamic-info.h" +#include "cmd-resp-helpers.h" + +/** + * \see IEEE 1722.1-2021 Section 7.4.76 GET_DYNAMIC_INFO + * \see Milan v1.2 Section 5.4.2.29 + * + * Returns the current dynamic state for all descriptors in the requested + * configuration. Each descriptor type contributes a fixed-size record; + * descriptor types with no mutable runtime state are skipped. + */ +int handle_cmd_get_dynamic_info_milan_v12(struct aecp *aecp, int64_t now, + const void *m, int len) +{ + uint8_t buf[AVB_PACKET_MILAN_DEFAULT_MTU + sizeof(struct avb_ethernet_header)]; + struct server *server = aecp->server; + const struct avb_ethernet_header *h_in = m; + const struct avb_packet_aecp_aem *p_in = SPA_PTROFF(h_in, sizeof(*h_in), void); + const struct avb_packet_aecp_aem_get_dynamic_info *cmd = + (const struct avb_packet_aecp_aem_get_dynamic_info *)p_in->payload; + struct avb_ethernet_header *h; + struct avb_packet_aecp_aem *reply; + struct avb_packet_aecp_aem_get_dynamic_info *resp_hdr; + uint16_t config_idx; + const struct descriptor *entity_desc; + const struct avb_aem_desc_entity *entity; + const struct descriptor *d; + size_t psize, size; + uint8_t *ptr; + + /* + * Milan v1.2 Section 5.4: AECP PDUs shall not exceed the interface MTU. + * Reject anything that would overflow our response buffer before + * touching it. + */ + if ((size_t)len > sizeof(buf)) { + pw_log_warn("%s: command PDU exceeds MTU (%d bytes)", __func__, len); + return reply_bad_arguments(aecp, m, len); + } + + config_idx = ntohs(cmd->configuration_index); + + entity_desc = server_find_descriptor(server, AVB_AEM_DESC_ENTITY, 0); + if (entity_desc == NULL) + return reply_status(aecp, AVB_AECP_AEM_STATUS_NO_SUCH_DESCRIPTOR, m, len); + + entity = (const struct avb_aem_desc_entity *)entity_desc->ptr; + if (config_idx >= ntohs(entity->configurations_count)) + return reply_status(aecp, AVB_AECP_AEM_STATUS_NO_SUCH_DESCRIPTOR, m, len); + + memcpy(buf, m, len); + h = (struct avb_ethernet_header *)buf; + reply = SPA_PTROFF(h, sizeof(*h), void); + + AVB_PACKET_AECP_SET_MESSAGE_TYPE(&reply->aecp, AVB_AECP_MESSAGE_TYPE_AEM_RESPONSE); + AVB_PACKET_AECP_SET_STATUS(&reply->aecp, AVB_AECP_AEM_STATUS_SUCCESS); + + resp_hdr = (struct avb_packet_aecp_aem_get_dynamic_info *)reply->payload; + resp_hdr->configuration_index = htons(config_idx); + resp_hdr->reserved = 0; + + psize = sizeof(*resp_hdr); + size = sizeof(*h) + sizeof(*reply) + psize; + ptr = buf + size; + + spa_list_for_each(d, &server->descriptors, link) { + switch (d->type) { + case AVB_AEM_DESC_ENTITY: { + const struct avb_aem_desc_entity *e = + (const struct avb_aem_desc_entity *)d->ptr; + struct avb_aem_dynamic_info_entity rec; + + if (size + sizeof(rec) > sizeof(buf)) { + pw_log_warn("%s: buffer full, truncating response", __func__); + goto done; + } + rec.hdr.descriptor_type = htons(d->type); + rec.hdr.descriptor_index = htons(d->index); + rec.current_configuration = e->current_configuration; + rec.reserved = 0; + memcpy(ptr, &rec, sizeof(rec)); + ptr += sizeof(rec); + psize += sizeof(rec); + size += sizeof(rec); + break; + } + case AVB_AEM_DESC_AUDIO_UNIT: { + const struct avb_aem_desc_audio_unit *au = + (const struct avb_aem_desc_audio_unit *)d->ptr; + struct avb_aem_dynamic_info_audio_unit rec; + + if (size + sizeof(rec) > sizeof(buf)) { + pw_log_warn("%s: buffer full, truncating response", __func__); + goto done; + } + rec.hdr.descriptor_type = htons(d->type); + rec.hdr.descriptor_index = htons(d->index); + rec.current_sampling_rate = au->current_sampling_rate.pull_frequency; + memcpy(ptr, &rec, sizeof(rec)); + ptr += sizeof(rec); + psize += sizeof(rec); + size += sizeof(rec); + break; + } + case AVB_AEM_DESC_STREAM_INPUT: { + const struct aecp_aem_stream_input_state *state = + (const struct aecp_aem_stream_input_state *)d->ptr; + struct avb_aem_dynamic_info_stream rec; + + if (size + sizeof(rec) > sizeof(buf)) { + pw_log_warn("%s: buffer full, truncating response", __func__); + goto done; + } + memset(&rec, 0, sizeof(rec)); + rec.hdr.descriptor_type = htons(d->type); + rec.hdr.descriptor_index = htons(d->index); + rec.stream_id = 0; + rec.stream_format = state->desc.current_format; + if (state->desc.current_format != 0) + rec.stream_info_flags = + htonl(AVB_AEM_STREAM_INFO_FLAG_STREAM_FORMAT_VALID); + memcpy(ptr, &rec, sizeof(rec)); + ptr += sizeof(rec); + psize += sizeof(rec); + size += sizeof(rec); + break; + } + case AVB_AEM_DESC_STREAM_OUTPUT: { + const struct aecp_aem_stream_output_state *state = + (const struct aecp_aem_stream_output_state *)d->ptr; + struct avb_aem_dynamic_info_stream rec; + + if (size + sizeof(rec) > sizeof(buf)) { + pw_log_warn("%s: buffer full, truncating response", __func__); + goto done; + } + memset(&rec, 0, sizeof(rec)); + rec.hdr.descriptor_type = htons(d->type); + rec.hdr.descriptor_index = htons(d->index); + rec.stream_id = 0; + rec.stream_format = state->desc.current_format; + if (state->desc.current_format != 0) + rec.stream_info_flags = + htonl(AVB_AEM_STREAM_INFO_FLAG_STREAM_FORMAT_VALID); + memcpy(ptr, &rec, sizeof(rec)); + ptr += sizeof(rec); + psize += sizeof(rec); + size += sizeof(rec); + break; + } + case AVB_AEM_DESC_CLOCK_DOMAIN: { + const struct avb_aem_desc_clock_domain *cd = + (const struct avb_aem_desc_clock_domain *)d->ptr; + struct avb_aem_dynamic_info_clock_domain rec; + + if (size + sizeof(rec) > sizeof(buf)) { + pw_log_warn("%s: buffer full, truncating response", __func__); + goto done; + } + rec.hdr.descriptor_type = htons(d->type); + rec.hdr.descriptor_index = htons(d->index); + rec.clock_source_index = cd->clock_source_index; + rec.reserved = 0; + memcpy(ptr, &rec, sizeof(rec)); + ptr += sizeof(rec); + psize += sizeof(rec); + size += sizeof(rec); + break; + } + default: + break; + } + } + +done: + AVB_PACKET_SET_LENGTH(&reply->aecp.hdr, psize + AVB_PACKET_CONTROL_DATA_OFFSET); + return avb_server_send_packet(server, h->src, AVB_TSN_ETH, buf, size); +} diff --git a/src/modules/module-avb/aecp-aem-cmds-resps/cmd-get-dynamic-info.h b/src/modules/module-avb/aecp-aem-cmds-resps/cmd-get-dynamic-info.h new file mode 100644 index 000000000..1491b11fc --- /dev/null +++ b/src/modules/module-avb/aecp-aem-cmds-resps/cmd-get-dynamic-info.h @@ -0,0 +1,12 @@ +/* SPDX-FileCopyrightText: Copyright © 2025 Alexandre Malki */ +/* SPDX-License-Identifier: MIT */ + +#ifndef __AVB_AECP_AEM_CMD_GET_DYNAMIC_INFO_H__ +#define __AVB_AECP_AEM_CMD_GET_DYNAMIC_INFO_H__ + +#include "../aecp-aem.h" + +int handle_cmd_get_dynamic_info_milan_v12(struct aecp *aecp, int64_t now, + const void *m, int len); + +#endif /* __AVB_AECP_AEM_CMD_GET_DYNAMIC_INFO_H__ */ diff --git a/src/modules/module-avb/aecp-aem.c b/src/modules/module-avb/aecp-aem.c index 51fbab23b..f6e86402f 100644 --- a/src/modules/module-avb/aecp-aem.c +++ b/src/modules/module-avb/aecp-aem.c @@ -21,6 +21,7 @@ #include "aecp-aem-cmds-resps/cmd-get-set-stream-format.h" #include "aecp-aem-cmds-resps/cmd-get-set-clock-source.h" #include "aecp-aem-cmds-resps/cmd-lock-entity.h" +#include "aecp-aem-cmds-resps/cmd-get-dynamic-info.h" /* ACQUIRE_ENTITY */ @@ -364,6 +365,9 @@ static const struct cmd_info cmd_info_milan_v12[] = { AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_GET_SAMPLING_RATE, true, handle_cmd_get_sampling_rate_common), + + AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_GET_DYNAMIC_INFO, true, + handle_cmd_get_dynamic_info_milan_v12), }; static const struct { diff --git a/src/modules/module-avb/aecp-aem.h b/src/modules/module-avb/aecp-aem.h index e4e120452..1eefd413b 100644 --- a/src/modules/module-avb/aecp-aem.h +++ b/src/modules/module-avb/aecp-aem.h @@ -226,6 +226,44 @@ struct avb_packet_aecp_aem_operation_status { uint16_t percent_complete; } __attribute__ ((__packed__)); +/* GET_DYNAMIC_INFO (IEEE 1722.1-2021 Section 7.4.76, Milan v1.2 Section 5.4.2.29) */ +struct avb_packet_aecp_aem_get_dynamic_info { + uint16_t configuration_index; + uint16_t reserved; +} __attribute__ ((__packed__)); + +struct avb_aem_dynamic_info_hdr { + uint16_t descriptor_type; + uint16_t descriptor_index; +} __attribute__ ((__packed__)); + +struct avb_aem_dynamic_info_entity { + struct avb_aem_dynamic_info_hdr hdr; + uint16_t current_configuration; + uint16_t reserved; +} __attribute__ ((__packed__)); + +struct avb_aem_dynamic_info_audio_unit { + struct avb_aem_dynamic_info_hdr hdr; + uint32_t current_sampling_rate; +} __attribute__ ((__packed__)); + +struct avb_aem_dynamic_info_stream { + struct avb_aem_dynamic_info_hdr hdr; + uint64_t stream_id; + uint64_t stream_format; + uint32_t stream_info_flags; + uint16_t acmp_connection_count; + uint8_t flags_ex; + uint8_t pbsta; +} __attribute__ ((__packed__)); + +struct avb_aem_dynamic_info_clock_domain { + struct avb_aem_dynamic_info_hdr hdr; + uint16_t clock_source_index; + uint16_t reserved; +} __attribute__ ((__packed__)); + struct avb_packet_aecp_aem { struct avb_packet_aecp_header aecp; #if __BYTE_ORDER == __BIG_ENDIAN