From 4bc365cbb56d531418fc8e6f22270194cdda0a62 Mon Sep 17 00:00:00 2001 From: Nils Tonnaett Date: Tue, 18 Nov 2025 13:20:17 -0800 Subject: [PATCH 01/42] module-avb: add avb interface descriptor defines --- src/modules/module-avb/entity-model.h | 32 +++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 src/modules/module-avb/entity-model.h diff --git a/src/modules/module-avb/entity-model.h b/src/modules/module-avb/entity-model.h new file mode 100644 index 000000000..b3ae52286 --- /dev/null +++ b/src/modules/module-avb/entity-model.h @@ -0,0 +1,32 @@ +/* PipeWire */ +/* SPDX-FileCopyrightText: Copyright © 2025 Kebag-Logic */ +/* SPDX-FileCopyrightText: Copyright © 2025 Simon Gapp */ +/* SPDX-License-Identifier: MIT */ + +#ifndef _ENTITY_MODEL_MILAN_H_ +#define _ENTITY_MODEL_MILAN_H_ + +#include "aecp-aem-descriptors.h" + +/**************************************************************************************/ +/* IEEE 1722.1-2021, Sec. 7.2.8 AVB Interface Descriptor */ +/* Milan v1.2, Sec. 5.3.3.5 */ + +#define DSC_AVB_INTERFACE_LOCALIZED_DESCRIPTION AVB_AEM_DESC_INVALID +#define DSC_AVB_INTERFACE_INTERFACE_FLAGS (AVB_AEM_DESC_AVB_INTERFACE_FLAG_GPTP_GRANDMASTER_SUPPORTED | \ + AVB_AEM_DESC_AVB_INTERFACE_FLAG_GPTP_SUPPORTED | \ + AVB_AEM_DESC_AVB_INTERFACE_FLAG_SRP_SUPPORTED) +// TODO: This is a dynamic parameter +#define DSC_AVB_INTERFACE_CLOCK_IDENTITY 0x3cc0c6FFFE0002CB +#define DSC_AVB_INTERFACE_PRIORITY1 0xF8 +#define DSC_AVB_INTERFACE_CLOCK_CLASS 0xF8 +#define DSC_AVB_INTERFACE_OFFSET_SCALED_LOG_VARIANCE 0x436A +#define DSC_AVB_INTERFACE_CLOCK_ACCURACY 0x21 +#define DSC_AVB_INTERFACE_PRIORITY2 0xF8 +#define DSC_AVB_INTERFACE_DOMAIN_NUMBER 0 +#define DSC_AVB_INTERFACE_LOG_SYNC_INTERVAL 0 +#define DSC_AVB_INTERFACE_LOG_ANNOUNCE_INTERVAL 0 +#define DSC_AVB_INTERFACE_PDELAY_INTERVAL 0 +#define DSC_AVB_INTERFACE_PORT_NUMBER 0 + +#endif From 54a9495715c5797016ad21568319f41a659e69a3 Mon Sep 17 00:00:00 2001 From: Nils Tonnaett Date: Tue, 13 Jan 2026 17:40:13 -0800 Subject: [PATCH 02/42] module-avb: check ptp management socket periodically --- src/daemon/pipewire-avb.conf.in | 1 + src/modules/meson.build | 1 + src/modules/module-avb/avdecc.c | 4 + src/modules/module-avb/gptp.c | 251 ++++++++++++++++++++++++++++++ src/modules/module-avb/gptp.h | 73 +++++++++ src/modules/module-avb/internal.h | 2 + 6 files changed, 332 insertions(+) create mode 100644 src/modules/module-avb/gptp.c create mode 100644 src/modules/module-avb/gptp.h diff --git a/src/daemon/pipewire-avb.conf.in b/src/daemon/pipewire-avb.conf.in index e43015780..1a9f48499 100644 --- a/src/daemon/pipewire-avb.conf.in +++ b/src/daemon/pipewire-avb.conf.in @@ -71,6 +71,7 @@ avb.properties = { #ifname = "eth0.2" ifname = "enp3s0" milan = false + ptp.management-socket = "/var/run/ptp4lro" } avb.properties.rules = [ diff --git a/src/modules/meson.build b/src/modules/meson.build index 242b9fad3..439a37693 100644 --- a/src/modules/meson.build +++ b/src/modules/meson.build @@ -821,6 +821,7 @@ if build_module_avb 'module-avb/es-builder.c', 'module-avb/avdecc.c', 'module-avb/descriptors.c', + 'module-avb/gptp.c', 'module-avb/maap.c', 'module-avb/mmrp.c', 'module-avb/mrp.c', diff --git a/src/modules/module-avb/avdecc.c b/src/modules/module-avb/avdecc.c index 737244ccb..dae9dbed7 100644 --- a/src/modules/module-avb/avdecc.c +++ b/src/modules/module-avb/avdecc.c @@ -21,6 +21,7 @@ #include "avb.h" #include "packets.h" #include "internal.h" +#include "gptp.h" #include "stream.h" #include "acmp.h" #include "adp.h" @@ -405,6 +406,9 @@ struct server *avdecc_server_new(struct impl *impl, struct spa_dict *props) if ((res = server->transport->setup(server)) < 0) goto error_free; + server->gptp = avb_gptp_new(server); + + init_descriptors(server); server->mrp = avb_mrp_new(server); if (server->mrp == NULL) diff --git a/src/modules/module-avb/gptp.c b/src/modules/module-avb/gptp.c new file mode 100644 index 000000000..0779f817b --- /dev/null +++ b/src/modules/module-avb/gptp.c @@ -0,0 +1,251 @@ +/* PipeWire */ +/* SPDX-FileCopyrightText: Copyright © 2024 Dmitry Sharshakov */ +/* SPDX-FileCopyrightText: Copyright © 2025 Nils Tonnaett */ +/* SPDX-License-Identifier: MIT */ + +#include "gptp.h" +#include "module-avb/aecp-aem.h" +#include "pipewire/properties.h" +#include "pipewire/protocol.h" + +#include /* Definition of constants */ +#include +#include +#include +#include +#include +#include +#include +#include + + +struct gptp { + struct server *server; + + struct pw_loop *loop; + struct pw_timer_queue *timer_queue; + + struct spa_hook server_listener; + + struct spa_hook_list listener_list; + + struct spa_list attributes; + + char *ptp_mgmt_socket_path; + int ptp_fd; + uint32_t ptp_seq; + uint8_t clock_id[8]; + uint8_t gm_id[8]; +}; + +static int make_unix_ptp_mgmt_socket(const char *path) { + struct sockaddr_un addr; + + spa_autoclose int fd = socket(AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, 0); + if (fd < 0) { + pw_log_warn("Failed to create PTP management socket"); + return -1; + } + + int val = 1; + if (setsockopt(fd, SOL_SOCKET, SO_PASSCRED, &val, sizeof(val)) < 0) { + pw_log_warn("Failed to bind PTP management socket"); + return -1; + } + + spa_zero(addr); + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1); + + if (connect(fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) { + pw_log_warn("Failed to connect PTP management socket"); + return -1; + } + + return spa_steal_fd(fd); +} + +static bool update_ts_refclk(struct gptp *gptp) { + if (!gptp->ptp_mgmt_socket_path) + return false; + if (gptp->ptp_fd < 0) { + gptp->ptp_fd = make_unix_ptp_mgmt_socket(gptp->ptp_mgmt_socket_path); + if (gptp->ptp_fd < 0) + return false; + } + + // Read if something is left in the socket + int avail; + uint8_t tmp; + + ioctl(gptp->ptp_fd, FIONREAD, &avail); + pw_log_debug("Flushing stale data: %u bytes", avail); + while (avail-- && read(gptp->ptp_fd, &tmp, 1)); + + struct ptp_management_msg req; + spa_zero(req); + + req.major_sdo_id_message_type = PTP_MESSAGE_TYPE_MANAGEMENT; + req.ver = PTP_VERSION_1588_2008_2_1; + req.message_length_be = htobe16(sizeof(struct ptp_management_msg)); + spa_zero(req.clock_identity); + req.source_port_id_be = htobe16(getpid()); + req.log_message_interval = 127; + req.sequence_id_be = htobe16(gptp->ptp_seq++); + memset(req.target_port_identity, 0xff, 8); + req.target_port_id_be = htobe16(0xffff); + req.starting_boundary_hops = 1; + req.boundary_hops = 1; + req.action = PTP_MGMT_ACTION_GET; + req.tlv_type_be = htobe16(PTP_TLV_TYPE_MGMT); + // sent empty TLV, only sending management_id + req.management_message_length_be = htobe16(2); + req.management_id_be = htobe16(PTP_MGMT_ID_PARENT_DATA_SET); + + if (write(gptp->ptp_fd, &req, sizeof(req)) == -1) { + pw_log_warn("Failed to send PTP management request: %m"); + if (errno != ENOTCONN) + return false; + close(gptp->ptp_fd); + gptp->ptp_fd = make_unix_ptp_mgmt_socket(gptp->ptp_mgmt_socket_path); + if (gptp->ptp_fd > -1) + pw_log_info("Reopened PTP management socket"); + return false; + } + + uint8_t buf[sizeof(struct ptp_management_msg) + sizeof(struct ptp_parent_data_set)]; + if (read(gptp->ptp_fd, &buf, sizeof(buf)) == -1) { + pw_log_warn("Failed to receive PTP management response: %m"); + return false; + } + + struct ptp_management_msg res = *(struct ptp_management_msg *)buf; + struct ptp_parent_data_set parent = + *(struct ptp_parent_data_set *)(buf + sizeof(struct ptp_management_msg)); + + if ((res.ver & 0x0f) != 2) { + pw_log_warn("PTP major version is %d, expected 2", res.ver); + return false; + } + + if ((res.major_sdo_id_message_type & 0x0f) != PTP_MESSAGE_TYPE_MANAGEMENT) { + pw_log_warn("PTP management returned type %x, expected management", res.major_sdo_id_message_type); + return false; + } + + if (res.action != PTP_MGMT_ACTION_RESPONSE) { + pw_log_warn("PTP management returned action %d, expected response", res.action); + return false; + } + + if (be16toh(res.tlv_type_be) != PTP_TLV_TYPE_MGMT) { + pw_log_warn("PTP management returned tlv type %d, expected management", be16toh(res.tlv_type_be)); + return false; + } + + if (be16toh(res.management_id_be) != PTP_MGMT_ID_PARENT_DATA_SET) { + pw_log_warn("PTP management returned ID %d, expected PARENT_DATA_SET", be16toh(res.management_id_be)); + return false; + } + + uint16_t data_len = be16toh(res.management_message_length_be) - 2; + if (data_len != sizeof(struct ptp_parent_data_set)) + pw_log_warn("Unexpected PTP GET PARENT_DATA_SET response length %u, expected %zu", data_len, sizeof(struct ptp_parent_data_set)); + + uint8_t *cid = res.clock_identity; + if (memcmp(cid, gptp->clock_id, 8) != 0) + pw_log_info( + "Local clock ID: IEEE1588-2008:%02X-%02X-%02X-%02X-%02X-%02X-%02X-%02X:%d", + cid[0], + cid[1], + cid[2], + cid[3], + cid[4], + cid[5], + cid[6], + cid[7], + 0 /* domain */ + ); + + uint8_t *gmid = parent.gm_clock_id; + bool gmid_changed = false; + if (memcmp(gmid, gptp->gm_id, 8) != 0) { + pw_log_info( + "GM ID: IEEE1588-2008:%02X-%02X-%02X-%02X-%02X-%02X-%02X-%02X:%d", + gmid[0], + gmid[1], + gmid[2], + gmid[3], + gmid[4], + gmid[5], + gmid[6], + gmid[7], + 0 /* domain */ + ); + gmid_changed = true; + } + + // When GM is not equal to own clock we are clocked by external master + pw_log_debug("Synced to GM: %s", (memcmp(cid, gmid, 8) != 0) ? "true" : "false"); + + memcpy(gptp->clock_id, cid, 8); + memcpy(gptp->gm_id, gmid, 8); + return gmid_changed; +} + +static void gptp_periodic(void *data, uint64_t now) +{ + struct gptp *gptp = data; + update_ts_refclk(gptp); +} + +static void gptp_destroy(void *data) +{ + struct gptp *gptp = data; + spa_hook_remove(&gptp->server_listener); + free(gptp); +} + +static const struct server_events server_events = { + AVB_VERSION_SERVER_EVENTS, + .destroy = gptp_destroy, + .periodic = gptp_periodic, +}; + +void avb_gptp_destroy(struct avb_gptp *gptp) +{ + gptp_destroy(gptp); +} + +struct avb_gptp *avb_gptp_new(struct server *server) +{ + struct impl *impl; + struct gptp *gptp; + const char *str; + + gptp = calloc(1, sizeof(*gptp)); + if (gptp == NULL) + return NULL; + + gptp->server = server; + gptp->ptp_fd = -1; + + impl = server->impl; + + gptp->loop = pw_context_get_main_loop(impl->context); + gptp->timer_queue = pw_context_get_timer_queue(impl->context); + + str = pw_properties_get(impl->props, "ptp.management-socket"); + gptp->ptp_mgmt_socket_path = str ? strdup(str) : NULL; + + if(gptp->ptp_mgmt_socket_path) + gptp->ptp_fd = make_unix_ptp_mgmt_socket(gptp->ptp_mgmt_socket_path); + + spa_list_init(&gptp->attributes); + spa_hook_list_init(&gptp->listener_list); + + avdecc_server_add_listener(server, &gptp->server_listener, &server_events, gptp); + + return (struct avb_gptp*)gptp; +} + diff --git a/src/modules/module-avb/gptp.h b/src/modules/module-avb/gptp.h new file mode 100644 index 000000000..73cd9e7f0 --- /dev/null +++ b/src/modules/module-avb/gptp.h @@ -0,0 +1,73 @@ +/* PipeWire */ +/* SPDX-FileCopyrightText: Copyright © 2024 Dmitry Sharshakov */ +/* SPDX-License-Identifier: MIT */ + +#ifndef AVB_GPTP_H +#define AVB_GPTP_H + +#include +#include "internal.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define PTP_MESSAGE_TYPE_MANAGEMENT 0x0d +#define PTP_VERSION_1588_2008_2_1 0x12 +#define PTP_DEFAULT_LOG_MESSAGE_INTERVAL 127 +#define PTP_MGMT_ACTION_GET 0 +#define PTP_MGMT_ACTION_RESPONSE 2 +#define PTP_TLV_TYPE_MGMT 0x0001 +#define PTP_MGMT_ID_PARENT_DATA_SET 0x2002 + + +struct ptp_management_msg { + // 4 for major_sdo, 4 for msg_type + uint8_t major_sdo_id_message_type; + // 4 for minor, 4 for major + uint8_t ver; + uint16_t message_length_be; + uint8_t domain_number; + uint8_t minor_sdo_id; + uint16_t flags_be; + uint8_t correction_field[8]; + uint32_t message_type_specific; + uint8_t clock_identity[8]; + uint16_t source_port_id_be; + uint16_t sequence_id_be; + uint8_t control_field; + uint8_t log_message_interval; + uint8_t target_port_identity[8]; + uint16_t target_port_id_be; + uint8_t starting_boundary_hops; + uint8_t boundary_hops; + uint8_t action; + uint8_t reserved; + uint16_t tlv_type_be; + // length of data after this + 2 for management_id + uint16_t management_message_length_be; + uint16_t management_id_be; +} __attribute__((packed)); + +struct ptp_parent_data_set { + uint8_t parent_clock_id[8]; + uint16_t parent_port_id_be; + uint8_t parent_stats; + uint8_t reserved; + uint16_t log_variance_be; + int32_t phase_change_rate_be; + uint8_t gm_prio1; + uint8_t gm_clock_class; + uint8_t gm_clock_accuracy; + uint16_t gm_clock_variance_be; + uint8_t gm_prio2; + uint8_t gm_clock_id[8]; +} __attribute__((packed)); + +struct avb_gptp *avb_gptp_new(struct server *server); + +#ifdef __cplusplus +} +#endif + +#endif /* AVB_GPTP_H */ diff --git a/src/modules/module-avb/internal.h b/src/modules/module-avb/internal.h index e7698fa52..a9a38d0c6 100644 --- a/src/modules/module-avb/internal.h +++ b/src/modules/module-avb/internal.h @@ -14,6 +14,7 @@ extern "C" { #endif struct server; +struct avb_gptp; struct avb_mrp; #define AVB_TSN_ETH 0x22f0 @@ -114,6 +115,7 @@ struct server { unsigned debug_messages:1; + struct avb_gptp *gptp; struct avb_mrp *mrp; struct avb_mmrp *mmrp; struct avb_mvrp *mvrp; From f7526538148b6bb9547b86a22535c5e930001025 Mon Sep 17 00:00:00 2001 From: Nils Tonnaett Date: Tue, 13 Jan 2026 18:14:07 -0800 Subject: [PATCH 03/42] module-avb: free gptp->ptp_mgmt_socket_path --- src/modules/module-avb/gptp.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/modules/module-avb/gptp.c b/src/modules/module-avb/gptp.c index 0779f817b..fa2904891 100644 --- a/src/modules/module-avb/gptp.c +++ b/src/modules/module-avb/gptp.c @@ -203,6 +203,7 @@ static void gptp_destroy(void *data) { struct gptp *gptp = data; spa_hook_remove(&gptp->server_listener); + free(gptp->ptp_mgmt_socket_path); free(gptp); } From fc6f2e33e26121e5beb16f7d1dd84fca0a1e5f8c Mon Sep 17 00:00:00 2001 From: Nils Tonnaett Date: Wed, 14 Jan 2026 22:23:47 -0800 Subject: [PATCH 04/42] module-avb: close ptp_fd when destroyed --- src/modules/module-avb/gptp.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/modules/module-avb/gptp.c b/src/modules/module-avb/gptp.c index fa2904891..18cc3893c 100644 --- a/src/modules/module-avb/gptp.c +++ b/src/modules/module-avb/gptp.c @@ -203,6 +203,10 @@ static void gptp_destroy(void *data) { struct gptp *gptp = data; spa_hook_remove(&gptp->server_listener); + + if (gptp->ptp_fd != -1) + close(gptp->ptp_fd); + free(gptp->ptp_mgmt_socket_path); free(gptp); } From 16189ae1676775509331398c945770c2c44f7ca1 Mon Sep 17 00:00:00 2001 From: Nils Tonnaett Date: Wed, 14 Jan 2026 22:25:14 -0800 Subject: [PATCH 05/42] module-avb: add specs comments for PTP management message format --- src/modules/module-avb/gptp.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/modules/module-avb/gptp.h b/src/modules/module-avb/gptp.h index 73cd9e7f0..d520ad5ce 100644 --- a/src/modules/module-avb/gptp.h +++ b/src/modules/module-avb/gptp.h @@ -20,8 +20,11 @@ extern "C" { #define PTP_TLV_TYPE_MGMT 0x0001 #define PTP_MGMT_ID_PARENT_DATA_SET 0x2002 +/**************************************************************************************/ +/* IEEE 1588-2019, Sec. 15.4.1 PTP management message format - Common Fields */ struct ptp_management_msg { +/* IEEE 1588-2019, Sec. 13.3 Header */ // 4 for major_sdo, 4 for msg_type uint8_t major_sdo_id_message_type; // 4 for minor, 4 for major @@ -37,6 +40,7 @@ struct ptp_management_msg { uint16_t sequence_id_be; uint8_t control_field; uint8_t log_message_interval; + uint8_t target_port_identity[8]; uint16_t target_port_id_be; uint8_t starting_boundary_hops; From 6c43bdfa8523e20a89448e4aab2f99d568fe262b Mon Sep 17 00:00:00 2001 From: Nils Tonnaett Date: Fri, 16 Jan 2026 18:00:33 -0800 Subject: [PATCH 06/42] module-avb: htobe16/be16toh to htons/ntohs --- src/modules/module-avb/gptp.c | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/modules/module-avb/gptp.c b/src/modules/module-avb/gptp.c index 18cc3893c..1763bab4c 100644 --- a/src/modules/module-avb/gptp.c +++ b/src/modules/module-avb/gptp.c @@ -87,20 +87,20 @@ static bool update_ts_refclk(struct gptp *gptp) { req.major_sdo_id_message_type = PTP_MESSAGE_TYPE_MANAGEMENT; req.ver = PTP_VERSION_1588_2008_2_1; - req.message_length_be = htobe16(sizeof(struct ptp_management_msg)); + req.message_length_be = htons(sizeof(struct ptp_management_msg)); spa_zero(req.clock_identity); - req.source_port_id_be = htobe16(getpid()); + req.source_port_id_be = htons(getpid()); req.log_message_interval = 127; - req.sequence_id_be = htobe16(gptp->ptp_seq++); + req.sequence_id_be = htons(gptp->ptp_seq++); memset(req.target_port_identity, 0xff, 8); - req.target_port_id_be = htobe16(0xffff); + req.target_port_id_be = htons(0xffff); req.starting_boundary_hops = 1; req.boundary_hops = 1; req.action = PTP_MGMT_ACTION_GET; - req.tlv_type_be = htobe16(PTP_TLV_TYPE_MGMT); + req.tlv_type_be = htons(PTP_TLV_TYPE_MGMT); // sent empty TLV, only sending management_id - req.management_message_length_be = htobe16(2); - req.management_id_be = htobe16(PTP_MGMT_ID_PARENT_DATA_SET); + req.management_message_length_be = htons(2); + req.management_id_be = htons(PTP_MGMT_ID_PARENT_DATA_SET); if (write(gptp->ptp_fd, &req, sizeof(req)) == -1) { pw_log_warn("Failed to send PTP management request: %m"); @@ -138,17 +138,17 @@ static bool update_ts_refclk(struct gptp *gptp) { return false; } - if (be16toh(res.tlv_type_be) != PTP_TLV_TYPE_MGMT) { - pw_log_warn("PTP management returned tlv type %d, expected management", be16toh(res.tlv_type_be)); + if (ntohs(res.tlv_type_be) != PTP_TLV_TYPE_MGMT) { + pw_log_warn("PTP management returned tlv type %d, expected management", ntohs(res.tlv_type_be)); return false; } - if (be16toh(res.management_id_be) != PTP_MGMT_ID_PARENT_DATA_SET) { - pw_log_warn("PTP management returned ID %d, expected PARENT_DATA_SET", be16toh(res.management_id_be)); + if (ntohs(res.management_id_be) != PTP_MGMT_ID_PARENT_DATA_SET) { + pw_log_warn("PTP management returned ID %d, expected PARENT_DATA_SET", ntohs(res.management_id_be)); return false; } - uint16_t data_len = be16toh(res.management_message_length_be) - 2; + uint16_t data_len = ntohs(res.management_message_length_be) - 2; if (data_len != sizeof(struct ptp_parent_data_set)) pw_log_warn("Unexpected PTP GET PARENT_DATA_SET response length %u, expected %zu", data_len, sizeof(struct ptp_parent_data_set)); From 07533eb59023b1c07351c9496c210c0c0752ceea Mon Sep 17 00:00:00 2001 From: Nils Tonnaett Date: Fri, 16 Jan 2026 20:35:14 -0800 Subject: [PATCH 07/42] module-avb: check that PTP management response is complete --- src/modules/module-avb/gptp.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/modules/module-avb/gptp.c b/src/modules/module-avb/gptp.c index 1763bab4c..2c7141833 100644 --- a/src/modules/module-avb/gptp.c +++ b/src/modules/module-avb/gptp.c @@ -114,11 +114,17 @@ static bool update_ts_refclk(struct gptp *gptp) { } uint8_t buf[sizeof(struct ptp_management_msg) + sizeof(struct ptp_parent_data_set)]; - if (read(gptp->ptp_fd, &buf, sizeof(buf)) == -1) { + ssize_t ret = read(gptp->ptp_fd, &buf, sizeof(buf)); + if (ret == -1) { pw_log_warn("Failed to receive PTP management response: %m"); return false; } + if (ret != sizeof(buf)) { + pw_log_warn("Received incomplete PTP management response: %m"); + return false; + } + struct ptp_management_msg res = *(struct ptp_management_msg *)buf; struct ptp_parent_data_set parent = *(struct ptp_parent_data_set *)(buf + sizeof(struct ptp_management_msg)); From c8f2edd94e706d60111ecb534cdb97b7f6b52c0d Mon Sep 17 00:00:00 2001 From: Nils Tonnaett Date: Fri, 16 Jan 2026 20:45:32 -0800 Subject: [PATCH 08/42] module-avb: check ioctl for success --- src/modules/module-avb/gptp.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/modules/module-avb/gptp.c b/src/modules/module-avb/gptp.c index 2c7141833..d542a5063 100644 --- a/src/modules/module-avb/gptp.c +++ b/src/modules/module-avb/gptp.c @@ -78,7 +78,10 @@ static bool update_ts_refclk(struct gptp *gptp) { int avail; uint8_t tmp; - ioctl(gptp->ptp_fd, FIONREAD, &avail); + if (ioctl(gptp->ptp_fd, FIONREAD, &avail) == -1) { + pw_log_warn("Failed to get number of byes in ptp_fd input buffer: %m"); + return false; + } pw_log_debug("Flushing stale data: %u bytes", avail); while (avail-- && read(gptp->ptp_fd, &tmp, 1)); From f360af08896a727403dcf7c2cf54f0f4c1730318 Mon Sep 17 00:00:00 2001 From: Nils Tonnaett Date: Fri, 16 Jan 2026 21:25:04 -0800 Subject: [PATCH 09/42] module-avb: check return value of read when clearing ptp_fd input buffer --- src/modules/module-avb/gptp.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/modules/module-avb/gptp.c b/src/modules/module-avb/gptp.c index d542a5063..0b5469259 100644 --- a/src/modules/module-avb/gptp.c +++ b/src/modules/module-avb/gptp.c @@ -77,13 +77,18 @@ static bool update_ts_refclk(struct gptp *gptp) { // Read if something is left in the socket int avail; uint8_t tmp; + ssize_t ret; if (ioctl(gptp->ptp_fd, FIONREAD, &avail) == -1) { pw_log_warn("Failed to get number of byes in ptp_fd input buffer: %m"); return false; } - pw_log_debug("Flushing stale data: %u bytes", avail); - while (avail-- && read(gptp->ptp_fd, &tmp, 1)); + pw_log_debug("Clearing stale data: %u bytes", avail); + while (avail-- && (ret = read(gptp->ptp_fd, &tmp, 1)) > 0); + if (ret == -1) { + pw_log_warn("Failed to clear ptp_fd input buffer: %m"); + return false; + } struct ptp_management_msg req; spa_zero(req); @@ -117,7 +122,7 @@ static bool update_ts_refclk(struct gptp *gptp) { } uint8_t buf[sizeof(struct ptp_management_msg) + sizeof(struct ptp_parent_data_set)]; - ssize_t ret = read(gptp->ptp_fd, &buf, sizeof(buf)); + ret = read(gptp->ptp_fd, &buf, sizeof(buf)); if (ret == -1) { pw_log_warn("Failed to receive PTP management response: %m"); return false; From f4c26cd3ed8fc4cbc087562c3051a9c5394c7a1c Mon Sep 17 00:00:00 2001 From: Nils Tonnaett Date: Fri, 16 Jan 2026 22:00:37 -0800 Subject: [PATCH 10/42] module-avb: check that ptp management request is complete --- src/modules/module-avb/gptp.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/modules/module-avb/gptp.c b/src/modules/module-avb/gptp.c index 0b5469259..bb19bb819 100644 --- a/src/modules/module-avb/gptp.c +++ b/src/modules/module-avb/gptp.c @@ -110,7 +110,8 @@ static bool update_ts_refclk(struct gptp *gptp) { req.management_message_length_be = htons(2); req.management_id_be = htons(PTP_MGMT_ID_PARENT_DATA_SET); - if (write(gptp->ptp_fd, &req, sizeof(req)) == -1) { + ret = write(gptp->ptp_fd, &req, sizeof(req)); + if (ret == -1) { pw_log_warn("Failed to send PTP management request: %m"); if (errno != ENOTCONN) return false; @@ -120,6 +121,10 @@ static bool update_ts_refclk(struct gptp *gptp) { pw_log_info("Reopened PTP management socket"); return false; } + if (ret != sizeof(req)) { + pw_log_warn("Incomplete PTP management request: %m"); + return false; + } uint8_t buf[sizeof(struct ptp_management_msg) + sizeof(struct ptp_parent_data_set)]; ret = read(gptp->ptp_fd, &buf, sizeof(buf)); From bf6fae7df9f04cababc780f84c0973fa6b5d3f48 Mon Sep 17 00:00:00 2001 From: Nils Tonnaett Date: Sun, 12 Apr 2026 17:52:37 -0700 Subject: [PATCH 11/42] module-avb: fail if ptp.management-socket not set --- src/modules/module-avb/avdecc.c | 2 ++ src/modules/module-avb/gptp.c | 10 +++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/modules/module-avb/avdecc.c b/src/modules/module-avb/avdecc.c index dae9dbed7..f4cd77813 100644 --- a/src/modules/module-avb/avdecc.c +++ b/src/modules/module-avb/avdecc.c @@ -407,6 +407,8 @@ struct server *avdecc_server_new(struct impl *impl, struct spa_dict *props) goto error_free; server->gptp = avb_gptp_new(server); + if (server->gptp == NULL) + goto error_free; init_descriptors(server); diff --git a/src/modules/module-avb/gptp.c b/src/modules/module-avb/gptp.c index bb19bb819..c9f1aaac5 100644 --- a/src/modules/module-avb/gptp.c +++ b/src/modules/module-avb/gptp.c @@ -262,8 +262,12 @@ struct avb_gptp *avb_gptp_new(struct server *server) str = pw_properties_get(impl->props, "ptp.management-socket"); gptp->ptp_mgmt_socket_path = str ? strdup(str) : NULL; - if(gptp->ptp_mgmt_socket_path) + if(gptp->ptp_mgmt_socket_path) { gptp->ptp_fd = make_unix_ptp_mgmt_socket(gptp->ptp_mgmt_socket_path); + } else { + pw_log_error("server %p: ptp.management-socket not set", impl); + goto error_free; + } spa_list_init(&gptp->attributes); spa_hook_list_init(&gptp->listener_list); @@ -271,5 +275,9 @@ struct avb_gptp *avb_gptp_new(struct server *server) avdecc_server_add_listener(server, &gptp->server_listener, &server_events, gptp); return (struct avb_gptp*)gptp; + +error_free: + gptp_destroy(gptp); + return NULL; } From 5fd9c1eaff64f10ee4fa9dc47344f5d242133bec Mon Sep 17 00:00:00 2001 From: Nils Tonnaett Date: Sun, 12 Apr 2026 17:58:18 -0700 Subject: [PATCH 12/42] module-avb: fail if ptp management socket can't be created --- src/modules/module-avb/gptp.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/modules/module-avb/gptp.c b/src/modules/module-avb/gptp.c index c9f1aaac5..0d44278bd 100644 --- a/src/modules/module-avb/gptp.c +++ b/src/modules/module-avb/gptp.c @@ -246,6 +246,7 @@ struct avb_gptp *avb_gptp_new(struct server *server) struct impl *impl; struct gptp *gptp; const char *str; + int ret; gptp = calloc(1, sizeof(*gptp)); if (gptp == NULL) @@ -263,7 +264,11 @@ struct avb_gptp *avb_gptp_new(struct server *server) gptp->ptp_mgmt_socket_path = str ? strdup(str) : NULL; if(gptp->ptp_mgmt_socket_path) { - gptp->ptp_fd = make_unix_ptp_mgmt_socket(gptp->ptp_mgmt_socket_path); + ret = make_unix_ptp_mgmt_socket(gptp->ptp_mgmt_socket_path); + if (ret == -1) + goto error_free; + else + gptp->ptp_fd = ret; } else { pw_log_error("server %p: ptp.management-socket not set", impl); goto error_free; From 37efd5c5f95e7dc066462d297947b06587ffbdde Mon Sep 17 00:00:00 2001 From: Nils Tonnaett Date: Tue, 21 Apr 2026 17:13:44 -0700 Subject: [PATCH 13/42] module-avb: emit gm_changed event --- src/modules/module-avb/gptp.c | 7 +++++++ src/modules/module-avb/internal.h | 2 ++ 2 files changed, 9 insertions(+) diff --git a/src/modules/module-avb/gptp.c b/src/modules/module-avb/gptp.c index 0d44278bd..1a0297c16 100644 --- a/src/modules/module-avb/gptp.c +++ b/src/modules/module-avb/gptp.c @@ -18,6 +18,8 @@ #include #include +#define server_emit(s,m,v,...) spa_hook_list_call(&s->listener_list, struct server_events, m, v, ##__VA_ARGS__) +#define server_emit_gm_changed(s, n, g) server_emit(s, gm_changed, 0, n, g) struct gptp { struct server *server; @@ -207,8 +209,13 @@ static bool update_ts_refclk(struct gptp *gptp) { // When GM is not equal to own clock we are clocked by external master pw_log_debug("Synced to GM: %s", (memcmp(cid, gmid, 8) != 0) ? "true" : "false"); + struct timespec now; memcpy(gptp->clock_id, cid, 8); memcpy(gptp->gm_id, gmid, 8); + + clock_gettime(CLOCK_REALTIME, &now); + server_emit_gm_changed(gptp->server, SPA_TIMESPEC_TO_NSEC(&now), gmid); + return gmid_changed; } diff --git a/src/modules/module-avb/internal.h b/src/modules/module-avb/internal.h index a9a38d0c6..4fe8e7e75 100644 --- a/src/modules/module-avb/internal.h +++ b/src/modules/module-avb/internal.h @@ -61,6 +61,8 @@ struct server_events { void (*periodic) (void *data, uint64_t now); int (*command) (void *data, uint64_t now, const char *command, const char *args, FILE *out); + + void (*gm_changed) (void *data, uint64_t now, uint8_t gm_id[8]); }; struct descriptor { From 0345623e972361291365cdce3b4783b55ce0bbd0 Mon Sep 17 00:00:00 2001 From: Nils Tonnaett Date: Wed, 22 Apr 2026 19:56:47 -0700 Subject: [PATCH 14/42] module-avb: remove redundant init_descriptors() call --- src/modules/module-avb/avdecc.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/modules/module-avb/avdecc.c b/src/modules/module-avb/avdecc.c index f4cd77813..f6983c125 100644 --- a/src/modules/module-avb/avdecc.c +++ b/src/modules/module-avb/avdecc.c @@ -410,8 +410,6 @@ struct server *avdecc_server_new(struct impl *impl, struct spa_dict *props) if (server->gptp == NULL) goto error_free; - init_descriptors(server); - server->mrp = avb_mrp_new(server); if (server->mrp == NULL) goto error_free; From b197ae79c503774be1e44ccf97060e1fcbec6031 Mon Sep 17 00:00:00 2001 From: hackerman-kl Date: Tue, 28 Apr 2026 07:35:42 +0200 Subject: [PATCH 15/42] milan-avb: gptp: align code style with the rest of module-avb --- src/modules/module-avb/gptp.c | 85 ++++++++++++++++------------------- 1 file changed, 39 insertions(+), 46 deletions(-) diff --git a/src/modules/module-avb/gptp.c b/src/modules/module-avb/gptp.c index 1a0297c16..0a7fcb205 100644 --- a/src/modules/module-avb/gptp.c +++ b/src/modules/module-avb/gptp.c @@ -40,7 +40,8 @@ struct gptp { uint8_t gm_id[8]; }; -static int make_unix_ptp_mgmt_socket(const char *path) { +static int make_unix_ptp_mgmt_socket(const char *path) +{ struct sockaddr_un addr; spa_autoclose int fd = socket(AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, 0); @@ -67,7 +68,21 @@ static int make_unix_ptp_mgmt_socket(const char *path) { return spa_steal_fd(fd); } -static bool update_ts_refclk(struct gptp *gptp) { +static bool update_ts_refclk(struct gptp *gptp) +{ + struct ptp_management_msg req; + struct ptp_management_msg res; + struct ptp_parent_data_set parent; + struct timespec now; + uint8_t buf[sizeof(struct ptp_management_msg) + sizeof(struct ptp_parent_data_set)]; + uint8_t *cid; + uint8_t *gmid; + uint8_t tmp; + ssize_t ret; + uint16_t data_len; + int avail; + bool gmid_changed = false; + if (!gptp->ptp_mgmt_socket_path) return false; if (gptp->ptp_fd < 0) { @@ -76,11 +91,7 @@ static bool update_ts_refclk(struct gptp *gptp) { return false; } - // Read if something is left in the socket - int avail; - uint8_t tmp; - ssize_t ret; - + /* Read if something is left in the socket */ if (ioctl(gptp->ptp_fd, FIONREAD, &avail) == -1) { pw_log_warn("Failed to get number of byes in ptp_fd input buffer: %m"); return false; @@ -92,7 +103,6 @@ static bool update_ts_refclk(struct gptp *gptp) { return false; } - struct ptp_management_msg req; spa_zero(req); req.major_sdo_id_message_type = PTP_MESSAGE_TYPE_MANAGEMENT; @@ -108,7 +118,7 @@ static bool update_ts_refclk(struct gptp *gptp) { req.boundary_hops = 1; req.action = PTP_MGMT_ACTION_GET; req.tlv_type_be = htons(PTP_TLV_TYPE_MGMT); - // sent empty TLV, only sending management_id + /* sent empty TLV, only sending management_id */ req.management_message_length_be = htons(2); req.management_id_be = htons(PTP_MGMT_ID_PARENT_DATA_SET); @@ -128,7 +138,6 @@ static bool update_ts_refclk(struct gptp *gptp) { return false; } - uint8_t buf[sizeof(struct ptp_management_msg) + sizeof(struct ptp_parent_data_set)]; ret = read(gptp->ptp_fd, &buf, sizeof(buf)); if (ret == -1) { pw_log_warn("Failed to receive PTP management response: %m"); @@ -140,9 +149,8 @@ static bool update_ts_refclk(struct gptp *gptp) { return false; } - struct ptp_management_msg res = *(struct ptp_management_msg *)buf; - struct ptp_parent_data_set parent = - *(struct ptp_parent_data_set *)(buf + sizeof(struct ptp_management_msg)); + res = *(struct ptp_management_msg *)buf; + parent = *(struct ptp_parent_data_set *)(buf + sizeof(struct ptp_management_msg)); if ((res.ver & 0x0f) != 2) { pw_log_warn("PTP major version is %d, expected 2", res.ver); @@ -169,47 +177,32 @@ static bool update_ts_refclk(struct gptp *gptp) { return false; } - uint16_t data_len = ntohs(res.management_message_length_be) - 2; + data_len = ntohs(res.management_message_length_be) - 2; if (data_len != sizeof(struct ptp_parent_data_set)) - pw_log_warn("Unexpected PTP GET PARENT_DATA_SET response length %u, expected %zu", data_len, sizeof(struct ptp_parent_data_set)); + pw_log_warn("Unexpected PTP GET PARENT_DATA_SET response length %u, expected %zu", + data_len, sizeof(struct ptp_parent_data_set)); - uint8_t *cid = res.clock_identity; + cid = res.clock_identity; if (memcmp(cid, gptp->clock_id, 8) != 0) - pw_log_info( - "Local clock ID: IEEE1588-2008:%02X-%02X-%02X-%02X-%02X-%02X-%02X-%02X:%d", - cid[0], - cid[1], - cid[2], - cid[3], - cid[4], - cid[5], - cid[6], - cid[7], - 0 /* domain */ - ); + pw_log_info("Local clock ID: IEEE1588-2008:" + "%02X-%02X-%02X-%02X-%02X-%02X-%02X-%02X:%d", + cid[0], cid[1], cid[2], cid[3], + cid[4], cid[5], cid[6], cid[7], + 0 /* domain */); - uint8_t *gmid = parent.gm_clock_id; - bool gmid_changed = false; + gmid = parent.gm_clock_id; if (memcmp(gmid, gptp->gm_id, 8) != 0) { - pw_log_info( - "GM ID: IEEE1588-2008:%02X-%02X-%02X-%02X-%02X-%02X-%02X-%02X:%d", - gmid[0], - gmid[1], - gmid[2], - gmid[3], - gmid[4], - gmid[5], - gmid[6], - gmid[7], - 0 /* domain */ - ); - gmid_changed = true; + pw_log_info("GM ID: IEEE1588-2008:" + "%02X-%02X-%02X-%02X-%02X-%02X-%02X-%02X:%d", + gmid[0], gmid[1], gmid[2], gmid[3], + gmid[4], gmid[5], gmid[6], gmid[7], + 0 /* domain */); + gmid_changed = true; } - // When GM is not equal to own clock we are clocked by external master + /* When GM is not equal to own clock we are clocked by external master */ pw_log_debug("Synced to GM: %s", (memcmp(cid, gmid, 8) != 0) ? "true" : "false"); - struct timespec now; memcpy(gptp->clock_id, cid, 8); memcpy(gptp->gm_id, gmid, 8); @@ -270,7 +263,7 @@ struct avb_gptp *avb_gptp_new(struct server *server) str = pw_properties_get(impl->props, "ptp.management-socket"); gptp->ptp_mgmt_socket_path = str ? strdup(str) : NULL; - if(gptp->ptp_mgmt_socket_path) { + if (gptp->ptp_mgmt_socket_path) { ret = make_unix_ptp_mgmt_socket(gptp->ptp_mgmt_socket_path); if (ret == -1) goto error_free; From 7f687cae776df59a63f1cd0985bb42d9e890f9bd Mon Sep 17 00:00:00 2001 From: hackerman-kl Date: Tue, 28 Apr 2026 07:36:16 +0200 Subject: [PATCH 16/42] milan-avb: gptp: drop unused includes --- src/modules/module-avb/gptp.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/modules/module-avb/gptp.c b/src/modules/module-avb/gptp.c index 0a7fcb205..3eb1ffc4f 100644 --- a/src/modules/module-avb/gptp.c +++ b/src/modules/module-avb/gptp.c @@ -4,19 +4,16 @@ /* SPDX-License-Identifier: MIT */ #include "gptp.h" -#include "module-avb/aecp-aem.h" -#include "pipewire/properties.h" -#include "pipewire/protocol.h" -#include /* Definition of constants */ -#include #include #include #include +#include + #include +#include #include #include -#include #define server_emit(s,m,v,...) spa_hook_list_call(&s->listener_list, struct server_events, m, v, ##__VA_ARGS__) #define server_emit_gm_changed(s, n, g) server_emit(s, gm_changed, 0, n, g) From 51a997294e14a7b58d38bbb2ab8a7a1ccfcad63e Mon Sep 17 00:00:00 2001 From: hackerman-kl Date: Tue, 28 Apr 2026 07:37:10 +0200 Subject: [PATCH 17/42] milan-avb: gptp: drop unused struct fields --- src/modules/module-avb/gptp.c | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/modules/module-avb/gptp.c b/src/modules/module-avb/gptp.c index 3eb1ffc4f..cc791b504 100644 --- a/src/modules/module-avb/gptp.c +++ b/src/modules/module-avb/gptp.c @@ -21,15 +21,8 @@ struct gptp { struct server *server; - struct pw_loop *loop; - struct pw_timer_queue *timer_queue; - struct spa_hook server_listener; - struct spa_hook_list listener_list; - - struct spa_list attributes; - char *ptp_mgmt_socket_path; int ptp_fd; uint32_t ptp_seq; @@ -254,9 +247,6 @@ struct avb_gptp *avb_gptp_new(struct server *server) impl = server->impl; - gptp->loop = pw_context_get_main_loop(impl->context); - gptp->timer_queue = pw_context_get_timer_queue(impl->context); - str = pw_properties_get(impl->props, "ptp.management-socket"); gptp->ptp_mgmt_socket_path = str ? strdup(str) : NULL; @@ -271,9 +261,6 @@ struct avb_gptp *avb_gptp_new(struct server *server) goto error_free; } - spa_list_init(&gptp->attributes); - spa_hook_list_init(&gptp->listener_list); - avdecc_server_add_listener(server, &gptp->server_listener, &server_events, gptp); return (struct avb_gptp*)gptp; From d5e4f11be7b371418d018183220d950fc0131a05 Mon Sep 17 00:00:00 2001 From: hackerman-kl Date: Tue, 28 Apr 2026 07:38:11 +0200 Subject: [PATCH 18/42] milan-avb: gptp: fix log message for SO_PASSCRED setsockopt failure --- src/modules/module-avb/gptp.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/modules/module-avb/gptp.c b/src/modules/module-avb/gptp.c index cc791b504..c3ef954f8 100644 --- a/src/modules/module-avb/gptp.c +++ b/src/modules/module-avb/gptp.c @@ -36,13 +36,13 @@ static int make_unix_ptp_mgmt_socket(const char *path) spa_autoclose int fd = socket(AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, 0); if (fd < 0) { - pw_log_warn("Failed to create PTP management socket"); + pw_log_warn("Failed to create PTP management socket: %m"); return -1; } int val = 1; if (setsockopt(fd, SOL_SOCKET, SO_PASSCRED, &val, sizeof(val)) < 0) { - pw_log_warn("Failed to bind PTP management socket"); + pw_log_warn("Failed to set SO_PASSCRED on PTP management socket: %m"); return -1; } @@ -51,7 +51,7 @@ static int make_unix_ptp_mgmt_socket(const char *path) strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1); if (connect(fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) { - pw_log_warn("Failed to connect PTP management socket"); + pw_log_warn("Failed to connect PTP management socket: %m"); return -1; } From e83bbfacf12afa05f7778551fd14d53c1ded4ca5 Mon Sep 17 00:00:00 2001 From: hackerman-kl Date: Tue, 28 Apr 2026 07:38:35 +0200 Subject: [PATCH 19/42] milan-avb: gptp: drop %m from incomplete request/response warnings --- src/modules/module-avb/gptp.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/modules/module-avb/gptp.c b/src/modules/module-avb/gptp.c index c3ef954f8..85b1e85c5 100644 --- a/src/modules/module-avb/gptp.c +++ b/src/modules/module-avb/gptp.c @@ -124,7 +124,8 @@ static bool update_ts_refclk(struct gptp *gptp) return false; } if (ret != sizeof(req)) { - pw_log_warn("Incomplete PTP management request: %m"); + pw_log_warn("Incomplete PTP management request: %zd of %zu bytes", + ret, sizeof(req)); return false; } @@ -135,7 +136,8 @@ static bool update_ts_refclk(struct gptp *gptp) } if (ret != sizeof(buf)) { - pw_log_warn("Received incomplete PTP management response: %m"); + pw_log_warn("Received incomplete PTP management response: %zd of %zu bytes", + ret, sizeof(buf)); return false; } From 8c9e5f1974b30baedccd4c8026642276afd61fef Mon Sep 17 00:00:00 2001 From: hackerman-kl Date: Tue, 28 Apr 2026 07:38:51 +0200 Subject: [PATCH 20/42] milan-avb: gptp: use PTP_DEFAULT_LOG_MESSAGE_INTERVAL macro --- src/modules/module-avb/gptp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/module-avb/gptp.c b/src/modules/module-avb/gptp.c index 85b1e85c5..ba2f2f48b 100644 --- a/src/modules/module-avb/gptp.c +++ b/src/modules/module-avb/gptp.c @@ -100,7 +100,7 @@ static bool update_ts_refclk(struct gptp *gptp) req.message_length_be = htons(sizeof(struct ptp_management_msg)); spa_zero(req.clock_identity); req.source_port_id_be = htons(getpid()); - req.log_message_interval = 127; + req.log_message_interval = PTP_DEFAULT_LOG_MESSAGE_INTERVAL; req.sequence_id_be = htons(gptp->ptp_seq++); memset(req.target_port_identity, 0xff, 8); req.target_port_id_be = htons(0xffff); From f5389a4225ac7cfe6d673aa1f97ed8f6456b358f Mon Sep 17 00:00:00 2001 From: hackerman-kl Date: Tue, 28 Apr 2026 07:39:27 +0200 Subject: [PATCH 21/42] milan-avb: gptp: drop unused avb_gptp_destroy wrapper --- src/modules/module-avb/gptp.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/modules/module-avb/gptp.c b/src/modules/module-avb/gptp.c index ba2f2f48b..92c22b200 100644 --- a/src/modules/module-avb/gptp.c +++ b/src/modules/module-avb/gptp.c @@ -228,11 +228,6 @@ static const struct server_events server_events = { .periodic = gptp_periodic, }; -void avb_gptp_destroy(struct avb_gptp *gptp) -{ - gptp_destroy(gptp); -} - struct avb_gptp *avb_gptp_new(struct server *server) { struct impl *impl; From 1e9f89b638058884be367d342dad84ceab4b6c09 Mon Sep 17 00:00:00 2001 From: hackerman-kl Date: Tue, 28 Apr 2026 07:39:56 +0200 Subject: [PATCH 22/42] milan-avb: gptp: emit gm_changed only when the GM actually changes --- src/modules/module-avb/gptp.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/modules/module-avb/gptp.c b/src/modules/module-avb/gptp.c index 92c22b200..e3c5a9d03 100644 --- a/src/modules/module-avb/gptp.c +++ b/src/modules/module-avb/gptp.c @@ -198,8 +198,10 @@ static bool update_ts_refclk(struct gptp *gptp) memcpy(gptp->clock_id, cid, 8); memcpy(gptp->gm_id, gmid, 8); - clock_gettime(CLOCK_REALTIME, &now); - server_emit_gm_changed(gptp->server, SPA_TIMESPEC_TO_NSEC(&now), gmid); + if (gmid_changed) { + clock_gettime(CLOCK_REALTIME, &now); + server_emit_gm_changed(gptp->server, SPA_TIMESPEC_TO_NSEC(&now), gmid); + } return gmid_changed; } From 21c4c59587ddc48bf46b58c2f8ba7eac1f279a1e Mon Sep 17 00:00:00 2001 From: hackerman-kl Date: Tue, 28 Apr 2026 07:40:21 +0200 Subject: [PATCH 23/42] milan-avb: gptp: use entity_id for management source port id --- src/modules/module-avb/gptp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/module-avb/gptp.c b/src/modules/module-avb/gptp.c index e3c5a9d03..fac694009 100644 --- a/src/modules/module-avb/gptp.c +++ b/src/modules/module-avb/gptp.c @@ -99,7 +99,7 @@ static bool update_ts_refclk(struct gptp *gptp) req.ver = PTP_VERSION_1588_2008_2_1; req.message_length_be = htons(sizeof(struct ptp_management_msg)); spa_zero(req.clock_identity); - req.source_port_id_be = htons(getpid()); + req.source_port_id_be = htons((uint16_t)gptp->server->entity_id); req.log_message_interval = PTP_DEFAULT_LOG_MESSAGE_INTERVAL; req.sequence_id_be = htons(gptp->ptp_seq++); memset(req.target_port_identity, 0xff, 8); From 0b09fb2b1eb48cfd9b9aab0afe37bcf6a4d4dbb4 Mon Sep 17 00:00:00 2001 From: hackerman-kl Date: Tue, 28 Apr 2026 07:40:44 +0200 Subject: [PATCH 24/42] milan-avb: gptp: initialise ret in the drain loop --- src/modules/module-avb/gptp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/module-avb/gptp.c b/src/modules/module-avb/gptp.c index fac694009..56a14bfc3 100644 --- a/src/modules/module-avb/gptp.c +++ b/src/modules/module-avb/gptp.c @@ -68,7 +68,7 @@ static bool update_ts_refclk(struct gptp *gptp) uint8_t *cid; uint8_t *gmid; uint8_t tmp; - ssize_t ret; + ssize_t ret = 0; uint16_t data_len; int avail; bool gmid_changed = false; From 0da747fd44846a71484e533ea148cd8d1c3510a0 Mon Sep 17 00:00:00 2001 From: hackerman-kl Date: Tue, 28 Apr 2026 19:17:39 +0200 Subject: [PATCH 25/42] milan-avb: gptp: do not fail server creation on missing PTP socket --- src/modules/module-avb/gptp.c | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/modules/module-avb/gptp.c b/src/modules/module-avb/gptp.c index 56a14bfc3..bf645d2ee 100644 --- a/src/modules/module-avb/gptp.c +++ b/src/modules/module-avb/gptp.c @@ -252,20 +252,18 @@ struct avb_gptp *avb_gptp_new(struct server *server) if (gptp->ptp_mgmt_socket_path) { ret = make_unix_ptp_mgmt_socket(gptp->ptp_mgmt_socket_path); if (ret == -1) - goto error_free; + pw_log_warn("server %p: PTP management socket unavailable, " + "continuing without GM tracking; will retry on '%s'", + impl, gptp->ptp_mgmt_socket_path); else gptp->ptp_fd = ret; } else { - pw_log_error("server %p: ptp.management-socket not set", impl); - goto error_free; + pw_log_warn("server %p: ptp.management-socket not set, " + "continuing without GM tracking", impl); } avdecc_server_add_listener(server, &gptp->server_listener, &server_events, gptp); return (struct avb_gptp*)gptp; - -error_free: - gptp_destroy(gptp); - return NULL; } From 3f63b51fcc0f3157d2b994d0d6b6551f435584c1 Mon Sep 17 00:00:00 2001 From: hackerman-kl Date: Tue, 28 Apr 2026 21:43:06 +0200 Subject: [PATCH 26/42] milan-avb: gptp: rework management I/O as non-blocking with sequence-id matching --- src/modules/module-avb/gptp.c | 523 ++++++++++++++++++++++++++-------- src/modules/module-avb/gptp.h | 37 +++ 2 files changed, 447 insertions(+), 113 deletions(-) diff --git a/src/modules/module-avb/gptp.c b/src/modules/module-avb/gptp.c index bf645d2ee..4326165ae 100644 --- a/src/modules/module-avb/gptp.c +++ b/src/modules/module-avb/gptp.c @@ -5,184 +5,277 @@ #include "gptp.h" -#include +#include +#include +#include +#include #include #include #include -#include -#include +#include #include #include +#include "aecp-aem-descriptors.h" + #define server_emit(s,m,v,...) spa_hook_list_call(&s->listener_list, struct server_events, m, v, ##__VA_ARGS__) #define server_emit_gm_changed(s, n, g) server_emit(s, gm_changed, 0, n, g) +#define PTP_REQUEST_TIMEOUT_NS (SPA_NSEC_PER_SEC / 2) + struct gptp { struct server *server; struct spa_hook server_listener; + struct spa_source *source; char *ptp_mgmt_socket_path; int ptp_fd; - uint32_t ptp_seq; + uint16_t ptp_seq; uint8_t clock_id[8]; uint8_t gm_id[8]; + + bool req_in_flight; + uint16_t req_sequence_id; + uint16_t req_management_id; + uint64_t req_sent_ns; + + uint32_t tick_count; + bool data_valid; }; -static int make_unix_ptp_mgmt_socket(const char *path) +static int make_bind_path(char *out, size_t out_size, uint64_t entity_id) +{ + int len = snprintf(out, out_size, + "/tmp/pipewire-avb-gptp-%016" PRIx64, entity_id); + if (len < 0 || (size_t)len >= out_size) { + return -1; + } + return 0; +} + +static int make_unix_ptp_mgmt_socket(const char *path, uint64_t entity_id) { struct sockaddr_un addr; + char bind_path[64]; + int val = 1; - spa_autoclose int fd = socket(AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, 0); + if (make_bind_path(bind_path, sizeof(bind_path), entity_id) < 0) { + pw_log_warn("Failed to format PTP management bind path"); + return -1; + } + + spa_autoclose int fd = socket(AF_UNIX, + SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0); if (fd < 0) { pw_log_warn("Failed to create PTP management socket: %m"); return -1; } - int val = 1; if (setsockopt(fd, SOL_SOCKET, SO_PASSCRED, &val, sizeof(val)) < 0) { pw_log_warn("Failed to set SO_PASSCRED on PTP management socket: %m"); return -1; } + unlink(bind_path); + spa_zero(addr); + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, bind_path, sizeof(addr.sun_path) - 1); + if (bind(fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) { + pw_log_warn("Failed to bind PTP management socket to '%s': %m", + bind_path); + return -1; + } + pw_log_info("PTP management socket bound to '%s'", bind_path); + spa_zero(addr); addr.sun_family = AF_UNIX; strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1); - if (connect(fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) { pw_log_warn("Failed to connect PTP management socket: %m"); + unlink(bind_path); return -1; } + pw_log_info("PTP management socket connected to '%s'", path); return spa_steal_fd(fd); } -static bool update_ts_refclk(struct gptp *gptp) +static void on_ptp_mgmt_data(void *data, int fd, uint32_t mask); + +static void gptp_close_socket(struct gptp *gptp) +{ + char bind_path[64]; + + if (gptp->source) { + pw_loop_destroy_source(gptp->server->impl->loop, gptp->source); + gptp->source = NULL; + } + if (gptp->ptp_fd >= 0) { + close(gptp->ptp_fd); + gptp->ptp_fd = -1; + if (make_bind_path(bind_path, sizeof(bind_path), + gptp->server->entity_id) == 0) { + unlink(bind_path); + } + } + gptp->req_in_flight = false; +} + +static int gptp_open_socket(struct gptp *gptp) +{ + struct impl *impl = gptp->server->impl; + int fd; + + fd = make_unix_ptp_mgmt_socket(gptp->ptp_mgmt_socket_path, + gptp->server->entity_id); + if (fd < 0) { + return -1; + } + + gptp->source = pw_loop_add_io(impl->loop, fd, SPA_IO_IN, false, + on_ptp_mgmt_data, gptp); + if (gptp->source == NULL) { + pw_log_warn("Failed to add PTP management IO source: %m"); + close(fd); + return -1; + } + gptp->ptp_fd = fd; + return 0; +} + +static struct avb_aem_desc_avb_interface *get_avb_interface(struct gptp *gptp) +{ + struct descriptor *d = server_find_descriptor(gptp->server, + AVB_AEM_DESC_AVB_INTERFACE, 0); + return d ? descriptor_body(d) : NULL; +} + +static void update_avb_interface_clock_identity(struct gptp *gptp, + const uint8_t cid[8]) +{ + struct avb_aem_desc_avb_interface *iface = get_avb_interface(gptp); + if (iface == NULL) { + return; + } + memcpy(&iface->clock_identity, cid, sizeof(iface->clock_identity)); +} + +static void update_avb_interface_default(struct gptp *gptp, + const struct ptp_default_data_set *dds) +{ + struct avb_aem_desc_avb_interface *iface = get_avb_interface(gptp); + if (iface == NULL) { + return; + } + memcpy(&iface->clock_identity, dds->clock_identity, + sizeof(iface->clock_identity)); + iface->priority1 = dds->priority1; + iface->clock_class = dds->clock_class; + iface->clock_accuracy = dds->clock_accuracy; + iface->offset_scaled_log_variance = dds->offset_scaled_log_variance_be; + iface->priority2 = dds->priority2; + iface->domain_number = dds->domain_number; +} + +static void update_avb_interface_port(struct gptp *gptp, + const struct ptp_port_data_set *pds) +{ + struct avb_aem_desc_avb_interface *iface = get_avb_interface(gptp); + if (iface == NULL) { + return; + } + iface->log_sync_interval = pds->log_sync_interval; + iface->log_announce_interval = pds->log_announce_interval; + iface->log_pdelay_interval = pds->log_min_pdelay_req_interval; + iface->port_number = pds->port_number_be; +} + +static int send_management_request(struct gptp *gptp, uint16_t management_id, + uint64_t now_ns) { struct ptp_management_msg req; - struct ptp_management_msg res; - struct ptp_parent_data_set parent; - struct timespec now; - uint8_t buf[sizeof(struct ptp_management_msg) + sizeof(struct ptp_parent_data_set)]; - uint8_t *cid; - uint8_t *gmid; - uint8_t tmp; - ssize_t ret = 0; - uint16_t data_len; - int avail; - bool gmid_changed = false; - - if (!gptp->ptp_mgmt_socket_path) - return false; - if (gptp->ptp_fd < 0) { - gptp->ptp_fd = make_unix_ptp_mgmt_socket(gptp->ptp_mgmt_socket_path); - if (gptp->ptp_fd < 0) - return false; - } - - /* Read if something is left in the socket */ - if (ioctl(gptp->ptp_fd, FIONREAD, &avail) == -1) { - pw_log_warn("Failed to get number of byes in ptp_fd input buffer: %m"); - return false; - } - pw_log_debug("Clearing stale data: %u bytes", avail); - while (avail-- && (ret = read(gptp->ptp_fd, &tmp, 1)) > 0); - if (ret == -1) { - pw_log_warn("Failed to clear ptp_fd input buffer: %m"); - return false; - } + ssize_t ret; + uint16_t seq; spa_zero(req); + seq = gptp->ptp_seq++; req.major_sdo_id_message_type = PTP_MESSAGE_TYPE_MANAGEMENT; req.ver = PTP_VERSION_1588_2008_2_1; req.message_length_be = htons(sizeof(struct ptp_management_msg)); spa_zero(req.clock_identity); req.source_port_id_be = htons((uint16_t)gptp->server->entity_id); req.log_message_interval = PTP_DEFAULT_LOG_MESSAGE_INTERVAL; - req.sequence_id_be = htons(gptp->ptp_seq++); + req.sequence_id_be = htons(seq); memset(req.target_port_identity, 0xff, 8); req.target_port_id_be = htons(0xffff); req.starting_boundary_hops = 1; req.boundary_hops = 1; req.action = PTP_MGMT_ACTION_GET; req.tlv_type_be = htons(PTP_TLV_TYPE_MGMT); - /* sent empty TLV, only sending management_id */ req.management_message_length_be = htons(2); - req.management_id_be = htons(PTP_MGMT_ID_PARENT_DATA_SET); + req.management_id_be = htons(management_id); ret = write(gptp->ptp_fd, &req, sizeof(req)); if (ret == -1) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + pw_log_debug("PTP management write would block, skipping tick"); + return -EAGAIN; + } pw_log_warn("Failed to send PTP management request: %m"); - if (errno != ENOTCONN) - return false; - close(gptp->ptp_fd); - gptp->ptp_fd = make_unix_ptp_mgmt_socket(gptp->ptp_mgmt_socket_path); - if (gptp->ptp_fd > -1) - pw_log_info("Reopened PTP management socket"); - return false; + return -errno; } - if (ret != sizeof(req)) { + if (ret != (ssize_t)sizeof(req)) { pw_log_warn("Incomplete PTP management request: %zd of %zu bytes", ret, sizeof(req)); - return false; + return -EIO; } - ret = read(gptp->ptp_fd, &buf, sizeof(buf)); - if (ret == -1) { - pw_log_warn("Failed to receive PTP management response: %m"); - return false; + gptp->req_in_flight = true; + gptp->req_sequence_id = seq; + gptp->req_management_id = management_id; + gptp->req_sent_ns = now_ns; + pw_log_info("PTP management request sent: id=%04x seq=%u", + management_id, seq); + return 0; +} + +static void handle_parent_data_set(struct gptp *gptp, + const struct ptp_management_msg *res, + const uint8_t *payload, size_t payload_len) +{ + const struct ptp_parent_data_set *parent; + struct timespec ts; + uint16_t data_len; + const uint8_t *cid, *gmid; + bool gmid_changed = false; + bool cid_changed = false; + + data_len = ntohs(res->management_message_length_be) - 2; + if (data_len != sizeof(struct ptp_parent_data_set) || + payload_len < sizeof(struct ptp_parent_data_set)) { + pw_log_warn("Unexpected PTP GET PARENT_DATA_SET response length: " + "tlv=%u payload=%zu expected=%zu", + data_len, payload_len, + sizeof(struct ptp_parent_data_set)); + return; } - if (ret != sizeof(buf)) { - pw_log_warn("Received incomplete PTP management response: %zd of %zu bytes", - ret, sizeof(buf)); - return false; - } + parent = (const struct ptp_parent_data_set *)payload; - res = *(struct ptp_management_msg *)buf; - parent = *(struct ptp_parent_data_set *)(buf + sizeof(struct ptp_management_msg)); - - if ((res.ver & 0x0f) != 2) { - pw_log_warn("PTP major version is %d, expected 2", res.ver); - return false; - } - - if ((res.major_sdo_id_message_type & 0x0f) != PTP_MESSAGE_TYPE_MANAGEMENT) { - pw_log_warn("PTP management returned type %x, expected management", res.major_sdo_id_message_type); - return false; - } - - if (res.action != PTP_MGMT_ACTION_RESPONSE) { - pw_log_warn("PTP management returned action %d, expected response", res.action); - return false; - } - - if (ntohs(res.tlv_type_be) != PTP_TLV_TYPE_MGMT) { - pw_log_warn("PTP management returned tlv type %d, expected management", ntohs(res.tlv_type_be)); - return false; - } - - if (ntohs(res.management_id_be) != PTP_MGMT_ID_PARENT_DATA_SET) { - pw_log_warn("PTP management returned ID %d, expected PARENT_DATA_SET", ntohs(res.management_id_be)); - return false; - } - - data_len = ntohs(res.management_message_length_be) - 2; - if (data_len != sizeof(struct ptp_parent_data_set)) - pw_log_warn("Unexpected PTP GET PARENT_DATA_SET response length %u, expected %zu", - data_len, sizeof(struct ptp_parent_data_set)); - - cid = res.clock_identity; - if (memcmp(cid, gptp->clock_id, 8) != 0) + cid = res->clock_identity; + if (memcmp(cid, gptp->clock_id, 8) != 0) { pw_log_info("Local clock ID: IEEE1588-2008:" "%02X-%02X-%02X-%02X-%02X-%02X-%02X-%02X:%d", cid[0], cid[1], cid[2], cid[3], cid[4], cid[5], cid[6], cid[7], 0 /* domain */); + cid_changed = true; + } - gmid = parent.gm_clock_id; + gmid = parent->gm_clock_id; if (memcmp(gmid, gptp->gm_id, 8) != 0) { pw_log_info("GM ID: IEEE1588-2008:" "%02X-%02X-%02X-%02X-%02X-%02X-%02X-%02X:%d", @@ -192,24 +285,202 @@ static bool update_ts_refclk(struct gptp *gptp) gmid_changed = true; } - /* When GM is not equal to own clock we are clocked by external master */ - pw_log_debug("Synced to GM: %s", (memcmp(cid, gmid, 8) != 0) ? "true" : "false"); + pw_log_debug("Synced to GM: %s", + (memcmp(cid, gmid, 8) != 0) ? "true" : "false"); memcpy(gptp->clock_id, cid, 8); memcpy(gptp->gm_id, gmid, 8); + gptp->data_valid = true; - if (gmid_changed) { - clock_gettime(CLOCK_REALTIME, &now); - server_emit_gm_changed(gptp->server, SPA_TIMESPEC_TO_NSEC(&now), gmid); + /* IEEE 1722.1-2021 Section 7.2.8: AVB_INTERFACE.clock_identity is + * the local gPTP clock. */ + if (cid_changed) { + update_avb_interface_clock_identity(gptp, cid); } - return gmid_changed; + if (gmid_changed) { + clock_gettime(CLOCK_REALTIME, &ts); + server_emit_gm_changed(gptp->server, + SPA_TIMESPEC_TO_NSEC(&ts), (uint8_t *)gmid); + } +} + +static void handle_default_data_set(struct gptp *gptp, + const struct ptp_management_msg *res, + const uint8_t *payload, size_t payload_len) +{ + const struct ptp_default_data_set *dds; + uint16_t data_len; + + data_len = ntohs(res->management_message_length_be) - 2; + if (data_len != sizeof(struct ptp_default_data_set) || + payload_len < sizeof(struct ptp_default_data_set)) { + pw_log_warn("Unexpected PTP GET DEFAULT_DATA_SET response length: " + "tlv=%u payload=%zu expected=%zu", + data_len, payload_len, + sizeof(struct ptp_default_data_set)); + return; + } + dds = (const struct ptp_default_data_set *)payload; + update_avb_interface_default(gptp, dds); +} + +static void handle_port_data_set(struct gptp *gptp, + const struct ptp_management_msg *res, + const uint8_t *payload, size_t payload_len) +{ + const struct ptp_port_data_set *pds; + uint16_t data_len; + + data_len = ntohs(res->management_message_length_be) - 2; + if (data_len != sizeof(struct ptp_port_data_set) || + payload_len < sizeof(struct ptp_port_data_set)) { + pw_log_warn("Unexpected PTP GET PORT_DATA_SET response length: " + "tlv=%u payload=%zu expected=%zu", + data_len, payload_len, + sizeof(struct ptp_port_data_set)); + return; + } + pds = (const struct ptp_port_data_set *)payload; + update_avb_interface_port(gptp, pds); +} + +static void on_ptp_mgmt_data(void *data, int fd, uint32_t mask) +{ + struct gptp *gptp = data; + uint8_t buf[sizeof(struct ptp_management_msg) + 256]; + struct ptp_management_msg res; + ssize_t ret; + uint16_t seq; + uint16_t mgmt_id; + + if (!(mask & SPA_IO_IN)) { + return; + } + + pw_log_info("PTP management socket has data (mask=%#x)", mask); + + for (;;) { + ret = recv(fd, buf, sizeof(buf), 0); + if (ret == -1) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + return; + } + pw_log_warn("Failed to receive PTP management response: %m"); + return; + } + pw_log_info("PTP management received %zd bytes", ret); + if (ret < (ssize_t)sizeof(struct ptp_management_msg)) { + pw_log_warn("Received undersized PTP management response: %zd bytes", + ret); + continue; + } + + memcpy(&res, buf, sizeof(res)); + + if ((res.ver & 0x0f) != 2) { + pw_log_warn("PTP major version is %d, expected 2", res.ver); + continue; + } + + if ((res.major_sdo_id_message_type & 0x0f) != PTP_MESSAGE_TYPE_MANAGEMENT) { + pw_log_warn("PTP management returned type %x, expected management", + res.major_sdo_id_message_type); + continue; + } + + if (res.action != PTP_MGMT_ACTION_RESPONSE) { + pw_log_debug("PTP management returned action %d, expected response", + res.action); + continue; + } + + seq = ntohs(res.sequence_id_be); + mgmt_id = ntohs(res.management_id_be); + + if (!gptp->req_in_flight || seq != gptp->req_sequence_id) { + pw_log_debug("Ignoring unsolicited PTP response (seq=%u, id=%04x)", + seq, mgmt_id); + continue; + } + + if (ntohs(res.tlv_type_be) != PTP_TLV_TYPE_MGMT) { + pw_log_warn("PTP management returned tlv type %d, expected management", + ntohs(res.tlv_type_be)); + gptp->req_in_flight = false; + continue; + } + + if (mgmt_id != gptp->req_management_id) { + pw_log_warn("PTP management returned ID %04x, expected %04x", + mgmt_id, gptp->req_management_id); + gptp->req_in_flight = false; + continue; + } + + switch (mgmt_id) { + case PTP_MGMT_ID_PARENT_DATA_SET: + handle_parent_data_set(gptp, &res, + buf + sizeof(struct ptp_management_msg), + (size_t)ret - sizeof(struct ptp_management_msg)); + break; + case PTP_MGMT_ID_DEFAULT_DATA_SET: + handle_default_data_set(gptp, &res, + buf + sizeof(struct ptp_management_msg), + (size_t)ret - sizeof(struct ptp_management_msg)); + break; + case PTP_MGMT_ID_PORT_DATA_SET: + handle_port_data_set(gptp, &res, + buf + sizeof(struct ptp_management_msg), + (size_t)ret - sizeof(struct ptp_management_msg)); + break; + default: + pw_log_debug("Unhandled PTP management ID: %04x", mgmt_id); + break; + } + gptp->req_in_flight = false; + } +} + +static uint16_t next_management_id(uint32_t tick_count) +{ + switch (tick_count % 10) { + case 0: return PTP_MGMT_ID_DEFAULT_DATA_SET; + case 5: return PTP_MGMT_ID_PORT_DATA_SET; + default: return PTP_MGMT_ID_PARENT_DATA_SET; + } } static void gptp_periodic(void *data, uint64_t now) { struct gptp *gptp = data; - update_ts_refclk(gptp); + int err; + + if (!gptp->ptp_mgmt_socket_path) { + return; + } + + if (gptp->ptp_fd < 0 && gptp_open_socket(gptp) < 0) { + return; + } + + if (gptp->req_in_flight && (now - gptp->req_sent_ns) > PTP_REQUEST_TIMEOUT_NS) { + pw_log_debug("PTP management request seq=%u timed out", + gptp->req_sequence_id); + gptp->req_in_flight = false; + } + + if (gptp->req_in_flight) { + return; + } + + err = send_management_request(gptp, next_management_id(gptp->tick_count), now); + if (err == 0) { + gptp->tick_count++; + } else if (err == -ENOTCONN) { + pw_log_info("PTP management socket disconnected, will reopen"); + gptp_close_socket(gptp); + } } static void gptp_destroy(void *data) @@ -217,8 +488,7 @@ static void gptp_destroy(void *data) struct gptp *gptp = data; spa_hook_remove(&gptp->server_listener); - if (gptp->ptp_fd != -1) - close(gptp->ptp_fd); + gptp_close_socket(gptp); free(gptp->ptp_mgmt_socket_path); free(gptp); @@ -235,11 +505,11 @@ struct avb_gptp *avb_gptp_new(struct server *server) struct impl *impl; struct gptp *gptp; const char *str; - int ret; gptp = calloc(1, sizeof(*gptp)); - if (gptp == NULL) + if (gptp == NULL) { return NULL; + } gptp->server = server; gptp->ptp_fd = -1; @@ -250,20 +520,47 @@ struct avb_gptp *avb_gptp_new(struct server *server) gptp->ptp_mgmt_socket_path = str ? strdup(str) : NULL; if (gptp->ptp_mgmt_socket_path) { - ret = make_unix_ptp_mgmt_socket(gptp->ptp_mgmt_socket_path); - if (ret == -1) + if (gptp_open_socket(gptp) < 0) { pw_log_warn("server %p: PTP management socket unavailable, " "continuing without GM tracking; will retry on '%s'", impl, gptp->ptp_mgmt_socket_path); - else - gptp->ptp_fd = ret; + } } else { pw_log_warn("server %p: ptp.management-socket not set, " "continuing without GM tracking", impl); } - avdecc_server_add_listener(server, &gptp->server_listener, &server_events, gptp); + avdecc_server_add_listener(server, &gptp->server_listener, + &server_events, gptp); return (struct avb_gptp*)gptp; } +bool avb_gptp_get_clock_id(const struct avb_gptp *agptp, uint64_t *clock_id_be) +{ + const struct gptp *gptp = (const struct gptp *)agptp; + if (gptp == NULL || !gptp->data_valid) { + return false; + } + memcpy(clock_id_be, gptp->clock_id, sizeof(*clock_id_be)); + return true; +} + +bool avb_gptp_get_grandmaster_id(const struct avb_gptp *agptp, uint64_t *gm_id_be) +{ + const struct gptp *gptp = (const struct gptp *)agptp; + if (gptp == NULL || !gptp->data_valid) { + return false; + } + memcpy(gm_id_be, gptp->gm_id, sizeof(*gm_id_be)); + return true; +} + +bool avb_gptp_is_grandmaster(const struct avb_gptp *agptp) +{ + const struct gptp *gptp = (const struct gptp *)agptp; + if (gptp == NULL || !gptp->data_valid) { + return false; + } + return memcmp(gptp->clock_id, gptp->gm_id, 8) == 0; +} diff --git a/src/modules/module-avb/gptp.h b/src/modules/module-avb/gptp.h index d520ad5ce..7cacde7ae 100644 --- a/src/modules/module-avb/gptp.h +++ b/src/modules/module-avb/gptp.h @@ -5,6 +5,7 @@ #ifndef AVB_GPTP_H #define AVB_GPTP_H +#include #include #include "internal.h" @@ -18,7 +19,9 @@ extern "C" { #define PTP_MGMT_ACTION_GET 0 #define PTP_MGMT_ACTION_RESPONSE 2 #define PTP_TLV_TYPE_MGMT 0x0001 +#define PTP_MGMT_ID_DEFAULT_DATA_SET 0x2000 #define PTP_MGMT_ID_PARENT_DATA_SET 0x2002 +#define PTP_MGMT_ID_PORT_DATA_SET 0x2004 /**************************************************************************************/ /* IEEE 1588-2019, Sec. 15.4.1 PTP management message format - Common Fields */ @@ -68,8 +71,42 @@ struct ptp_parent_data_set { uint8_t gm_clock_id[8]; } __attribute__((packed)); +/* IEEE 1588-2008 Section 15.5.1.1 defaultDS */ +struct ptp_default_data_set { + uint8_t flags; + uint8_t reserved1; + uint16_t number_ports_be; + uint8_t priority1; + uint8_t clock_class; + uint8_t clock_accuracy; + uint16_t offset_scaled_log_variance_be; + uint8_t priority2; + uint8_t clock_identity[8]; + uint8_t domain_number; + uint8_t reserved2; +} __attribute__((packed)); + +/* IEEE 1588-2008 Section 15.5.1.4 portDS */ +struct ptp_port_data_set { + uint8_t port_clock_identity[8]; + uint16_t port_number_be; + uint8_t port_state; + int8_t log_min_delay_req_interval; + uint8_t peer_mean_path_delay[8]; + int8_t log_announce_interval; + uint8_t announce_receipt_timeout; + int8_t log_sync_interval; + uint8_t delay_mechanism; + int8_t log_min_pdelay_req_interval; + uint8_t version_number; +} __attribute__((packed)); + struct avb_gptp *avb_gptp_new(struct server *server); +bool avb_gptp_get_clock_id(const struct avb_gptp *gptp, uint64_t *clock_id_be); +bool avb_gptp_get_grandmaster_id(const struct avb_gptp *gptp, uint64_t *gm_id_be); +bool avb_gptp_is_grandmaster(const struct avb_gptp *gptp); + #ifdef __cplusplus } #endif From 231b0950c587f1712a28856272208c9d6de836cd Mon Sep 17 00:00:00 2001 From: hackerman-kl Date: Tue, 28 Apr 2026 21:43:10 +0200 Subject: [PATCH 27/42] milan-avb: descriptors: derive AVB_INTERFACE clock_identity from entity_id --- src/modules/module-avb/descriptors.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/module-avb/descriptors.c b/src/modules/module-avb/descriptors.c index f983cb191..b496d713d 100644 --- a/src/modules/module-avb/descriptors.c +++ b/src/modules/module-avb/descriptors.c @@ -748,7 +748,7 @@ static void init_descriptor_milan_v12(struct server *server) struct avb_aem_desc_avb_interface avb_interface = { .localized_description = htons(DSC_AVB_INTERFACE_LOCALIZED_DESCRIPTION), .interface_flags = htons(DSC_AVB_INTERFACE_INTERFACE_FLAGS), - .clock_identity = htobe64(DSC_AVB_INTERFACE_CLOCK_IDENTITY), + .clock_identity = htobe64(server->entity_id), .priority1 = DSC_AVB_INTERFACE_PRIORITY1, .clock_class = DSC_AVB_INTERFACE_CLOCK_CLASS, .offset_scaled_log_variance = htons(DSC_AVB_INTERFACE_OFFSET_SCALED_LOG_VARIANCE), From e9a1e509963a87a29c3b8c8d83c78d3e09772ffe Mon Sep 17 00:00:00 2001 From: hackerman-kl Date: Tue, 28 Apr 2026 21:43:14 +0200 Subject: [PATCH 28/42] milan-avb: adp: refresh grandmaster_id from gptp on each advertise --- src/modules/module-avb/adp.c | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/src/modules/module-avb/adp.c b/src/modules/module-avb/adp.c index 6cc0cfbad..1126ec7b4 100644 --- a/src/modules/module-avb/adp.c +++ b/src/modules/module-avb/adp.c @@ -9,6 +9,7 @@ #include "adp.h" #include "acmp.h" #include "aecp-aem-descriptors.h" +#include "gptp.h" #include "internal.h" #include "utils.h" @@ -46,11 +47,32 @@ static void entity_free(struct entity *e) free(e); } +static void refresh_gptp_fields(struct server *server, struct avb_packet_adp *p) +{ + const struct descriptor *d; + struct avb_aem_desc_avb_interface *avb_interface; + uint64_t gm_id_be; + + d = server_find_descriptor(server, AVB_AEM_DESC_AVB_INTERFACE, 0); + avb_interface = d ? descriptor_body(d) : NULL; + if (avb_interface == NULL) { + return; + } + + if (avb_gptp_get_grandmaster_id(server->gptp, &gm_id_be)) { + p->gptp_grandmaster_id = gm_id_be; + } else { + p->gptp_grandmaster_id = avb_interface->clock_identity; + } + p->gptp_domain_number = avb_interface->domain_number; +} + static int send_departing(struct adp *adp, uint64_t now, struct entity *e) { struct avb_ethernet_header *h = (void*)e->buf; struct avb_packet_adp *p = SPA_PTROFF(h, sizeof(*h), void); + refresh_gptp_fields(adp->server, p); AVB_PACKET_ADP_SET_MESSAGE_TYPE(p, AVB_ADP_MESSAGE_TYPE_ENTITY_DEPARTING); p->available_index = htonl(adp->available_index++); avb_server_send_packet(adp->server, mac, AVB_TSN_ETH, e->buf, e->len); @@ -63,6 +85,7 @@ static int send_advertise(struct adp *adp, uint64_t now, struct entity *e) struct avb_ethernet_header *h = (void*)e->buf; struct avb_packet_adp *p = SPA_PTROFF(h, sizeof(*h), void); + refresh_gptp_fields(adp->server, p); AVB_PACKET_ADP_SET_MESSAGE_TYPE(p, AVB_ADP_MESSAGE_TYPE_ENTITY_AVAILABLE); p->available_index = htonl(adp->available_index++); avb_server_send_packet(adp->server, mac, AVB_TSN_ETH, e->buf, e->len); @@ -269,7 +292,6 @@ static int check_advertise(struct adp *adp, uint64_t now) struct server *server = adp->server; const struct descriptor *d; struct avb_aem_desc_entity *entity; - struct avb_aem_desc_avb_interface *avb_interface; struct entity *e; uint64_t entity_id; struct avb_ethernet_header *h; @@ -289,9 +311,6 @@ static int check_advertise(struct adp *adp, uint64_t now) return 0; } - d = server_find_descriptor(server, AVB_AEM_DESC_AVB_INTERFACE, 0); - avb_interface = d ? descriptor_body(d) : NULL; - pw_log_info("entity %s advertise", avb_utils_format_id(buf, sizeof(buf), entity_id)); @@ -321,10 +340,6 @@ static int check_advertise(struct adp *adp, uint64_t now) p->listener_capabilities = entity->listener_capabilities; p->controller_capabilities = entity->controller_capabilities; p->available_index = entity->available_index; - if (avb_interface) { - p->gptp_grandmaster_id = avb_interface->clock_identity; - p->gptp_domain_number = avb_interface->domain_number; - } p->identify_control_index = 0; p->interface_index = 0; p->association_id = entity->association_id; From 9f019a061ddce565512b2feab81352f550790b5f Mon Sep 17 00:00:00 2001 From: hackerman-kl Date: Tue, 28 Apr 2026 21:43:18 +0200 Subject: [PATCH 29/42] milan-avb: aecp-aem: source GET_AVB_INFO grandmaster from gptp --- src/modules/module-avb/aecp-aem.c | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/modules/module-avb/aecp-aem.c b/src/modules/module-avb/aecp-aem.c index d3b204c76..f244c5440 100644 --- a/src/modules/module-avb/aecp-aem.c +++ b/src/modules/module-avb/aecp-aem.c @@ -7,6 +7,7 @@ #include "aecp-aem-descriptors.h" #include "aecp-aem-state.h" #include "aecp-aem-cmds-resps/cmd-resp-helpers.h" +#include "gptp.h" #include "utils.h" /* The headers including the command and response of the system */ @@ -144,6 +145,8 @@ static int handle_get_avb_info_common(struct aecp *aecp, int64_t now, const struct descriptor *desc; uint8_t buf[2048]; size_t size, psize; + uint64_t gm_id_be; + uint8_t flags; i = (struct avb_packet_aecp_aem_get_avb_info*)p->payload; @@ -174,13 +177,17 @@ static int handle_get_avb_info_common(struct aecp *aecp, int64_t now, 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; + flags = AVB_AEM_AVB_INFO_FLAG_SRP_ENABLED; + /* IEEE 1722.1-2021 Section 7.4.40 GET_AVB_INFO. */ + if (avb_gptp_get_grandmaster_id(server->gptp, &gm_id_be)) { + i->gptp_grandmaster_id = gm_id_be; + flags |= AVB_AEM_AVB_INFO_FLAG_GPTP_ENABLED; + } else { + i->gptp_grandmaster_id = avb_interface->clock_identity; + } + i->flags = flags; i->propagation_delay = htonl(0); i->gptp_domain_number = avb_interface->domain_number; - /* IEEE 1722.1-2021 Section 7.4.40: GPTP_ENABLED / GPTP_GRANDMASTER_SUPPORTED - * stay 0 until the gPTP interface lands. SRP_ENABLED is on because - * MSRP is running on every AVB interface this module manages. */ - i->flags = AVB_AEM_AVB_INFO_FLAG_SRP_ENABLED; i->msrp_mappings_count = htons(0); return avb_server_send_packet(server, h->src, AVB_TSN_ETH, buf, size); From 46f9c5130e05d05d6579f3087b1edc808371e108 Mon Sep 17 00:00:00 2001 From: hackerman-kl Date: Tue, 28 Apr 2026 21:43:22 +0200 Subject: [PATCH 30/42] milan-avb: cmd-get-as-path: build path from gptp data --- .../aecp-aem-cmds-resps/cmd-get-as-path.c | 54 +++++++++++++++---- 1 file changed, 43 insertions(+), 11 deletions(-) diff --git a/src/modules/module-avb/aecp-aem-cmds-resps/cmd-get-as-path.c b/src/modules/module-avb/aecp-aem-cmds-resps/cmd-get-as-path.c index 0bf21ea63..42643a194 100644 --- a/src/modules/module-avb/aecp-aem-cmds-resps/cmd-get-as-path.c +++ b/src/modules/module-avb/aecp-aem-cmds-resps/cmd-get-as-path.c @@ -5,37 +5,59 @@ #include "../aecp.h" #include "../aecp-aem.h" +#include "../gptp.h" #include "../internal.h" #include "cmd-get-as-path.h" #include "cmd-resp-helpers.h" -/* IEEE 1722.1-2021 Section 7.4.41 GET_AS_PATH placeholder: single-entry path - * pointing at the entity itself, until gPTP is wired up. */ +/* IEEE 1722.1-2021 Section 7.4.41 GET_AS_PATH. */ int handle_cmd_get_as_path_milan_v12(struct aecp *aecp, int64_t now, const void *m, int len) { + struct server *server = aecp->server; + uint64_t gm_id_be = 0; + uint64_t clock_id_be = 0; + bool have_gm; + bool have_clock; + bool is_gm; + uint16_t count; + int total; uint8_t buf[2048]; struct avb_ethernet_header *h_reply; struct avb_packet_aecp_aem *p_reply; struct avb_packet_aecp_aem_get_as_path *body; uint64_t *path; - int total = (int)(sizeof(struct avb_ethernet_header) + - sizeof(struct avb_packet_aecp_aem) + - sizeof(struct avb_packet_aecp_aem_get_as_path) + - sizeof(uint64_t)); (void)now; - if (total > (int)sizeof(buf)) + have_gm = avb_gptp_get_grandmaster_id(server->gptp, &gm_id_be); + have_clock = avb_gptp_get_clock_id(server->gptp, &clock_id_be); + is_gm = avb_gptp_is_grandmaster(server->gptp); + + if (have_gm && have_clock) { + count = is_gm ? 1 : 2; + } else { + count = 1; + } + + total = (int)(sizeof(struct avb_ethernet_header) + + sizeof(struct avb_packet_aecp_aem) + + sizeof(struct avb_packet_aecp_aem_get_as_path) + + count * sizeof(uint64_t)); + + if (total > (int)sizeof(buf)) { return reply_no_resources(aecp, m, len); + } if (len < (int)(sizeof(*h_reply) + sizeof(*p_reply) + - sizeof(*body))) + sizeof(*body))) { return reply_bad_arguments(aecp, m, len); + } memcpy(buf, m, len); - if (len < total) + if (len < total) { memset(buf + len, 0, total - len); + } h_reply = (struct avb_ethernet_header *)buf; p_reply = SPA_PTROFF(h_reply, sizeof(*h_reply), void); @@ -47,10 +69,20 @@ int handle_cmd_get_as_path_milan_v12(struct aecp *aecp, int64_t now, sizeof(uint64_t))); body = (struct avb_packet_aecp_aem_get_as_path *)p_reply->payload; - body->reserved = htons(1); + /* IEEE 1722.1-2021 Section 7.4.41.2: count of clock_identity entries. */ + body->reserved = htons(count); path = (uint64_t *)(body + 1); - *path = htobe64(aecp->server->entity_id); + if (have_gm && have_clock) { + if (is_gm) { + path[0] = clock_id_be; + } else { + path[0] = gm_id_be; + path[1] = clock_id_be; + } + } else { + path[0] = htobe64(server->entity_id); + } return reply_success(aecp, buf, total); } From 21dd14618c9547f1b06f6291c03f599d2a6ebbb1 Mon Sep 17 00:00:00 2001 From: hackerman-kl Date: Wed, 29 Apr 2026 07:40:25 +0200 Subject: [PATCH 31/42] milan-avb: update banner --- src/modules/module-avb/gptp.c | 1 + src/modules/module-avb/gptp.h | 2 ++ 2 files changed, 3 insertions(+) diff --git a/src/modules/module-avb/gptp.c b/src/modules/module-avb/gptp.c index 4326165ae..41ecb863e 100644 --- a/src/modules/module-avb/gptp.c +++ b/src/modules/module-avb/gptp.c @@ -1,6 +1,7 @@ /* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2024 Dmitry Sharshakov */ /* SPDX-FileCopyrightText: Copyright © 2025 Nils Tonnaett */ +/* SPDX-FileCopyrightText: Copyright © 2026 Alexandre Malki */ /* SPDX-License-Identifier: MIT */ #include "gptp.h" diff --git a/src/modules/module-avb/gptp.h b/src/modules/module-avb/gptp.h index 7cacde7ae..631f37df9 100644 --- a/src/modules/module-avb/gptp.h +++ b/src/modules/module-avb/gptp.h @@ -1,5 +1,7 @@ /* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2024 Dmitry Sharshakov */ +/* SPDX-FileCopyrightText: Copyright © 2025 Nils Tonnaett */ +/* SPDX-FileCopyrightText: Copyright © 2026 Alexandre Malki */ /* SPDX-License-Identifier: MIT */ #ifndef AVB_GPTP_H From 4b44c157688bc60d60302ec69262ea96494a1de2 Mon Sep 17 00:00:00 2001 From: hackerman-kl Date: Wed, 29 Apr 2026 07:41:30 +0200 Subject: [PATCH 32/42] milan-avb; introducing gptp/as_path interface specific dirty flags --- src/modules/module-avb/aecp-aem-state.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/modules/module-avb/aecp-aem-state.h b/src/modules/module-avb/aecp-aem-state.h index 21c5743c8..df3f65c4c 100644 --- a/src/modules/module-avb/aecp-aem-state.h +++ b/src/modules/module-avb/aecp-aem-state.h @@ -104,6 +104,9 @@ struct aecp_aem_avb_interface_state { * has elapsed, then clears dirty and updates last_counters_emit_ns. */ bool counters_dirty; int64_t last_counters_emit_ns; + + bool gptp_info_dirty; + bool as_path_dirty; }; From 1b81dbab85aa8d6aacfa0c21e2a26f133c7639ab Mon Sep 17 00:00:00 2001 From: hackerman-kl Date: Wed, 29 Apr 2026 07:42:23 +0200 Subject: [PATCH 33/42] milan-avb: adding the ptp4l command line information --- src/modules/module-avb/gptp.c | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/modules/module-avb/gptp.c b/src/modules/module-avb/gptp.c index 41ecb863e..1ff824e62 100644 --- a/src/modules/module-avb/gptp.c +++ b/src/modules/module-avb/gptp.c @@ -4,6 +4,27 @@ /* SPDX-FileCopyrightText: Copyright © 2026 Alexandre Malki */ /* SPDX-License-Identifier: MIT */ +/* + * Companion ptp4l invocation (gPTP profile, UDS management enabled, + * Announce path trace recorded): + * + * ptp4l -i -f /etc/linuxptp/gPTP.cfg \ + * --uds_address=/var/run/ptp4l \ + * --path_trace_enabled=1 \ + * -m + * + * pipewire-avb.conf must point ptp.management-socket at the same path: + * + * ptp.management-socket = "/var/run/ptp4l" + * + * Equivalent pmc(8) probe to confirm ptp4l accepts our management + * messages (-t 1 forces transportSpecific = 1 / TS_IEEE_8021AS, + * matching PTP_GPTP_MANAGEMENT_TYPE in this file): + * + * pmc -u -b 0 -t 1 -s /var/run/ptp4l "GET PARENT_DATA_SET" + * pmc -u -b 0 -t 1 -s /var/run/ptp4l "GET PATH_TRACE_LIST" + */ + #include "gptp.h" #include From fc08d2444a4b11e8923dc0c17e13f9206e719836 Mon Sep 17 00:00:00 2001 From: hackerman-kl Date: Wed, 29 Apr 2026 07:45:33 +0200 Subject: [PATCH 34/42] milan-avb: gptp: send PTP management with majorSdoId=1 for gPTP profile --- src/modules/module-avb/gptp.c | 2 +- src/modules/module-avb/gptp.h | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/modules/module-avb/gptp.c b/src/modules/module-avb/gptp.c index 1ff824e62..e3cd88c8b 100644 --- a/src/modules/module-avb/gptp.c +++ b/src/modules/module-avb/gptp.c @@ -224,7 +224,7 @@ static int send_management_request(struct gptp *gptp, uint16_t management_id, spa_zero(req); seq = gptp->ptp_seq++; - req.major_sdo_id_message_type = PTP_MESSAGE_TYPE_MANAGEMENT; + req.major_sdo_id_message_type = PTP_GPTP_MANAGEMENT_TYPE; req.ver = PTP_VERSION_1588_2008_2_1; req.message_length_be = htons(sizeof(struct ptp_management_msg)); spa_zero(req.clock_identity); diff --git a/src/modules/module-avb/gptp.h b/src/modules/module-avb/gptp.h index 631f37df9..2e66f6b4f 100644 --- a/src/modules/module-avb/gptp.h +++ b/src/modules/module-avb/gptp.h @@ -16,6 +16,9 @@ extern "C" { #endif #define PTP_MESSAGE_TYPE_MANAGEMENT 0x0d +#define PTP_MAJOR_SDO_ID_GPTP (0x1u << 4) +#define PTP_GPTP_MANAGEMENT_TYPE \ + (PTP_MAJOR_SDO_ID_GPTP | PTP_MESSAGE_TYPE_MANAGEMENT) #define PTP_VERSION_1588_2008_2_1 0x12 #define PTP_DEFAULT_LOG_MESSAGE_INTERVAL 127 #define PTP_MGMT_ACTION_GET 0 From c877ea4243051b99a69cefd53235b6d09bbb918b Mon Sep 17 00:00:00 2001 From: hackerman-kl Date: Wed, 29 Apr 2026 07:45:57 +0200 Subject: [PATCH 35/42] milan-avb: gptp: rate-limit management requests to 375 ms --- src/modules/module-avb/gptp.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/modules/module-avb/gptp.c b/src/modules/module-avb/gptp.c index e3cd88c8b..56b9a8a2b 100644 --- a/src/modules/module-avb/gptp.c +++ b/src/modules/module-avb/gptp.c @@ -44,7 +44,8 @@ #define server_emit(s,m,v,...) spa_hook_list_call(&s->listener_list, struct server_events, m, v, ##__VA_ARGS__) #define server_emit_gm_changed(s, n, g) server_emit(s, gm_changed, 0, n, g) -#define PTP_REQUEST_TIMEOUT_NS (SPA_NSEC_PER_SEC / 2) +#define PTP_REQUEST_INTERVAL_NS (375 * SPA_NSEC_PER_MSEC) +#define PTP_REQUEST_TIMEOUT_NS PTP_REQUEST_INTERVAL_NS struct gptp { struct server *server; @@ -496,6 +497,11 @@ static void gptp_periodic(void *data, uint64_t now) return; } + if (gptp->req_sent_ns != 0 && + (now - gptp->req_sent_ns) < PTP_REQUEST_INTERVAL_NS) { + return; + } + err = send_management_request(gptp, next_management_id(gptp->tick_count), now); if (err == 0) { gptp->tick_count++; From 55bb0b6a6a0810828182a48f21b35dba9146c55a Mon Sep 17 00:00:00 2001 From: hackerman-kl Date: Wed, 29 Apr 2026 07:46:36 +0200 Subject: [PATCH 36/42] milan-avb: gptp: query CURRENT_DATA_SET for canonical is_grandmaster check --- src/modules/module-avb/gptp.c | 61 ++++++++++++++++++++++++++++++++--- src/modules/module-avb/gptp.h | 8 +++++ 2 files changed, 64 insertions(+), 5 deletions(-) diff --git a/src/modules/module-avb/gptp.c b/src/modules/module-avb/gptp.c index 56b9a8a2b..2aa4ac7d9 100644 --- a/src/modules/module-avb/gptp.c +++ b/src/modules/module-avb/gptp.c @@ -66,6 +66,10 @@ struct gptp { uint32_t tick_count; bool data_valid; + + uint16_t steps_removed; + int64_t offset_from_master_scaled_ns; + bool data_valid_current; }; static int make_bind_path(char *out, size_t out_size, uint64_t entity_id) @@ -368,6 +372,41 @@ static void handle_port_data_set(struct gptp *gptp, update_avb_interface_port(gptp, pds); } +static void handle_current_data_set(struct gptp *gptp, + const struct ptp_management_msg *res, + const uint8_t *payload, size_t payload_len) +{ + const struct ptp_current_data_set *cds; + uint16_t data_len; + uint16_t steps_removed; + int64_t offset_from_master; + + data_len = ntohs(res->management_message_length_be) - 2; + if (data_len != sizeof(struct ptp_current_data_set) || + payload_len < sizeof(struct ptp_current_data_set)) { + pw_log_warn("Unexpected PTP GET CURRENT_DATA_SET response length: " + "tlv=%u payload=%zu expected=%zu", + data_len, payload_len, + sizeof(struct ptp_current_data_set)); + return; + } + + cds = (const struct ptp_current_data_set *)payload; + steps_removed = ntohs(cds->steps_removed_be); + offset_from_master = (int64_t)be64toh((uint64_t)cds->offset_from_master_be); + + if (!gptp->data_valid_current || + gptp->steps_removed != steps_removed) { + pw_log_info("PTP currentDS: steps_removed=%u offset_from_master=%" + PRId64 " (scaled ns)", + steps_removed, offset_from_master); + } + + gptp->steps_removed = steps_removed; + gptp->offset_from_master_scaled_ns = offset_from_master; + gptp->data_valid_current = true; +} + static void on_ptp_mgmt_data(void *data, int fd, uint32_t mask) { struct gptp *gptp = data; @@ -457,6 +496,11 @@ static void on_ptp_mgmt_data(void *data, int fd, uint32_t mask) buf + sizeof(struct ptp_management_msg), (size_t)ret - sizeof(struct ptp_management_msg)); break; + case PTP_MGMT_ID_CURRENT_DATA_SET: + handle_current_data_set(gptp, &res, + buf + sizeof(struct ptp_management_msg), + (size_t)ret - sizeof(struct ptp_management_msg)); + break; default: pw_log_debug("Unhandled PTP management ID: %04x", mgmt_id); break; @@ -467,10 +511,11 @@ static void on_ptp_mgmt_data(void *data, int fd, uint32_t mask) static uint16_t next_management_id(uint32_t tick_count) { - switch (tick_count % 10) { - case 0: return PTP_MGMT_ID_DEFAULT_DATA_SET; - case 5: return PTP_MGMT_ID_PORT_DATA_SET; - default: return PTP_MGMT_ID_PARENT_DATA_SET; + switch (tick_count % 4) { + case 0: return PTP_MGMT_ID_PARENT_DATA_SET; + case 1: return PTP_MGMT_ID_CURRENT_DATA_SET; + case 2: return PTP_MGMT_ID_DEFAULT_DATA_SET; + default: return PTP_MGMT_ID_PORT_DATA_SET; } } @@ -587,7 +632,13 @@ bool avb_gptp_get_grandmaster_id(const struct avb_gptp *agptp, uint64_t *gm_id_b bool avb_gptp_is_grandmaster(const struct avb_gptp *agptp) { const struct gptp *gptp = (const struct gptp *)agptp; - if (gptp == NULL || !gptp->data_valid) { + if (gptp == NULL) { + return false; + } + if (gptp->data_valid_current) { + return gptp->steps_removed == 0; + } + if (!gptp->data_valid) { return false; } return memcmp(gptp->clock_id, gptp->gm_id, 8) == 0; diff --git a/src/modules/module-avb/gptp.h b/src/modules/module-avb/gptp.h index 2e66f6b4f..5c65f9000 100644 --- a/src/modules/module-avb/gptp.h +++ b/src/modules/module-avb/gptp.h @@ -25,6 +25,7 @@ extern "C" { #define PTP_MGMT_ACTION_RESPONSE 2 #define PTP_TLV_TYPE_MGMT 0x0001 #define PTP_MGMT_ID_DEFAULT_DATA_SET 0x2000 +#define PTP_MGMT_ID_CURRENT_DATA_SET 0x2001 #define PTP_MGMT_ID_PARENT_DATA_SET 0x2002 #define PTP_MGMT_ID_PORT_DATA_SET 0x2004 @@ -91,6 +92,13 @@ struct ptp_default_data_set { uint8_t reserved2; } __attribute__((packed)); +/* IEEE 1588-2008 Section 15.5.1.2 currentDS */ +struct ptp_current_data_set { + uint16_t steps_removed_be; + int64_t offset_from_master_be; + int64_t mean_path_delay_be; +} __attribute__((packed)); + /* IEEE 1588-2008 Section 15.5.1.4 portDS */ struct ptp_port_data_set { uint8_t port_clock_identity[8]; From e02a4854dee76cb13601db6754e5a107a305bcc7 Mon Sep 17 00:00:00 2001 From: hackerman-kl Date: Wed, 29 Apr 2026 07:47:37 +0200 Subject: [PATCH 37/42] milan-avb: gptp: query PATH_TRACE_LIST and store Announce path trace --- src/modules/module-avb/gptp.c | 75 +++++++++++++++++++++++++++++++++-- src/modules/module-avb/gptp.h | 5 +++ 2 files changed, 77 insertions(+), 3 deletions(-) diff --git a/src/modules/module-avb/gptp.c b/src/modules/module-avb/gptp.c index 2aa4ac7d9..64b27da7f 100644 --- a/src/modules/module-avb/gptp.c +++ b/src/modules/module-avb/gptp.c @@ -70,6 +70,10 @@ struct gptp { uint16_t steps_removed; int64_t offset_from_master_scaled_ns; bool data_valid_current; + + uint64_t path_trace[PTP_AS_PATH_MAX_ENTRIES]; + uint16_t path_trace_count; + bool path_trace_valid; }; static int make_bind_path(char *out, size_t out_size, uint64_t entity_id) @@ -407,6 +411,48 @@ static void handle_current_data_set(struct gptp *gptp, gptp->data_valid_current = true; } +/* IEEE 1588-2008 Section 16.2.1 PATH_TRACE_LIST */ +static void handle_path_trace_list(struct gptp *gptp, + const struct ptp_management_msg *res, + const uint8_t *payload, size_t payload_len) +{ + uint16_t data_len; + uint16_t entries; + uint16_t i; + uint64_t new_path[PTP_AS_PATH_MAX_ENTRIES]; + bool changed; + + data_len = ntohs(res->management_message_length_be) - 2; + if ((data_len % 8) != 0 || payload_len < data_len) { + pw_log_warn("Unexpected PTP GET PATH_TRACE_LIST response length: " + "tlv=%u payload=%zu", data_len, payload_len); + return; + } + + entries = data_len / 8; + if (entries > PTP_AS_PATH_MAX_ENTRIES) { + pw_log_warn("PTP PATH_TRACE_LIST has %u entries, capping to %u", + entries, PTP_AS_PATH_MAX_ENTRIES); + entries = PTP_AS_PATH_MAX_ENTRIES; + } + + for (i = 0; i < entries; i++) { + memcpy(&new_path[i], payload + i * 8, 8); + } + + changed = !gptp->path_trace_valid || + gptp->path_trace_count != entries || + memcmp(gptp->path_trace, new_path, + entries * sizeof(uint64_t)) != 0; + + if (changed) { + pw_log_info("PTP path_trace updated: %u entries", entries); + memcpy(gptp->path_trace, new_path, entries * sizeof(uint64_t)); + gptp->path_trace_count = entries; + gptp->path_trace_valid = true; + } +} + static void on_ptp_mgmt_data(void *data, int fd, uint32_t mask) { struct gptp *gptp = data; @@ -501,6 +547,11 @@ static void on_ptp_mgmt_data(void *data, int fd, uint32_t mask) buf + sizeof(struct ptp_management_msg), (size_t)ret - sizeof(struct ptp_management_msg)); break; + case PTP_MGMT_ID_PATH_TRACE_LIST: + handle_path_trace_list(gptp, &res, + buf + sizeof(struct ptp_management_msg), + (size_t)ret - sizeof(struct ptp_management_msg)); + break; default: pw_log_debug("Unhandled PTP management ID: %04x", mgmt_id); break; @@ -511,10 +562,11 @@ static void on_ptp_mgmt_data(void *data, int fd, uint32_t mask) static uint16_t next_management_id(uint32_t tick_count) { - switch (tick_count % 4) { + switch (tick_count % 5) { case 0: return PTP_MGMT_ID_PARENT_DATA_SET; - case 1: return PTP_MGMT_ID_CURRENT_DATA_SET; - case 2: return PTP_MGMT_ID_DEFAULT_DATA_SET; + case 1: return PTP_MGMT_ID_PATH_TRACE_LIST; + case 2: return PTP_MGMT_ID_CURRENT_DATA_SET; + case 3: return PTP_MGMT_ID_DEFAULT_DATA_SET; default: return PTP_MGMT_ID_PORT_DATA_SET; } } @@ -643,3 +695,20 @@ bool avb_gptp_is_grandmaster(const struct avb_gptp *agptp) } return memcmp(gptp->clock_id, gptp->gm_id, 8) == 0; } + +uint16_t avb_gptp_get_path_trace(const struct avb_gptp *agptp, + uint64_t *path_be, uint16_t max_entries) +{ + const struct gptp *gptp = (const struct gptp *)agptp; + uint16_t count; + + if (gptp == NULL || !gptp->path_trace_valid) { + return 0; + } + count = gptp->path_trace_count; + if (count > max_entries) { + count = max_entries; + } + memcpy(path_be, gptp->path_trace, count * sizeof(uint64_t)); + return count; +} diff --git a/src/modules/module-avb/gptp.h b/src/modules/module-avb/gptp.h index 5c65f9000..35b20f4f6 100644 --- a/src/modules/module-avb/gptp.h +++ b/src/modules/module-avb/gptp.h @@ -28,6 +28,8 @@ extern "C" { #define PTP_MGMT_ID_CURRENT_DATA_SET 0x2001 #define PTP_MGMT_ID_PARENT_DATA_SET 0x2002 #define PTP_MGMT_ID_PORT_DATA_SET 0x2004 +#define PTP_MGMT_ID_PATH_TRACE_LIST 0x401C +#define PTP_AS_PATH_MAX_ENTRIES 16 /**************************************************************************************/ /* IEEE 1588-2019, Sec. 15.4.1 PTP management message format - Common Fields */ @@ -120,6 +122,9 @@ bool avb_gptp_get_clock_id(const struct avb_gptp *gptp, uint64_t *clock_id_be); bool avb_gptp_get_grandmaster_id(const struct avb_gptp *gptp, uint64_t *gm_id_be); bool avb_gptp_is_grandmaster(const struct avb_gptp *gptp); +uint16_t avb_gptp_get_path_trace(const struct avb_gptp *gptp, + uint64_t *path_be, uint16_t max_entries); + #ifdef __cplusplus } #endif From ff3367dc05a236ac9ff29432f74366218a574c2f Mon Sep 17 00:00:00 2001 From: hackerman-kl Date: Wed, 29 Apr 2026 07:48:53 +0200 Subject: [PATCH 38/42] milan-avb: aecp-aem: emit unsolicited GET_AVB_INFO when gPTP changes --- src/modules/module-avb/aecp-aem.c | 85 ++++++++++++++++++++++++++----- src/modules/module-avb/gptp.c | 22 +++++++- 2 files changed, 91 insertions(+), 16 deletions(-) diff --git a/src/modules/module-avb/aecp-aem.c b/src/modules/module-avb/aecp-aem.c index f244c5440..17f8fbf61 100644 --- a/src/modules/module-avb/aecp-aem.c +++ b/src/modules/module-avb/aecp-aem.c @@ -7,6 +7,7 @@ #include "aecp-aem-descriptors.h" #include "aecp-aem-state.h" #include "aecp-aem-cmds-resps/cmd-resp-helpers.h" +#include "aecp-aem-cmds-resps/reply-unsol-helpers.h" #include "gptp.h" #include "utils.h" @@ -131,6 +132,25 @@ static int handle_read_descriptor_common(struct aecp *aecp, int64_t now, const v return avb_server_send_packet(server, h->src, AVB_TSN_ETH, buf, size); } +static void fill_get_avb_info_body(struct server *server, + struct avb_aem_desc_avb_interface *avb_interface, + struct avb_packet_aecp_aem_get_avb_info *i) +{ + uint64_t gm_id_be; + uint8_t flags = AVB_AEM_AVB_INFO_FLAG_SRP_ENABLED; + + if (avb_gptp_get_grandmaster_id(server->gptp, &gm_id_be)) { + i->gptp_grandmaster_id = gm_id_be; + flags |= AVB_AEM_AVB_INFO_FLAG_GPTP_ENABLED; + } else { + i->gptp_grandmaster_id = avb_interface->clock_identity; + } + i->flags = flags; + i->propagation_delay = htonl(0); + i->gptp_domain_number = avb_interface->domain_number; + i->msrp_mappings_count = htons(0); +} + /* GET_AVB_INFO */ static int handle_get_avb_info_common(struct aecp *aecp, int64_t now, const void *m, int len) @@ -145,8 +165,6 @@ static int handle_get_avb_info_common(struct aecp *aecp, int64_t now, const struct descriptor *desc; uint8_t buf[2048]; size_t size, psize; - uint64_t gm_id_be; - uint8_t flags; i = (struct avb_packet_aecp_aem_get_avb_info*)p->payload; @@ -177,22 +195,49 @@ static int handle_get_avb_info_common(struct aecp *aecp, int64_t now, AVB_PACKET_SET_LENGTH(&reply->aecp.hdr, psize + 12); i = (struct avb_packet_aecp_aem_get_avb_info*)reply->payload; - flags = AVB_AEM_AVB_INFO_FLAG_SRP_ENABLED; - /* IEEE 1722.1-2021 Section 7.4.40 GET_AVB_INFO. */ - if (avb_gptp_get_grandmaster_id(server->gptp, &gm_id_be)) { - i->gptp_grandmaster_id = gm_id_be; - flags |= AVB_AEM_AVB_INFO_FLAG_GPTP_ENABLED; - } else { - i->gptp_grandmaster_id = avb_interface->clock_identity; - } - i->flags = flags; - i->propagation_delay = htonl(0); - i->gptp_domain_number = avb_interface->domain_number; - i->msrp_mappings_count = htons(0); + fill_get_avb_info_body(server, avb_interface, i); return avb_server_send_packet(server, h->src, AVB_TSN_ETH, buf, size); } +#define GET_AVB_INFO_PACKET_LEN \ + (int)(sizeof(struct avb_ethernet_header) + \ + sizeof(struct avb_packet_aecp_aem) + \ + sizeof(struct avb_packet_aecp_aem_get_avb_info)) + +static void emit_unsol_get_avb_info(struct aecp *aecp, uint16_t desc_index) +{ + struct server *server = aecp->server; + uint8_t buf[GET_AVB_INFO_PACKET_LEN]; + struct avb_ethernet_header *h; + struct avb_packet_aecp_aem *p; + struct avb_packet_aecp_aem_get_avb_info *body; + struct descriptor *desc; + struct aecp_aem_base_info b_state = { 0 }; + + desc = server_find_descriptor(server, AVB_AEM_DESC_AVB_INTERFACE, desc_index); + if (desc == NULL) { + return; + } + + memset(buf, 0, sizeof(buf)); + h = (struct avb_ethernet_header *)buf; + p = SPA_PTROFF(h, sizeof(*h), void); + body = (struct avb_packet_aecp_aem_get_avb_info *)p->payload; + + AVB_PACKET_AECP_SET_MESSAGE_TYPE(&p->aecp, AVB_AECP_MESSAGE_TYPE_AEM_RESPONSE); + AVB_PACKET_AECP_SET_STATUS(&p->aecp, AVB_AECP_AEM_STATUS_SUCCESS); + p->cmd1 = 0; + p->cmd2 = AVB_AECP_AEM_CMD_GET_AVB_INFO; + + body->descriptor_type = htons(AVB_AEM_DESC_AVB_INTERFACE); + body->descriptor_id = htons(desc_index); + fill_get_avb_info_body(server, descriptor_body(desc), body); + + (void)reply_unsolicited_notifications(aecp, &b_state, buf, + GET_AVB_INFO_PACKET_LEN, true); +} + /* 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 */ @@ -586,6 +631,18 @@ void avb_aecp_aem_periodic(struct aecp *aecp, int64_t now) so->stream_info_dirty = false; } } + for (i = 0; i < UINT16_MAX; i++) { + struct descriptor *d = server_find_descriptor(srv, + AVB_AEM_DESC_AVB_INTERFACE, i); + struct aecp_aem_avb_interface_state *ifs; + if (d == NULL) + break; + ifs = d->ptr; + if (ifs->gptp_info_dirty) { + emit_unsol_get_avb_info(aecp, i); + ifs->gptp_info_dirty = false; + } + } } } diff --git a/src/modules/module-avb/gptp.c b/src/modules/module-avb/gptp.c index 64b27da7f..90c1f15f1 100644 --- a/src/modules/module-avb/gptp.c +++ b/src/modules/module-avb/gptp.c @@ -40,6 +40,7 @@ #include #include "aecp-aem-descriptors.h" +#include "aecp-aem-state.h" #define server_emit(s,m,v,...) spa_hook_list_call(&s->listener_list, struct server_events, m, v, ##__VA_ARGS__) #define server_emit_gm_changed(s, n, g) server_emit(s, gm_changed, 0, n, g) @@ -193,6 +194,19 @@ static void update_avb_interface_clock_identity(struct gptp *gptp, memcpy(&iface->clock_identity, cid, sizeof(iface->clock_identity)); } +static void mark_gptp_info_dirty(struct gptp *gptp) +{ + struct descriptor *d = server_find_descriptor(gptp->server, + AVB_AEM_DESC_AVB_INTERFACE, 0); + struct aecp_aem_avb_interface_state *ifs; + + if (d == NULL) { + return; + } + ifs = d->ptr; + ifs->gptp_info_dirty = true; +} + static void update_avb_interface_default(struct gptp *gptp, const struct ptp_default_data_set *dds) { @@ -323,12 +337,14 @@ static void handle_parent_data_set(struct gptp *gptp, memcpy(gptp->gm_id, gmid, 8); gptp->data_valid = true; - /* IEEE 1722.1-2021 Section 7.2.8: AVB_INTERFACE.clock_identity is - * the local gPTP clock. */ if (cid_changed) { update_avb_interface_clock_identity(gptp, cid); } + if (cid_changed || gmid_changed) { + mark_gptp_info_dirty(gptp); + } + if (gmid_changed) { clock_gettime(CLOCK_REALTIME, &ts); server_emit_gm_changed(gptp->server, @@ -354,6 +370,7 @@ static void handle_default_data_set(struct gptp *gptp, } dds = (const struct ptp_default_data_set *)payload; update_avb_interface_default(gptp, dds); + mark_gptp_info_dirty(gptp); } static void handle_port_data_set(struct gptp *gptp, @@ -404,6 +421,7 @@ static void handle_current_data_set(struct gptp *gptp, pw_log_info("PTP currentDS: steps_removed=%u offset_from_master=%" PRId64 " (scaled ns)", steps_removed, offset_from_master); + mark_gptp_info_dirty(gptp); } gptp->steps_removed = steps_removed; From 97436efe1e966fc58b97796118ed7ecb3ab90f9e Mon Sep 17 00:00:00 2001 From: hackerman-kl Date: Wed, 29 Apr 2026 07:49:50 +0200 Subject: [PATCH 39/42] milan-avb: cmd-get-as-path: build [gm,...,local] and emit unsolicited GET_AS_PATH --- .../aecp-aem-cmds-resps/cmd-get-as-path.c | 136 ++++++++++++------ .../aecp-aem-cmds-resps/cmd-get-as-path.h | 2 + src/modules/module-avb/aecp-aem.c | 4 + src/modules/module-avb/gptp.c | 14 ++ 4 files changed, 116 insertions(+), 40 deletions(-) diff --git a/src/modules/module-avb/aecp-aem-cmds-resps/cmd-get-as-path.c b/src/modules/module-avb/aecp-aem-cmds-resps/cmd-get-as-path.c index 42643a194..6b946b467 100644 --- a/src/modules/module-avb/aecp-aem-cmds-resps/cmd-get-as-path.c +++ b/src/modules/module-avb/aecp-aem-cmds-resps/cmd-get-as-path.c @@ -5,42 +5,81 @@ #include "../aecp.h" #include "../aecp-aem.h" +#include "../aecp-aem-descriptors.h" +#include "../aecp-aem-state.h" #include "../gptp.h" #include "../internal.h" #include "cmd-get-as-path.h" #include "cmd-resp-helpers.h" +#include "reply-unsol-helpers.h" + +/* IEEE 1722.1-2021 Section 7.4.41.2 path_sequence */ +static uint16_t fill_get_as_path_body(struct server *server, + struct avb_packet_aecp_aem_get_as_path *body) +{ + uint64_t *path = (uint64_t *)(body + 1); + uint64_t clock_id_be = 0; + uint64_t gm_id_be = 0; + bool have_clock = avb_gptp_get_clock_id(server->gptp, &clock_id_be); + bool have_gm = avb_gptp_get_grandmaster_id(server->gptp, &gm_id_be); + bool is_gm = avb_gptp_is_grandmaster(server->gptp); + uint16_t count = 0; + + if (!have_clock) { + body->reserved = htons(0); + return 0; + } + + if (is_gm) { + path[0] = clock_id_be; + count = 1; + } else { + count = avb_gptp_get_path_trace(server->gptp, path, + PTP_AS_PATH_MAX_ENTRIES - 1); + + if (count == 0 && have_gm) { + path[0] = gm_id_be; + count = 1; + } + + if (count < PTP_AS_PATH_MAX_ENTRIES) { + path[count++] = clock_id_be; + } + } + + body->reserved = htons(count); + return count; +} /* IEEE 1722.1-2021 Section 7.4.41 GET_AS_PATH. */ int handle_cmd_get_as_path_milan_v12(struct aecp *aecp, int64_t now, const void *m, int len) { struct server *server = aecp->server; - uint64_t gm_id_be = 0; - uint64_t clock_id_be = 0; - bool have_gm; - bool have_clock; - bool is_gm; - uint16_t count; int total; + uint16_t count; uint8_t buf[2048]; struct avb_ethernet_header *h_reply; struct avb_packet_aecp_aem *p_reply; struct avb_packet_aecp_aem_get_as_path *body; - uint64_t *path; (void)now; - have_gm = avb_gptp_get_grandmaster_id(server->gptp, &gm_id_be); - have_clock = avb_gptp_get_clock_id(server->gptp, &clock_id_be); - is_gm = avb_gptp_is_grandmaster(server->gptp); - - if (have_gm && have_clock) { - count = is_gm ? 1 : 2; - } else { - count = 1; + if (len < (int)(sizeof(*h_reply) + sizeof(*p_reply) + + sizeof(*body))) { + return reply_bad_arguments(aecp, m, len); } + memcpy(buf, m, len); + + h_reply = (struct avb_ethernet_header *)buf; + p_reply = SPA_PTROFF(h_reply, sizeof(*h_reply), void); + body = (struct avb_packet_aecp_aem_get_as_path *)p_reply->payload; + + memset(body, 0, sizeof(*body)); + count = fill_get_as_path_body(server, body); + total = (int)(sizeof(struct avb_ethernet_header) + sizeof(struct avb_packet_aecp_aem) + sizeof(struct avb_packet_aecp_aem_get_as_path) + @@ -49,40 +88,57 @@ int handle_cmd_get_as_path_milan_v12(struct aecp *aecp, int64_t now, if (total > (int)sizeof(buf)) { return reply_no_resources(aecp, m, len); } - if (len < (int)(sizeof(*h_reply) + sizeof(*p_reply) + - sizeof(*body))) { - return reply_bad_arguments(aecp, m, len); - } - - memcpy(buf, m, len); if (len < total) { memset(buf + len, 0, total - len); } - h_reply = (struct avb_ethernet_header *)buf; - p_reply = SPA_PTROFF(h_reply, sizeof(*h_reply), void); - /* IEEE 1722.1-2021 Section 9.2.1.1.7: CDL excludes the 12-octet AVTPDU common. */ AVB_PACKET_SET_LENGTH(&p_reply->aecp.hdr, (uint16_t)(total - sizeof(*h_reply) - sizeof(struct avb_packet_header) - sizeof(uint64_t))); - body = (struct avb_packet_aecp_aem_get_as_path *)p_reply->payload; - /* IEEE 1722.1-2021 Section 7.4.41.2: count of clock_identity entries. */ - body->reserved = htons(count); - - path = (uint64_t *)(body + 1); - if (have_gm && have_clock) { - if (is_gm) { - path[0] = clock_id_be; - } else { - path[0] = gm_id_be; - path[1] = clock_id_be; - } - } else { - path[0] = htobe64(server->entity_id); - } - return reply_success(aecp, buf, total); } + +void cmd_get_as_path_emit_unsol_milan_v12(struct aecp *aecp, uint16_t desc_index) +{ + struct server *server = aecp->server; + struct descriptor *desc; + uint8_t buf[256]; + struct avb_ethernet_header *h; + struct avb_packet_aecp_aem *p; + struct avb_packet_aecp_aem_get_as_path *body; + struct aecp_aem_base_info b_state = { 0 }; + uint16_t count; + int total; + + desc = server_find_descriptor(server, AVB_AEM_DESC_AVB_INTERFACE, desc_index); + if (desc == NULL) { + return; + } + + memset(buf, 0, sizeof(buf)); + h = (struct avb_ethernet_header *)buf; + p = SPA_PTROFF(h, sizeof(*h), void); + body = (struct avb_packet_aecp_aem_get_as_path *)p->payload; + + AVB_PACKET_AECP_SET_MESSAGE_TYPE(&p->aecp, AVB_AECP_MESSAGE_TYPE_AEM_RESPONSE); + AVB_PACKET_AECP_SET_STATUS(&p->aecp, AVB_AECP_AEM_STATUS_SUCCESS); + p->cmd1 = 0; + p->cmd2 = AVB_AECP_AEM_CMD_GET_AS_PATH; + + body->descriptor_index = htons(desc_index); + count = fill_get_as_path_body(server, body); + + total = (int)(sizeof(struct avb_ethernet_header) + + sizeof(struct avb_packet_aecp_aem) + + sizeof(*body) + count * sizeof(uint64_t)); + + if (total > (int)sizeof(buf)) { + return; + } + + (void)reply_unsolicited_notifications(aecp, &b_state, buf, + total, true); +} diff --git a/src/modules/module-avb/aecp-aem-cmds-resps/cmd-get-as-path.h b/src/modules/module-avb/aecp-aem-cmds-resps/cmd-get-as-path.h index fa25c89aa..d23543976 100644 --- a/src/modules/module-avb/aecp-aem-cmds-resps/cmd-get-as-path.h +++ b/src/modules/module-avb/aecp-aem-cmds-resps/cmd-get-as-path.h @@ -9,4 +9,6 @@ int handle_cmd_get_as_path_milan_v12(struct aecp *aecp, int64_t now, const void *m, int len); +void cmd_get_as_path_emit_unsol_milan_v12(struct aecp *aecp, uint16_t desc_index); + #endif /* __AVB_AECP_AEM_CMD_GET_AS_PATH_H__ */ diff --git a/src/modules/module-avb/aecp-aem.c b/src/modules/module-avb/aecp-aem.c index 17f8fbf61..f3fd2a531 100644 --- a/src/modules/module-avb/aecp-aem.c +++ b/src/modules/module-avb/aecp-aem.c @@ -642,6 +642,10 @@ void avb_aecp_aem_periodic(struct aecp *aecp, int64_t now) emit_unsol_get_avb_info(aecp, i); ifs->gptp_info_dirty = false; } + if (ifs->as_path_dirty) { + cmd_get_as_path_emit_unsol_milan_v12(aecp, i); + ifs->as_path_dirty = false; + } } } } diff --git a/src/modules/module-avb/gptp.c b/src/modules/module-avb/gptp.c index 90c1f15f1..3e5bfec04 100644 --- a/src/modules/module-avb/gptp.c +++ b/src/modules/module-avb/gptp.c @@ -207,6 +207,19 @@ static void mark_gptp_info_dirty(struct gptp *gptp) ifs->gptp_info_dirty = true; } +static void mark_as_path_dirty(struct gptp *gptp) +{ + struct descriptor *d = server_find_descriptor(gptp->server, + AVB_AEM_DESC_AVB_INTERFACE, 0); + struct aecp_aem_avb_interface_state *ifs; + + if (d == NULL) { + return; + } + ifs = d->ptr; + ifs->as_path_dirty = true; +} + static void update_avb_interface_default(struct gptp *gptp, const struct ptp_default_data_set *dds) { @@ -468,6 +481,7 @@ static void handle_path_trace_list(struct gptp *gptp, memcpy(gptp->path_trace, new_path, entries * sizeof(uint64_t)); gptp->path_trace_count = entries; gptp->path_trace_valid = true; + mark_as_path_dirty(gptp); } } From 09b3f0dc34d42222951c03f813c18ad67e572f0a Mon Sep 17 00:00:00 2001 From: hackerman-kl Date: Wed, 29 Apr 2026 07:50:29 +0200 Subject: [PATCH 40/42] milan-avb: gptp: invalidate cached state when ptp4l stops responding --- src/modules/module-avb/gptp.c | 51 +++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/src/modules/module-avb/gptp.c b/src/modules/module-avb/gptp.c index 3e5bfec04..a6c835692 100644 --- a/src/modules/module-avb/gptp.c +++ b/src/modules/module-avb/gptp.c @@ -41,12 +41,14 @@ #include "aecp-aem-descriptors.h" #include "aecp-aem-state.h" +#include "entity-model-milan-v12.h" #define server_emit(s,m,v,...) spa_hook_list_call(&s->listener_list, struct server_events, m, v, ##__VA_ARGS__) #define server_emit_gm_changed(s, n, g) server_emit(s, gm_changed, 0, n, g) #define PTP_REQUEST_INTERVAL_NS (375 * SPA_NSEC_PER_MSEC) #define PTP_REQUEST_TIMEOUT_NS PTP_REQUEST_INTERVAL_NS +#define PTP_LOST_TIMEOUT_THRESHOLD 8 struct gptp { struct server *server; @@ -68,6 +70,8 @@ struct gptp { uint32_t tick_count; bool data_valid; + uint32_t consecutive_timeouts; + uint16_t steps_removed; int64_t offset_from_master_scaled_ns; bool data_valid_current; @@ -220,6 +224,46 @@ static void mark_as_path_dirty(struct gptp *gptp) ifs->as_path_dirty = true; } +static void gptp_invalidate_state(struct gptp *gptp) +{ + bool had_data = gptp->data_valid || gptp->data_valid_current || + gptp->path_trace_valid; + struct avb_aem_desc_avb_interface *iface; + + gptp->data_valid = false; + gptp->data_valid_current = false; + gptp->path_trace_valid = false; + gptp->path_trace_count = 0; + gptp->steps_removed = 0; + gptp->offset_from_master_scaled_ns = 0; + memset(gptp->clock_id, 0, sizeof(gptp->clock_id)); + memset(gptp->gm_id, 0, sizeof(gptp->gm_id)); + memset(gptp->path_trace, 0, sizeof(gptp->path_trace)); + + iface = get_avb_interface(gptp); + if (iface != NULL) { + iface->clock_identity = htobe64(gptp->server->entity_id); + iface->priority1 = DSC_AVB_INTERFACE_PRIORITY1; + iface->clock_class = DSC_AVB_INTERFACE_CLOCK_CLASS; + iface->clock_accuracy = DSC_AVB_INTERFACE_CLOCK_ACCURACY; + iface->offset_scaled_log_variance = + htons(DSC_AVB_INTERFACE_OFFSET_SCALED_LOG_VARIANCE); + iface->priority2 = DSC_AVB_INTERFACE_PRIORITY2; + iface->domain_number = DSC_AVB_INTERFACE_DOMAIN_NUMBER; + iface->log_sync_interval = DSC_AVB_INTERFACE_LOG_SYNC_INTERVAL; + iface->log_announce_interval = DSC_AVB_INTERFACE_LOG_ANNOUNCE_INTERVAL; + iface->log_pdelay_interval = DSC_AVB_INTERFACE_PDELAY_INTERVAL; + iface->port_number = DSC_AVB_INTERFACE_PORT_NUMBER; + } + + if (had_data) { + pw_log_info("PTP management lost contact with ptp4l, " + "clearing cached gPTP state"); + mark_gptp_info_dirty(gptp); + mark_as_path_dirty(gptp); + } +} + static void update_avb_interface_default(struct gptp *gptp, const struct ptp_default_data_set *dds) { @@ -558,6 +602,8 @@ static void on_ptp_mgmt_data(void *data, int fd, uint32_t mask) continue; } + gptp->consecutive_timeouts = 0; + switch (mgmt_id) { case PTP_MGMT_ID_PARENT_DATA_SET: handle_parent_data_set(gptp, &res, @@ -620,6 +666,10 @@ static void gptp_periodic(void *data, uint64_t now) pw_log_debug("PTP management request seq=%u timed out", gptp->req_sequence_id); gptp->req_in_flight = false; + gptp->consecutive_timeouts++; + if (gptp->consecutive_timeouts >= PTP_LOST_TIMEOUT_THRESHOLD) { + gptp_invalidate_state(gptp); + } } if (gptp->req_in_flight) { @@ -636,6 +686,7 @@ static void gptp_periodic(void *data, uint64_t now) gptp->tick_count++; } else if (err == -ENOTCONN) { pw_log_info("PTP management socket disconnected, will reopen"); + gptp_invalidate_state(gptp); gptp_close_socket(gptp); } } From 4da6e392810cf42135cdaac5be504540b9fc3bf8 Mon Sep 17 00:00:00 2001 From: hackerman-kl Date: Wed, 29 Apr 2026 07:50:44 +0200 Subject: [PATCH 41/42] milan-avb: gptp: handle MANAGEMENT_ERROR_STATUS TLV as stale-data signal --- src/modules/module-avb/gptp.c | 13 ++++++++++++- src/modules/module-avb/gptp.h | 1 + 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/modules/module-avb/gptp.c b/src/modules/module-avb/gptp.c index a6c835692..4a09260e8 100644 --- a/src/modules/module-avb/gptp.c +++ b/src/modules/module-avb/gptp.c @@ -588,8 +588,19 @@ static void on_ptp_mgmt_data(void *data, int fd, uint32_t mask) continue; } + if (ntohs(res.tlv_type_be) == PTP_TLV_TYPE_MGMT_ERROR_STATUS) { + pw_log_debug("PTP management error TLV for id=%04x seq=%u", + mgmt_id, seq); + gptp->req_in_flight = false; + gptp->consecutive_timeouts++; + if (gptp->consecutive_timeouts >= PTP_LOST_TIMEOUT_THRESHOLD) { + gptp_invalidate_state(gptp); + } + continue; + } + if (ntohs(res.tlv_type_be) != PTP_TLV_TYPE_MGMT) { - pw_log_warn("PTP management returned tlv type %d, expected management", + pw_log_debug("PTP management returned unexpected tlv type %d", ntohs(res.tlv_type_be)); gptp->req_in_flight = false; continue; diff --git a/src/modules/module-avb/gptp.h b/src/modules/module-avb/gptp.h index 35b20f4f6..7cf0bc6b1 100644 --- a/src/modules/module-avb/gptp.h +++ b/src/modules/module-avb/gptp.h @@ -24,6 +24,7 @@ extern "C" { #define PTP_MGMT_ACTION_GET 0 #define PTP_MGMT_ACTION_RESPONSE 2 #define PTP_TLV_TYPE_MGMT 0x0001 +#define PTP_TLV_TYPE_MGMT_ERROR_STATUS 0x0002 #define PTP_MGMT_ID_DEFAULT_DATA_SET 0x2000 #define PTP_MGMT_ID_CURRENT_DATA_SET 0x2001 #define PTP_MGMT_ID_PARENT_DATA_SET 0x2002 From 7a826b1580c912ea0e3d80e6e33fe7d8a4afe2b5 Mon Sep 17 00:00:00 2001 From: hackerman-kl Date: Wed, 29 Apr 2026 07:55:51 +0200 Subject: [PATCH 42/42] milan-avb: gptp: track request timing on CLOCK_MONOTONIC --- src/modules/module-avb/gptp.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/modules/module-avb/gptp.c b/src/modules/module-avb/gptp.c index 4a09260e8..8f78d3cc1 100644 --- a/src/modules/module-avb/gptp.c +++ b/src/modules/module-avb/gptp.c @@ -663,8 +663,12 @@ static uint16_t next_management_id(uint32_t tick_count) static void gptp_periodic(void *data, uint64_t now) { struct gptp *gptp = data; + struct timespec mono_ts; + uint64_t mono_now; int err; + (void)now; + if (!gptp->ptp_mgmt_socket_path) { return; } @@ -673,7 +677,10 @@ static void gptp_periodic(void *data, uint64_t now) return; } - if (gptp->req_in_flight && (now - gptp->req_sent_ns) > PTP_REQUEST_TIMEOUT_NS) { + clock_gettime(CLOCK_MONOTONIC, &mono_ts); + mono_now = SPA_TIMESPEC_TO_NSEC(&mono_ts); + + if (gptp->req_in_flight && (mono_now - gptp->req_sent_ns) > PTP_REQUEST_TIMEOUT_NS) { pw_log_debug("PTP management request seq=%u timed out", gptp->req_sequence_id); gptp->req_in_flight = false; @@ -688,11 +695,11 @@ static void gptp_periodic(void *data, uint64_t now) } if (gptp->req_sent_ns != 0 && - (now - gptp->req_sent_ns) < PTP_REQUEST_INTERVAL_NS) { + (mono_now - gptp->req_sent_ns) < PTP_REQUEST_INTERVAL_NS) { return; } - err = send_management_request(gptp, next_management_id(gptp->tick_count), now); + err = send_management_request(gptp, next_management_id(gptp->tick_count), mono_now); if (err == 0) { gptp->tick_count++; } else if (err == -ENOTCONN) {