mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2025-12-26 12:10:04 +01:00
392 lines
13 KiB
C
392 lines
13 KiB
C
/* 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"
|
|
#include "aecp-aem-cmds-resps/cmd-get-set-configuration.h"
|
|
#include "aecp-aem-cmds-resps/cmd-lock-entity.h"
|
|
#include "aecp-aem-cmds-resps/cmd-register-unsolicited-notifications.h"
|
|
#include "aecp-aem-cmds-resps/cmd-deregister-unsolicited-notifications.h"
|
|
#include "aecp-aem-cmds-resps/cmd-get-set-name.h"
|
|
#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"
|
|
|
|
|
|
/* 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_GET_CONFIGURATION, false,
|
|
handle_cmd_get_configuration_common),
|
|
|
|
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 should not implement acquire */
|
|
AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_ACQUIRE_ENTITY, true,
|
|
direct_reply_not_supported),
|
|
|
|
AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_LOCK_ENTITY, false,
|
|
handle_cmd_lock_entity_milan_v12),
|
|
|
|
AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_ENTITY_AVAILABLE, true,
|
|
handle_cmd_entity_available_milan_v12),
|
|
|
|
AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_SET_STREAM_FORMAT, false,
|
|
handle_cmd_set_stream_format_milan_v12),
|
|
|
|
AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_GET_STREAM_FORMAT, true,
|
|
handle_cmd_get_stream_format_milan_v12),
|
|
|
|
AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_SET_CONFIGURATION, false,
|
|
handle_cmd_set_configuration_milan_v12),
|
|
|
|
AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_GET_CONFIGURATION, false,
|
|
handle_cmd_get_configuration_common),
|
|
|
|
AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_READ_DESCRIPTOR, true,
|
|
handle_read_descriptor_common),
|
|
|
|
AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_REGISTER_UNSOLICITED_NOTIFICATION,
|
|
false, handle_cmd_register_unsol_notif_milan_v12),
|
|
|
|
AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_DEREGISTER_UNSOLICITED_NOTIFICATION,
|
|
false, handle_cmd_deregister_unsol_notif_milan_v12),
|
|
|
|
AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_GET_AVB_INFO, true,
|
|
handle_get_avb_info_common),
|
|
|
|
AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_SET_NAME, false,
|
|
handle_cmd_set_name_common),
|
|
|
|
AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_GET_NAME, true,
|
|
handle_cmd_get_name_common),
|
|
|
|
AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_SET_CLOCK_SOURCE, false,
|
|
handle_cmd_set_clock_source_milan_v12),
|
|
|
|
AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_GET_CLOCK_SOURCE, true,
|
|
handle_cmd_get_clock_source_milan_v12),
|
|
};
|
|
|
|
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) {
|
|
pw_log_warn("Too many %d vs exp. %zu\n", cmd_type,
|
|
cmd_info_modes[server->avb_mode].count);
|
|
return reply_not_implemented(aecp, m, len);
|
|
}
|
|
|
|
info = &cmd_info_modes[server->avb_mode].cmd_info[cmd_type];
|
|
if (!info || !info->handle_command )
|
|
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;
|
|
}
|