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.
This commit is contained in:
hackerman-kl 2026-04-16 08:10:14 +02:00 committed by hackerman-kl
parent adad89dc0e
commit c551acf4d1
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 */
/* 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)

View file

@ -12,6 +12,15 @@
#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
*/

View file

@ -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;

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_response(struct aecp *aecp, const void *m, int len);
void avb_aecp_aem_periodic(struct aecp *aecp, int64_t now);
#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);
}
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
};