/* AVB support */ /* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */ /* SPDX-FileCopyrightText: Copyright © 2025 Alexandre Malki */ /* SPDX-License-Identifier: MIT */ #include "aecp-aem.h" #include "aecp-aem-descriptors.h" #include "aecp-aem-cmds-resps/cmd-resp-helpers.h" #include "utils.h" /* The headers including the command and response of the system */ //#include "aecp-aem-cmds-resps/cmd-available.h" /* ACQUIRE_ENTITY */ static int handle_acquire_entity_avb_legacy(struct aecp *aecp, int64_t now, const void *m, int len) { struct server *server = aecp->server; const struct avb_packet_aecp_aem *p = m; const struct avb_packet_aecp_aem_acquire *ae; const struct descriptor *desc; uint16_t desc_type, desc_id; ae = (const struct avb_packet_aecp_aem_acquire*)p->payload; desc_type = ntohs(ae->descriptor_type); desc_id = ntohs(ae->descriptor_id); desc = server_find_descriptor(server, desc_type, desc_id); if (desc == NULL) return reply_status(aecp, AVB_AECP_AEM_STATUS_NO_SUCH_DESCRIPTOR, p, len); if (desc_type != AVB_AEM_DESC_ENTITY || desc_id != 0) return reply_not_implemented(aecp, m, len); return reply_success(aecp, m, len); } /* LOCK_ENTITY */ static int handle_lock_entity_avb_legacy(struct aecp *aecp, int64_t now, const void *m, int len) { struct server *server = aecp->server; const struct avb_packet_aecp_aem *p = m; const struct avb_packet_aecp_aem_acquire *ae; const struct descriptor *desc; uint16_t desc_type, desc_id; ae = (const struct avb_packet_aecp_aem_acquire*)p->payload; desc_type = ntohs(ae->descriptor_type); desc_id = ntohs(ae->descriptor_id); desc = server_find_descriptor(server, desc_type, desc_id); if (desc == NULL) return reply_status(aecp, AVB_AECP_AEM_STATUS_NO_SUCH_DESCRIPTOR, p, len); if (desc_type != AVB_AEM_DESC_ENTITY || desc_id != 0) return reply_not_implemented(aecp, m, len); return reply_success(aecp, m, len); } /* READ_DESCRIPTOR */ static int handle_read_descriptor_common(struct aecp *aecp, int64_t now, const void *m, int len) { struct server *server = aecp->server; const struct avb_ethernet_header *h = m; const struct avb_packet_aecp_aem *p = SPA_PTROFF(h, sizeof(*h), void); struct avb_packet_aecp_aem *reply; const struct avb_packet_aecp_aem_read_descriptor *rd; uint16_t desc_type, desc_id; const struct descriptor *desc; uint8_t buf[2048]; size_t size, psize; rd = (struct avb_packet_aecp_aem_read_descriptor*)p->payload; desc_type = ntohs(rd->descriptor_type); desc_id = ntohs(rd->descriptor_id); pw_log_info("descriptor type:%04x index:%d", desc_type, desc_id); desc = server_find_descriptor(server, desc_type, desc_id); if (desc == NULL) return reply_status(aecp, AVB_AECP_AEM_STATUS_NO_SUCH_DESCRIPTOR, m, len); memcpy(buf, m, len); psize = sizeof(*rd); size = sizeof(*h) + sizeof(*reply) + psize; memcpy(buf + size, desc->ptr, desc->size); size += desc->size; psize += desc->size; h = (void*)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); AVB_PACKET_SET_LENGTH(&reply->aecp.hdr, psize + 12); return avb_server_send_packet(server, h->src, AVB_TSN_ETH, buf, size); } /* GET_AVB_INFO */ static int handle_get_avb_info_common(struct aecp *aecp, int64_t now, const void *m, int len) { struct server *server = aecp->server; const struct avb_ethernet_header *h = m; const struct avb_packet_aecp_aem *p = SPA_PTROFF(h, sizeof(*h), void); struct avb_packet_aecp_aem *reply; struct avb_packet_aecp_aem_get_avb_info *i; struct avb_aem_desc_avb_interface *avb_interface; uint16_t desc_type, desc_id; const struct descriptor *desc; uint8_t buf[2048]; size_t size, psize; i = (struct avb_packet_aecp_aem_get_avb_info*)p->payload; desc_type = ntohs(i->descriptor_type); desc_id = ntohs(i->descriptor_id); desc = server_find_descriptor(server, desc_type, desc_id); if (desc == NULL) return reply_status(aecp, AVB_AECP_AEM_STATUS_NO_SUCH_DESCRIPTOR, m, len); if (desc_type != AVB_AEM_DESC_AVB_INTERFACE || desc_id != 0) return reply_not_implemented(aecp, m, len); avb_interface = desc->ptr; memcpy(buf, m, len); psize = sizeof(*i); size = sizeof(*h) + sizeof(*reply) + psize; h = (void*)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); AVB_PACKET_SET_LENGTH(&reply->aecp.hdr, psize + 12); i = (struct avb_packet_aecp_aem_get_avb_info*)reply->payload; i->gptp_grandmaster_id = avb_interface->clock_identity; i->propagation_delay = htonl(0); i->gptp_domain_number = avb_interface->domain_number; i->flags = 0; i->msrp_mappings_count = htons(0); return avb_server_send_packet(server, h->src, AVB_TSN_ETH, buf, size); } /* AEM_COMMAND */ /* TODO in the case the AVB mode allows you to modifiy a Milan readonly descriptor, then create a array of is_readonly depending on the mode used */ static const char * const cmd_names[] = { [AVB_AECP_AEM_CMD_ACQUIRE_ENTITY] = "acquire-entity", [AVB_AECP_AEM_CMD_LOCK_ENTITY] = "lock-entity", [AVB_AECP_AEM_CMD_ENTITY_AVAILABLE] = "entity-available", [AVB_AECP_AEM_CMD_CONTROLLER_AVAILABLE] = "controller-available", [AVB_AECP_AEM_CMD_READ_DESCRIPTOR] = "read-descriptor", [AVB_AECP_AEM_CMD_WRITE_DESCRIPTOR] = "write-descriptor", [AVB_AECP_AEM_CMD_SET_CONFIGURATION] = "set-configuration", [AVB_AECP_AEM_CMD_GET_CONFIGURATION] = "get-configuration", [AVB_AECP_AEM_CMD_SET_STREAM_FORMAT] = "set-stream-format", [AVB_AECP_AEM_CMD_GET_STREAM_FORMAT] = "get-stream-format", [AVB_AECP_AEM_CMD_SET_VIDEO_FORMAT] = "set-video-format", [AVB_AECP_AEM_CMD_GET_VIDEO_FORMAT] = "get-video-format", [AVB_AECP_AEM_CMD_SET_SENSOR_FORMAT] = "set-sensor-format", [AVB_AECP_AEM_CMD_GET_SENSOR_FORMAT] = "get-sensor-format", [AVB_AECP_AEM_CMD_SET_STREAM_INFO] = "set-stream-info", [AVB_AECP_AEM_CMD_GET_STREAM_INFO] = "get-stream-info", [AVB_AECP_AEM_CMD_SET_NAME] = "set-name", [AVB_AECP_AEM_CMD_GET_NAME] = "get-name", [AVB_AECP_AEM_CMD_SET_ASSOCIATION_ID] = "set-association-id", [AVB_AECP_AEM_CMD_GET_ASSOCIATION_ID] = "get-association-id", [AVB_AECP_AEM_CMD_SET_SAMPLING_RATE] = "set-sampling-rate", [AVB_AECP_AEM_CMD_GET_SAMPLING_RATE] = "get-sampling-rate", [AVB_AECP_AEM_CMD_SET_CLOCK_SOURCE] = "set-clock-source", [AVB_AECP_AEM_CMD_GET_CLOCK_SOURCE] = "get-clock-source", [AVB_AECP_AEM_CMD_SET_CONTROL] = "set-control", [AVB_AECP_AEM_CMD_GET_CONTROL] = "get-control", [AVB_AECP_AEM_CMD_INCREMENT_CONTROL] = "increment-control", [AVB_AECP_AEM_CMD_DECREMENT_CONTROL] = "decrement-control", [AVB_AECP_AEM_CMD_SET_SIGNAL_SELECTOR] = "set-signal-selector", [AVB_AECP_AEM_CMD_GET_SIGNAL_SELECTOR] = "get-signal-selector", [AVB_AECP_AEM_CMD_SET_MIXER] = "set-mixer", [AVB_AECP_AEM_CMD_GET_MIXER] = "get-mixer", [AVB_AECP_AEM_CMD_SET_MATRIX] = "set-matrix", [AVB_AECP_AEM_CMD_GET_MATRIX] = "get-matrix", [AVB_AECP_AEM_CMD_START_STREAMING] = "start-streaming", [AVB_AECP_AEM_CMD_STOP_STREAMING] = "stop-streaming", [AVB_AECP_AEM_CMD_REGISTER_UNSOLICITED_NOTIFICATION] = "register-unsolicited-notification", [AVB_AECP_AEM_CMD_DEREGISTER_UNSOLICITED_NOTIFICATION] = "deregister-unsolicited-notification", [AVB_AECP_AEM_CMD_IDENTIFY_NOTIFICATION] = "identify-notification", [AVB_AECP_AEM_CMD_GET_AVB_INFO] = "get-avb-info", [AVB_AECP_AEM_CMD_GET_AS_PATH] = "get-as-path", [AVB_AECP_AEM_CMD_GET_COUNTERS] = "get-counters", [AVB_AECP_AEM_CMD_REBOOT] = "reboot", [AVB_AECP_AEM_CMD_GET_AUDIO_MAP] = "get-audio-map", [AVB_AECP_AEM_CMD_ADD_AUDIO_MAPPINGS] = "add-audio-mappings", [AVB_AECP_AEM_CMD_REMOVE_AUDIO_MAPPINGS] = "remove-audio-mappings", [AVB_AECP_AEM_CMD_GET_VIDEO_MAP] = "get-video-map", [AVB_AECP_AEM_CMD_ADD_VIDEO_MAPPINGS] = "add-video-mappings", [AVB_AECP_AEM_CMD_REMOVE_VIDEO_MAPPINGS] = "remove-video-mappings", [AVB_AECP_AEM_CMD_GET_SENSOR_MAP] = "get-sensor-map" }; /* AEM_COMMAND */ struct cmd_info { /** * \brief Is Readonly is a hint used to decide whether or not the * unsollocited notifications is to be sent for this descriptor or not */ const bool is_readonly; /** * \brief handle a command for a specific descriptor */ int (*handle_command) (struct aecp *aecp, int64_t now, const void *p, int len); /** * \brief Response are sent upon changes that occure internally * and that are then propagated to the network and are not * unsollicited notifications */ int (*handle_response) (struct aecp *aecp, int64_t now, const void *p, int len); /** * \brief Handling of the unsolicited notification that are used * to inform subscribed controller about the change of status of * a specific descriptor or the counter associted with it */ int (*handle_unsol_timer) (struct aecp *aecp, int64_t now); }; #define AECP_AEM_HANDLE_CMD(cmd, readonly_desc, handle_exec) \ [cmd] = { \ .is_readonly = readonly_desc, \ .handle_command = handle_exec \ } #define AECP_AEM_HANDLE_RESP(cmd, handle_cmd, handle_exec_unsol) \ [cmd] = { \ .name = name_str, \ .is_readonly = false, \ .handle_response = handle_cmd \ } #define AECP_AEM_CMD_RESP_AND_UNSOL(cmd, readonly_desc, handle_exec, \ handle_exec_unsol) \ [cmd] = { \ .name = name_str, \ .is_readonly = readonly_desc, \ .handle = handle_exec, \ .handle_unsol = handle_exec_unsol \ } static const struct cmd_info cmd_info_avb_legacy[] = { AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_ACQUIRE_ENTITY, true, handle_acquire_entity_avb_legacy), AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_LOCK_ENTITY, true, handle_lock_entity_avb_legacy), AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_READ_DESCRIPTOR, true, handle_read_descriptor_common), AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_GET_AVB_INFO, true, handle_get_avb_info_common), }; static const struct cmd_info cmd_info_milan_v12[] = { /** Milan V1.2 do not implement acquire */ AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_ACQUIRE_ENTITY, true, NULL), AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_LOCK_ENTITY, false, NULL), AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_ENTITY_AVAILABLE, true, NULL), AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_READ_DESCRIPTOR, true, handle_read_descriptor_common), }; static const struct { const struct cmd_info *cmd_info; size_t count; } cmd_info_modes[AVB_MODE_MAX] = { [AVB_MODE_LEGACY] = { .cmd_info = cmd_info_avb_legacy, .count = SPA_N_ELEMENTS(cmd_info_avb_legacy), }, [AVB_MODE_MILAN_V12] = { .cmd_info = cmd_info_milan_v12, .count = SPA_N_ELEMENTS(cmd_info_milan_v12), }, }; int avb_aecp_aem_handle_command(struct aecp *aecp, const void *m, int len) { const struct avb_ethernet_header *h = m; const struct avb_packet_aecp_aem *p = SPA_PTROFF(h, sizeof(*h), void); uint16_t cmd_type; struct server *server = aecp->server; const struct cmd_info *info; struct timespec ts_now = {0}; int64_t now; cmd_type = AVB_PACKET_AEM_GET_COMMAND_TYPE(p); pw_log_info("mode: %s aem command %s", get_avb_mode_str(server->avb_mode), cmd_names[cmd_type]); if (cmd_info_modes[server->avb_mode].count >= cmd_type) { return reply_not_implemented(aecp, m, len); } info = &cmd_info_modes[server->avb_mode].cmd_info[cmd_type]; if (info->handle_command == NULL) return reply_not_implemented(aecp, m, len); if (clock_gettime(CLOCK_TAI, &ts_now)) { pw_log_warn("clock_gettime(CLOCK_TAI): %m\n"); } now = SPA_TIMESPEC_TO_NSEC(&ts_now); return info->handle_command(aecp, now, m, len); } int avb_aecp_aem_handle_response(struct aecp *aecp, const void *m, int len) { return 0; }