Merge branch '5017-lock-make-it-effective' into 'master'

milan-avb: lock: ensure correct behavior

See merge request pipewire/pipewire!2792
This commit is contained in:
hackerman-kl 2026-04-16 12:42:27 +02:00
commit c651001882
5 changed files with 112 additions and 0 deletions

View file

@ -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 */ /* LOCK_ENTITY */
/* Milan v1.2, Sec. 5.4.2.2; IEEE 1722.1-2021, Sec. 7.4.2*/ /* 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) int handle_cmd_lock_entity_milan_v12(struct aecp *aecp, int64_t now, const void *m, int len)

View file

@ -12,6 +12,15 @@
#include <stdint.h> #include <stdint.h>
/**
* @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 * @brief Command handling will generate the response for the lock command
*/ */

View file

@ -5,6 +5,7 @@
#include "aecp-aem.h" #include "aecp-aem.h"
#include "aecp-aem-descriptors.h" #include "aecp-aem-descriptors.h"
#include "aecp-aem-state.h"
#include "aecp-aem-cmds-resps/cmd-resp-helpers.h" #include "aecp-aem-cmds-resps/cmd-resp-helpers.h"
#include "utils.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) int avb_aecp_aem_handle_command(struct aecp *aecp, const void *m, int len)
{ {
const struct avb_ethernet_header *h = m; 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); 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); 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) int avb_aecp_aem_handle_response(struct aecp *aecp, const void *m, int len)
{ {
return 0; return 0;

View file

@ -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_command(struct aecp *aecp, const void *m, int len);
int avb_aecp_aem_handle_response(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 */ #endif /* AVB_AEM_H */

View file

@ -86,6 +86,12 @@ static int aecp_message(void *data, uint64_t now, const void *message, int len)
return info->handle(aecp, message, 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) static void aecp_destroy(void *data)
{ {
struct aecp *aecp = data; struct aecp *aecp = data;
@ -124,6 +130,7 @@ static const struct server_events server_events = {
AVB_VERSION_SERVER_EVENTS, AVB_VERSION_SERVER_EVENTS,
.destroy = aecp_destroy, .destroy = aecp_destroy,
.message = aecp_message, .message = aecp_message,
.periodic = aecp_periodic,
.command = aecp_command .command = aecp_command
}; };