From c551acf4d12358214288b2296bc65860a068237b Mon Sep 17 00:00:00 2001 From: hackerman-kl Date: Thu, 16 Apr 2026 08:10:14 +0200 Subject: [PATCH] milan-avb: lock: make it lockable: 1. The period calls were added to handle timeouts. 2. Handle the case where lock must be unlocked after 60s if the controller owning the locked does not release it. --- .../aecp-aem-cmds-resps/cmd-lock-entity.c | 35 +++++++++++ .../aecp-aem-cmds-resps/cmd-lock-entity.h | 9 +++ src/modules/module-avb/aecp-aem.c | 60 +++++++++++++++++++ src/modules/module-avb/aecp-aem.h | 1 + src/modules/module-avb/aecp.c | 7 +++ 5 files changed, 112 insertions(+) diff --git a/src/modules/module-avb/aecp-aem-cmds-resps/cmd-lock-entity.c b/src/modules/module-avb/aecp-aem-cmds-resps/cmd-lock-entity.c index 73b7d2548..4b271b7fc 100644 --- a/src/modules/module-avb/aecp-aem-cmds-resps/cmd-lock-entity.c +++ b/src/modules/module-avb/aecp-aem-cmds-resps/cmd-lock-entity.c @@ -71,6 +71,41 @@ static int handle_unsol_lock_entity_milanv12(struct aecp *aecp, struct descripto } +void handle_cmd_lock_entity_expired_milan_v12(struct aecp *aecp, int64_t now) +{ + struct server *server = aecp->server; + struct descriptor *desc; + struct aecp_aem_entity_milan_state *entity_state; + struct aecp_aem_lock_state *lock; + + desc = server_find_descriptor(server, AVB_AEM_DESC_ENTITY, 0); + if (desc == NULL) + return; + + entity_state = desc->ptr; + lock = &entity_state->state.lock_state; + + if (!lock->is_locked) + return; + + if (lock->base_info.expire_timeout >= now) + return; + + pw_log_info("entity lock held by %" PRIx64 " expired after %lus, releasing", + lock->locked_id, AECP_AEM_LOCK_ENTITY_EXPIRE_TIMEOUT_SECOND); + + lock->is_locked = false; + lock->locked_id = 0; + /* + * No specific triggering controller (this is a timeout, not a command). + * Setting controller_entity_id to 0 combined with internal=true ensures + * reply_unsol_send notifies ALL registered controllers, including the + * one whose lock just expired. + */ + lock->base_info.controller_entity_id = 0; + handle_unsol_lock_common(aecp, lock, true); +} + /* LOCK_ENTITY */ /* Milan v1.2, Sec. 5.4.2.2; IEEE 1722.1-2021, Sec. 7.4.2*/ int handle_cmd_lock_entity_milan_v12(struct aecp *aecp, int64_t now, const void *m, int len) diff --git a/src/modules/module-avb/aecp-aem-cmds-resps/cmd-lock-entity.h b/src/modules/module-avb/aecp-aem-cmds-resps/cmd-lock-entity.h index dee877a7f..c83bc464d 100644 --- a/src/modules/module-avb/aecp-aem-cmds-resps/cmd-lock-entity.h +++ b/src/modules/module-avb/aecp-aem-cmds-resps/cmd-lock-entity.h @@ -12,6 +12,15 @@ #include +/** + * @brief Checks whether the Milan entity lock has expired and releases it. + * + * Called once per second from the AECP periodic handler. If the lock is + * active and its timeout (set at lock time) is earlier than @p now, the + * lock is cleared. + */ +void handle_cmd_lock_entity_expired_milan_v12(struct aecp *aecp, int64_t now); + /** * @brief Command handling will generate the response for the lock command */ diff --git a/src/modules/module-avb/aecp-aem.c b/src/modules/module-avb/aecp-aem.c index e655f02c5..78b3eb818 100644 --- a/src/modules/module-avb/aecp-aem.c +++ b/src/modules/module-avb/aecp-aem.c @@ -5,6 +5,7 @@ #include "aecp-aem.h" #include "aecp-aem-descriptors.h" +#include "aecp-aem-state.h" #include "aecp-aem-cmds-resps/cmd-resp-helpers.h" #include "utils.h" @@ -370,6 +371,44 @@ static const struct { }, }; +/** + * \brief Stub that queries the AECP entity lock state. + * + * Returns true when the entity is currently locked by a *different* controller + * than the one sending the command, meaning the command must be rejected with + * ENTITY_LOCKED. Returns false in all other cases (not locked, lock expired, + * or requester is the lock owner). + * + * Only Milan V1.2 entities maintain a lock state; legacy AVB entities always + * return false (unlocked). + */ +static bool check_locked(struct aecp *aecp, int64_t now, + const struct avb_packet_aecp_aem *p) +{ + struct server *server = aecp->server; + const struct descriptor *desc; + const struct aecp_aem_entity_milan_state *entity_state; + const struct aecp_aem_lock_state *lock; + + if (server->avb_mode != AVB_MODE_MILAN_V12) + return false; + + desc = server_find_descriptor(server, AVB_AEM_DESC_ENTITY, 0); + if (desc == NULL) + return false; + + entity_state = desc->ptr; + lock = &entity_state->state.lock_state; + + /* A lock that has expired is treated as if the entity is unlocked. */ + if (lock->base_info.expire_timeout < now) + return false; + + /* Locked by a different controller → reject. */ + return lock->is_locked && + (lock->locked_id != htobe64(p->aecp.controller_guid)); +} + int avb_aecp_aem_handle_command(struct aecp *aecp, const void *m, int len) { const struct avb_ethernet_header *h = m; @@ -402,9 +441,30 @@ int avb_aecp_aem_handle_command(struct aecp *aecp, const void *m, int len) now = SPA_TIMESPEC_TO_NSEC(&ts_now); + /* + * For write (non-readonly) commands, check whether the entity is locked + * by a different controller before dispatching the handler. Readonly + * commands are always allowed regardless of lock state. + */ + if (!info->is_readonly && check_locked(aecp, now, p)) { + pw_log_info("aem command %s rejected: entity locked", + cmd_names[cmd_type]); + return reply_entity_locked(aecp, m, len); + } + return info->handle_command(aecp, now, m, len); } +void avb_aecp_aem_periodic(struct aecp *aecp, int64_t now) +{ + struct server *server = aecp->server; + + if (server->avb_mode != AVB_MODE_MILAN_V12) + return; + + handle_cmd_lock_entity_expired_milan_v12(aecp, now); +} + int avb_aecp_aem_handle_response(struct aecp *aecp, const void *m, int len) { return 0; diff --git a/src/modules/module-avb/aecp-aem.h b/src/modules/module-avb/aecp-aem.h index 507d0f868..e4e120452 100644 --- a/src/modules/module-avb/aecp-aem.h +++ b/src/modules/module-avb/aecp-aem.h @@ -249,5 +249,6 @@ struct avb_packet_aecp_aem { int avb_aecp_aem_handle_command(struct aecp *aecp, const void *m, int len); int avb_aecp_aem_handle_response(struct aecp *aecp, const void *m, int len); +void avb_aecp_aem_periodic(struct aecp *aecp, int64_t now); #endif /* AVB_AEM_H */ diff --git a/src/modules/module-avb/aecp.c b/src/modules/module-avb/aecp.c index 6e6a1ba43..6a15f87a9 100644 --- a/src/modules/module-avb/aecp.c +++ b/src/modules/module-avb/aecp.c @@ -86,6 +86,12 @@ static int aecp_message(void *data, uint64_t now, const void *message, int len) return info->handle(aecp, message, len); } +static void aecp_periodic(void *data, uint64_t now) +{ + struct aecp *aecp = data; + avb_aecp_aem_periodic(aecp, (int64_t)now); +} + static void aecp_destroy(void *data) { struct aecp *aecp = data; @@ -124,6 +130,7 @@ static const struct server_events server_events = { AVB_VERSION_SERVER_EVENTS, .destroy = aecp_destroy, .message = aecp_message, + .periodic = aecp_periodic, .command = aecp_command };