From 11560318a02791bfa4731e457b7d5cda2a78d21d Mon Sep 17 00:00:00 2001 From: Faizan Ali Date: Wed, 23 Apr 2025 01:23:36 +0530 Subject: [PATCH 01/29] Add ALLOCATE_ENDPOINT_ID control message support Add MCTP control message structures for the ALLOCATE_ENDPOINT_ID command to support bridge endpoint EID pool allocation. Signed-off-by: Faizan Ali --- src/mctp-control-spec.h | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/mctp-control-spec.h b/src/mctp-control-spec.h index e3f2f62..d1293f8 100644 --- a/src/mctp-control-spec.h +++ b/src/mctp-control-spec.h @@ -183,6 +183,28 @@ struct mctp_ctrl_resp_resolve_endpoint_id { // ... uint8_t physical_address[N] } __attribute__((__packed__)); +typedef enum { + mctp_ctrl_cmd_alloc_eid_alloc_eid = 0, + mctp_ctrl_cmd_alloc_eid_force_alloc = 1, + mctp_ctrl_cmd_alloc_eid_get_alloc_info = 2, + mctp_ctrl_cmd_alloc_eid_reserved = 3 +} mctp_ctrl_cmd_alloc_eid_op; + +struct mctp_ctrl_cmd_alloc_eid { + struct mctp_ctrl_msg_hdr ctrl_hdr; + uint8_t alloc_eid_op; + uint8_t pool_size; + uint8_t start_eid; +} __attribute__((__packed__)); + +struct mctp_ctrl_resp_alloc_eid { + struct mctp_ctrl_msg_hdr ctrl_hdr; + uint8_t completion_code; + uint8_t status; + uint8_t eid_pool_size; + uint8_t eid_set; +} __attribute__((__packed__)); + #define MCTP_CTRL_HDR_MSG_TYPE 0 #define MCTP_CTRL_HDR_FLAG_REQUEST (1 << 7) #define MCTP_CTRL_HDR_FLAG_DGRAM (1 << 6) From 51a23b46e5d2aea45b071d6ae73f401f857c9323 Mon Sep 17 00:00:00 2001 From: Jeremy Kerr Date: Fri, 22 Aug 2025 10:00:22 +0800 Subject: [PATCH 02/29] mctp-control-spec: clarify naming for allocate endpoint IDs defintions The names are currently 'alloc_eid', whereas the command is specified as "Allocate Endpoint IDs". The plural is important, as it references the pool allocation, rather than a single EID allocation (which might imply non-bridged endpoints). Use the plural form instead. Also, don't abbreviate 'allocate', to better match existing command definitions. Signed-off-by: Jeremy Kerr --- src/mctp-control-spec.h | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/mctp-control-spec.h b/src/mctp-control-spec.h index d1293f8..db6e004 100644 --- a/src/mctp-control-spec.h +++ b/src/mctp-control-spec.h @@ -184,20 +184,20 @@ struct mctp_ctrl_resp_resolve_endpoint_id { } __attribute__((__packed__)); typedef enum { - mctp_ctrl_cmd_alloc_eid_alloc_eid = 0, - mctp_ctrl_cmd_alloc_eid_force_alloc = 1, - mctp_ctrl_cmd_alloc_eid_get_alloc_info = 2, - mctp_ctrl_cmd_alloc_eid_reserved = 3 -} mctp_ctrl_cmd_alloc_eid_op; + mctp_ctrl_cmd_allocate_eids_alloc_eids = 0, + mctp_ctrl_cmd_allocate_eids_force_alloc = 1, + mctp_ctrl_cmd_allocate_eids_get_alloc_info = 2, + mctp_ctrl_cmd_allocate_eids_reserved = 3 +} mctp_ctrl_cmd_allocate_eids_op; -struct mctp_ctrl_cmd_alloc_eid { +struct mctp_ctrl_cmd_allocate_eids { struct mctp_ctrl_msg_hdr ctrl_hdr; uint8_t alloc_eid_op; uint8_t pool_size; uint8_t start_eid; } __attribute__((__packed__)); -struct mctp_ctrl_resp_alloc_eid { +struct mctp_ctrl_resp_allocate_eids { struct mctp_ctrl_msg_hdr ctrl_hdr; uint8_t completion_code; uint8_t status; From a6529a7091f15d340c11454e1dc48de0ea79c40d Mon Sep 17 00:00:00 2001 From: Faizan Ali Date: Thu, 24 Apr 2025 01:55:51 +0530 Subject: [PATCH 03/29] Add mctp bridge endpoint support Add support for MCTP bridge endpoints that can allocate pools of EIDs for downstream endpoints. We assume each AssignEndpoint d-bus call will be for an MCTP bridge, with this we allocate/reserve a max_pool_size eid range contiguous to bridge's own eid. Later this pool size is updated based on SET_ENDPOINT_ID command response. - for static eid assignment via AssignEndpointStatic d-bus call, add check if eid is part of any other bridge's pool range. [Fixup and requested change from Jeremy Kerr ] Signed-off-by: Faizan Ali Signed-off-by: Jeremy Kerr --- src/mctpd.c | 184 +++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 155 insertions(+), 29 deletions(-) diff --git a/src/mctpd.c b/src/mctpd.c index d3ae63c..b2144fe 100644 --- a/src/mctpd.c +++ b/src/mctpd.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -185,6 +186,10 @@ struct peer { uint8_t endpoint_type; uint8_t medium_spec; } recovery; + + // Pool size + uint8_t pool_size; + uint8_t pool_start; }; struct ctx { @@ -232,7 +237,7 @@ static int emit_interface_removed(struct link *link); static int emit_net_added(struct ctx *ctx, struct net *net); static int emit_net_removed(struct ctx *ctx, struct net *net); static int add_peer(struct ctx *ctx, const dest_phys *dest, mctp_eid_t eid, - uint32_t net, struct peer **ret_peer); + uint32_t net, struct peer **ret_peer, bool net_learn); static int add_peer_from_addr(struct ctx *ctx, const struct sockaddr_mctp_ext *addr, struct peer **ret_peer); @@ -1484,7 +1489,7 @@ static int endpoint_query_phys(struct ctx *ctx, const dest_phys *dest, } /* returns -ECONNREFUSED if the endpoint returns failure. */ -static int endpoint_send_set_endpoint_id(const struct peer *peer, +static int endpoint_send_set_endpoint_id(struct peer *peer, mctp_eid_t *new_eidp) { struct sockaddr_mctp_ext addr; @@ -1552,9 +1557,20 @@ static int endpoint_send_set_endpoint_id(const struct peer *peer, alloc = resp->status & 0x3; if (alloc != 0) { - // TODO for bridges - warnx("%s requested allocation pool, unimplemented", - dest_phys_tostr(dest)); + peer->pool_size = resp->eid_pool_size; + if (peer->ctx->verbose) { + fprintf(stderr, + "%s requested allocation of pool size = %d\n", + dest_phys_tostr(dest), peer->pool_size); + } + if (peer->pool_size > peer->ctx->max_pool_size) { + warnx("Truncate: requested pool size > max pool size config"); + peer->pool_size = peer->ctx->max_pool_size; + } + } else { + // reset previous assumed pool + peer->pool_size = 0; + peer->pool_start = 0; } rc = 0; @@ -1563,10 +1579,27 @@ static int endpoint_send_set_endpoint_id(const struct peer *peer, return rc; } +// Checks if given EID belongs to any bridge's pool range +static bool is_eid_in_bridge_pool(struct net *n, struct ctx *ctx, + mctp_eid_t eid) +{ + for (int i = ctx->dyn_eid_min; i <= eid; i++) { + struct peer *peer = n->peers[i]; + if (peer && peer->pool_size > 0) { + if (eid >= peer->pool_start && + eid < peer->pool_start + peer->pool_size) { + return true; + } + i += peer->pool_size; + } + } + return false; +} + /* Returns the newly added peer. * Error is -EEXISTS if it exists */ static int add_peer(struct ctx *ctx, const dest_phys *dest, mctp_eid_t eid, - uint32_t net, struct peer **ret_peer) + uint32_t net, struct peer **ret_peer, bool net_learn) { struct peer *peer, **tmp; struct net *n; @@ -1584,6 +1617,14 @@ static int add_peer(struct ctx *ctx, const dest_phys *dest, mctp_eid_t eid, } *ret_peer = peer; return 0; + } else { + /* only LearnEndpoint methods of au.com.codeconstruct.MCTP.Network1 + * interface will approve peer structure if eid belongs to a bridge + * pool space else never allow. + */ + if (!net_learn & is_eid_in_bridge_pool(n, ctx, eid)) { + return -EADDRNOTAVAIL; + } } if (ctx->num_peers == MAX_PEER_SIZE) @@ -1627,7 +1668,7 @@ static int add_peer_from_addr(struct ctx *ctx, phys.hwaddr_len = addr->smctp_halen; return add_peer(ctx, &phys, addr->smctp_base.smctp_addr.s_addr, - addr->smctp_base.smctp_network, ret_peer); + addr->smctp_base.smctp_network, ret_peer, true); } static int check_peer_struct(const struct peer *peer, const struct net *n) @@ -1797,11 +1838,66 @@ static int peer_set_mtu(struct ctx *ctx, struct peer *peer, uint32_t mtu) return rc; } +struct eid_allocation { + mctp_eid_t start; + unsigned int extent; /* 0 = only the start EID */ +}; + +/* Allocate an unused dynamic EID for a peer, optionally with an associated + * bridge range (of size @bridged_len). + * + * We try to find the first allocation that contains the base EID plus the + * full range. If no space for that exists, we return the largest + * possible range. If the requested range is 0, then the first available + * (single) EID will suit as a match, the returned alloc->extent will be zero. + * + * It is up to the caller to check whether this range is suitable, and + * actually reserve that EID (& range) if so. + * + * returns 0 on success (with @alloc populated), non-zero on failure. + */ +static int allocate_eid(struct ctx *ctx, struct net *net, + unsigned int bridged_len, struct eid_allocation *alloc) +{ + struct eid_allocation cur = { 0 }, best = { 0 }; + mctp_eid_t eid; + + for (eid = ctx->dyn_eid_min; eid <= ctx->dyn_eid_max; eid++) { + if (net->peers[eid]) { + // reset our current candidate allocation + cur.start = 0; + eid += net->peers[eid]->pool_size; + continue; + } + + // start a new candidate allocation + if (!cur.start) + cur.start = eid; + cur.extent = eid - cur.start; + + // if this suits, we're done + if (cur.extent == bridged_len) { + *alloc = cur; + return 0; + } + + if (cur.extent > best.extent) + best = cur; + } + + if (best.start) { + *alloc = best; + return 0; + } + + return -1; +} + static int endpoint_assign_eid(struct ctx *ctx, sd_bus_error *berr, const dest_phys *dest, struct peer **ret_peer, - mctp_eid_t static_eid) + mctp_eid_t static_eid, bool assign_bridge) { - mctp_eid_t e, new_eid; + mctp_eid_t new_eid; struct net *n = NULL; struct peer *peer = NULL; uint32_t net; @@ -1820,28 +1916,49 @@ static int endpoint_assign_eid(struct ctx *ctx, sd_bus_error *berr, } if (static_eid) { - rc = add_peer(ctx, dest, static_eid, net, &peer); + rc = add_peer(ctx, dest, static_eid, net, &peer, false); if (rc < 0) return rc; new_eid = static_eid; } else { - /* Find an unused dynamic EID */ - for (e = ctx->dyn_eid_min; e <= ctx->dyn_eid_max; e++) { - if (n->peers[e]) - continue; - rc = add_peer(ctx, dest, e, net, &peer); - if (rc < 0) - return rc; - break; - } - if (e > ctx->dyn_eid_max) { - warnx("Ran out of EIDs for net %d, allocating %s", net, - dest_phys_tostr(dest)); + struct eid_allocation alloc; + unsigned int alloc_size = 0; + + if (assign_bridge) + alloc_size = ctx->max_pool_size; + + rc = allocate_eid(ctx, n, alloc_size, &alloc); + if (rc) { + warnx("Cannot allocate any EID (+pool %d) on net %d for %s", + alloc_size, net, dest_phys_tostr(dest)); sd_bus_error_setf(berr, SD_BUS_ERROR_FAILED, "Ran out of EIDs"); return -EADDRNOTAVAIL; } + + /* Only allow complete pools for now. In future we could reserve + * this range, in the assumption that the subsequent pool + * request (in the Set Endpoint ID response) will fit in this + * reservation. + */ + if (alloc.extent < alloc_size) { + warnx("Cannot allocate sufficient EIDs (+pool %d) on net %d for %s" + " (largest span %d at %d)", + alloc_size, net, dest_phys_tostr(dest), + alloc.extent, alloc.start); + alloc.extent = 0; + } + + new_eid = alloc.start; + + rc = add_peer(ctx, dest, new_eid, net, &peer, false); + if (rc < 0) + return rc; + + peer->pool_size = alloc.extent; + if (peer->pool_size) + peer->pool_start = new_eid + 1; } /* Add a route to the peer prior to assigning it an EID. @@ -1860,6 +1977,10 @@ static int endpoint_assign_eid(struct ctx *ctx, sd_bus_error *berr, } if (new_eid != peer->eid) { + // avoid allocation for any different EID in response + warnx("Mismatch of requested from received EID, resetting the pool"); + peer->pool_size = 0; + peer->pool_start = 0; rc = change_peer_eid(peer, new_eid); if (rc == -EEXIST) { sd_bus_error_setf( @@ -2023,7 +2144,7 @@ static int get_endpoint_peer(struct ctx *ctx, sd_bus_error *berr, return 0; } /* New endpoint */ - rc = add_peer(ctx, dest, eid, net, &peer); + rc = add_peer(ctx, dest, eid, net, &peer, false); if (rc < 0) return rc; } @@ -2262,7 +2383,7 @@ static int method_setup_endpoint(sd_bus_message *call, void *data, } /* Set Endpoint ID */ - rc = endpoint_assign_eid(ctx, berr, dest, &peer, 0); + rc = endpoint_assign_eid(ctx, berr, dest, &peer, 0, false); if (rc < 0) goto err; @@ -2315,7 +2436,7 @@ static int method_assign_endpoint(sd_bus_message *call, void *data, peer->net, peer_path, 0); } - rc = endpoint_assign_eid(ctx, berr, dest, &peer, 0); + rc = endpoint_assign_eid(ctx, berr, dest, &peer, 0, true); if (rc < 0) goto err; @@ -2323,6 +2444,10 @@ static int method_assign_endpoint(sd_bus_message *call, void *data, if (!peer_path) goto err; + if (peer->pool_size > 0) { + //TODO: Implement Allocate EndpointID + } + return sd_bus_reply_method_return(call, "yisb", peer->eid, peer->net, peer_path, 1); err: @@ -2387,7 +2512,7 @@ static int method_assign_endpoint_static(sd_bus_message *call, void *data, } } - rc = endpoint_assign_eid(ctx, berr, dest, &peer, eid); + rc = endpoint_assign_eid(ctx, berr, dest, &peer, eid, false); if (rc < 0) { goto err; } @@ -2796,7 +2921,8 @@ static int peer_endpoint_recover(sd_event_source *s, uint64_t usec, * after which we immediately return as there's no old peer state left to * maintain. */ - return endpoint_assign_eid(ctx, NULL, &phys, &peer, 0); + return endpoint_assign_eid(ctx, NULL, &phys, &peer, 0, + false); } /* Confirmation of the same device, apply its already allocated EID */ @@ -2957,7 +3083,7 @@ static int method_net_learn_endpoint(sd_bus_message *call, void *data, return sd_bus_reply_method_return(call, "sb", path_from_peer(peer), false); - rc = add_peer(ctx, &dest, eid, net->net, &peer); + rc = add_peer(ctx, &dest, eid, net->net, &peer, true); if (rc) { warnx("can't add peer: %s", strerror(-rc)); goto err; @@ -3758,7 +3884,7 @@ static int add_local_eid(struct ctx *ctx, uint32_t net, int eid) } } - rc = add_peer(ctx, &local_phys, eid, net, &peer); + rc = add_peer(ctx, &local_phys, eid, net, &peer, false); if (rc < 0) { bug_warn("Error adding local eid %d net %d", eid, net); return rc; From 0a01694c067f5ab30c7e7ecaa32690b1f3efafeb Mon Sep 17 00:00:00 2001 From: Jeremy Kerr Date: Thu, 21 Aug 2025 13:56:13 +0800 Subject: [PATCH 04/29] mctpd: fix EID allocation when only single-EID ranges are present While walking through the available EID space, we update the 'best-fit' allocation if the current extent is larger than the current best-fit. However, if the only remaining allocations all have an extent of zero, we will never populate the best-fit range. Add a start case for the best-fit: the first single-eid allocation we find. Signed-off-by: Jeremy Kerr --- src/mctpd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mctpd.c b/src/mctpd.c index b2144fe..9da0b16 100644 --- a/src/mctpd.c +++ b/src/mctpd.c @@ -1881,7 +1881,7 @@ static int allocate_eid(struct ctx *ctx, struct net *net, return 0; } - if (cur.extent > best.extent) + if (cur.extent > best.extent || !best.start) best = cur; } From 4ebfd231e3761a09d2ef063b5388918f17f6c116 Mon Sep 17 00:00:00 2001 From: Jeremy Kerr Date: Thu, 21 Aug 2025 11:44:37 +0800 Subject: [PATCH 05/29] mctpd: endpoint_send_set_endpoint_id should only send a Set Endpoint ID command An earlier change modified endpoint_send_set_endpoint_id() to also modify the peer state. That's not really the intention of this function - we should only be performing the command - hence the const peer pointer. Instead, have the requested pool size returned as an out arg, for the caller to handle. This means we can keep all of the pool logic in the one place in the caller. Signed-off-by: Jeremy Kerr --- src/mctpd.c | 44 ++++++++++++++++++++++++++++---------------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/src/mctpd.c b/src/mctpd.c index 9da0b16..fbb811a 100644 --- a/src/mctpd.c +++ b/src/mctpd.c @@ -1488,9 +1488,14 @@ static int endpoint_query_phys(struct ctx *ctx, const dest_phys *dest, resp_len, resp_addr); } -/* returns -ECONNREFUSED if the endpoint returns failure. */ -static int endpoint_send_set_endpoint_id(struct peer *peer, - mctp_eid_t *new_eidp) +/* returns -ECONNREFUSED if the endpoint returns failure. + * + * Returns new EID (in @new_eidp) and the requested pool size (provided in the + * Set Endpoint ID reponse, in @req_pool_size) on success. + */ +static int endpoint_send_set_endpoint_id(const struct peer *peer, + mctp_eid_t *new_eidp, + uint8_t *req_pool_size) { struct sockaddr_mctp_ext addr; struct mctp_ctrl_cmd_set_eid req = { 0 }; @@ -1498,7 +1503,7 @@ static int endpoint_send_set_endpoint_id(struct peer *peer, int rc; uint8_t *buf = NULL; size_t buf_size; - uint8_t iid, stat, alloc; + uint8_t iid, stat, alloc, pool_size = 0; const dest_phys *dest = &peer->phys; mctp_eid_t new_eid; @@ -1557,22 +1562,17 @@ static int endpoint_send_set_endpoint_id(struct peer *peer, alloc = resp->status & 0x3; if (alloc != 0) { - peer->pool_size = resp->eid_pool_size; + pool_size = resp->eid_pool_size; if (peer->ctx->verbose) { fprintf(stderr, "%s requested allocation of pool size = %d\n", - dest_phys_tostr(dest), peer->pool_size); - } - if (peer->pool_size > peer->ctx->max_pool_size) { - warnx("Truncate: requested pool size > max pool size config"); - peer->pool_size = peer->ctx->max_pool_size; + dest_phys_tostr(dest), pool_size); } - } else { - // reset previous assumed pool - peer->pool_size = 0; - peer->pool_start = 0; } + if (req_pool_size) + *req_pool_size = pool_size; + rc = 0; out: free(buf); @@ -1900,6 +1900,7 @@ static int endpoint_assign_eid(struct ctx *ctx, sd_bus_error *berr, mctp_eid_t new_eid; struct net *n = NULL; struct peer *peer = NULL; + uint8_t req_pool_size; uint32_t net; int rc; @@ -1966,7 +1967,7 @@ static int endpoint_assign_eid(struct ctx *ctx, sd_bus_error *berr, * it should be routable. */ add_peer_route(peer); - rc = endpoint_send_set_endpoint_id(peer, &new_eid); + rc = endpoint_send_set_endpoint_id(peer, &new_eid, &req_pool_size); if (rc == -ECONNREFUSED) sd_bus_error_setf( berr, SD_BUS_ERROR_FAILED, @@ -1976,6 +1977,17 @@ static int endpoint_assign_eid(struct ctx *ctx, sd_bus_error *berr, return rc; } + if (req_pool_size > peer->pool_size) { + warnx("EID %d: requested pool size (%d) > pool size available (%d)", + peer->eid, req_pool_size, peer->pool_size); + req_pool_size = peer->pool_size; + } + // peer will likely have requested less than the available range + peer->pool_size = req_pool_size; + + if (!peer->pool_size) + peer->pool_start = 0; + if (new_eid != peer->eid) { // avoid allocation for any different EID in response warnx("Mismatch of requested from received EID, resetting the pool"); @@ -2926,7 +2938,7 @@ static int peer_endpoint_recover(sd_event_source *s, uint64_t usec, } /* Confirmation of the same device, apply its already allocated EID */ - rc = endpoint_send_set_endpoint_id(peer, &new_eid); + rc = endpoint_send_set_endpoint_id(peer, &new_eid, NULL); if (rc < 0) { goto reschedule; } From 56f1c969665626dc80fbe5604573015145f4dc2b Mon Sep 17 00:00:00 2001 From: Faizan Ali Date: Thu, 24 Apr 2025 17:57:25 +0530 Subject: [PATCH 06/29] Implement ALLOCATE_ENDPOINT_ID support for bridges Add implementation for the MCTP ALLOCATE_ENDPOINT_ID control command to enable bridges to allocate EID pools for downstream endpoints. Update gateway route for downstream EIDs [Minor change by Jeremy Kerr : use renamed allocate_eids definitions] Signed-off-by: Faizan Ali --- src/mctpd.c | 148 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 147 insertions(+), 1 deletion(-) diff --git a/src/mctpd.c b/src/mctpd.c index fbb811a..1008113 100644 --- a/src/mctpd.c +++ b/src/mctpd.c @@ -259,6 +259,7 @@ static int del_local_eid(struct ctx *ctx, uint32_t net, int eid); static int add_net(struct ctx *ctx, uint32_t net); static void del_net(struct net *net); static int add_interface(struct ctx *ctx, int ifindex); +static int endpoint_allocate_eid(struct peer *peer); static const sd_bus_vtable bus_endpoint_obmc_vtable[]; static const sd_bus_vtable bus_endpoint_cc_vtable[]; @@ -2457,7 +2458,18 @@ static int method_assign_endpoint(sd_bus_message *call, void *data, goto err; if (peer->pool_size > 0) { - //TODO: Implement Allocate EndpointID + rc = endpoint_allocate_eid(peer); + if (rc < 0) { + warnx("Failed to allocate downstream EIDs"); + } else { + if (peer->ctx->verbose) { + fprintf(stderr, + "Downstream EIDs assigned from %d to %d : pool size %d\n", + peer->pool_start, + peer->pool_start + peer->pool_size - 1, + peer->pool_size); + } + } } return sd_bus_reply_method_return(call, "yisb", peer->eid, peer->net, @@ -2652,6 +2664,20 @@ static int peer_route_update(struct peer *peer, uint16_t type) return mctp_nl_route_add(peer->ctx->nl, peer->eid, 0, peer->phys.ifindex, NULL, peer->mtu); } else if (type == RTM_DELROUTE) { + if (peer->pool_size > 0) { + int rc = 0; + struct mctp_fq_addr gw_addr = { 0 }; + gw_addr.net = peer->net; + gw_addr.eid = peer->eid; + rc = mctp_nl_route_del(peer->ctx->nl, peer->pool_start, + peer->pool_size - 1, + peer->phys.ifindex, &gw_addr); + if (rc < 0) + warnx("failed to delete route for peer pool eids %d-%d %s", + peer->pool_start, + peer->pool_start + peer->pool_size - 1, + strerror(-rc)); + } return mctp_nl_route_del(peer->ctx->nl, peer->eid, 0, peer->phys.ifindex, NULL); } @@ -4378,6 +4404,126 @@ static void free_config(struct ctx *ctx) free(ctx->config_filename); } +static int endpoint_send_allocate_endpoint_id(struct peer *peer, + mctp_eid_t eid_start, + uint8_t eid_pool_size, + mctp_ctrl_cmd_allocate_eids_op op, + uint8_t *allocated_pool_size, + mctp_eid_t *allocated_pool_start) +{ + struct sockaddr_mctp_ext addr; + struct mctp_ctrl_cmd_allocate_eids req = { 0 }; + struct mctp_ctrl_resp_allocate_eids *resp = NULL; + uint8_t *buf = NULL; + size_t buf_size; + uint8_t iid, stat; + int rc; + + iid = mctp_next_iid(peer->ctx); + mctp_ctrl_msg_hdr_init_req(&req.ctrl_hdr, iid, + MCTP_CTRL_CMD_ALLOCATE_ENDPOINT_IDS); + req.alloc_eid_op = (uint8_t)(op & 0x03); + req.pool_size = eid_pool_size; + req.start_eid = eid_start; + rc = endpoint_query_peer(peer, MCTP_CTRL_HDR_MSG_TYPE, &req, + sizeof(req), &buf, &buf_size, &addr); + if (rc < 0) + goto out; + + rc = mctp_ctrl_validate_response(buf, buf_size, sizeof(*resp), + peer_tostr_short(peer), iid, + MCTP_CTRL_CMD_ALLOCATE_ENDPOINT_IDS); + + if (rc) + goto out; + + resp = (void *)buf; + if (!resp) { + warnx("Invalid response buffer"); + return -ENOMEM; + } + + stat = resp->status & 0x03; + if (stat == 0x00) { + if (peer->ctx->verbose) { + fprintf(stderr, "Allocation accepted\n"); + } + if (resp->eid_pool_size != eid_pool_size || + resp->eid_set != eid_start) { + warnx("Unexpected pool start %d pool size %d", + resp->eid_set, resp->eid_pool_size); + rc = -1; + goto out; + } + *allocated_pool_size = eid_pool_size; + *allocated_pool_start = eid_start; + } else { + if (stat == 0x1) + warnx("Allocation was rejected: already allocated by other bus" + " pool size %d, pool start %d", + resp->eid_pool_size, resp->eid_set); + rc = -1; + goto out; + } + + if (peer->ctx->verbose) { + fprintf(stderr, "Allocated size of %d, starting from EID %d\n", + resp->eid_pool_size, resp->eid_set); + } + +out: + free(buf); + return rc; +} + +static int endpoint_allocate_eid(struct peer *peer) +{ + uint8_t allocated_pool_size = 0; + mctp_eid_t allocated_pool_start = 0; + int rc = 0; + + if (peer->pool_start >= peer->ctx->dyn_eid_max || + peer->pool_start <= 0) { + warnx("Invalid pool start %d", peer->pool_start); + return -1; + } + rc = endpoint_send_allocate_endpoint_id( + peer, peer->pool_start, peer->pool_size, + mctp_ctrl_cmd_allocate_eids_alloc_eids, &allocated_pool_size, + &allocated_pool_start); + if (rc) { + //reset peer pool + peer->pool_size = 0; + peer->pool_start = 0; + return rc; + } + peer->pool_size = allocated_pool_size; + peer->pool_start = allocated_pool_start; + + // add gateway route for all bridge's downstream eids + if (peer->pool_size > 0) { + struct mctp_fq_addr gw_addr = { 0 }; + gw_addr.net = peer->net; + gw_addr.eid = peer->eid; + rc = mctp_nl_route_add(peer->ctx->nl, peer->pool_start, + peer->pool_size - 1, peer->phys.ifindex, + &gw_addr, peer->mtu); + if (rc < 0) { + warnx("Failed to add gateway route for EID %d: %s", + gw_addr.eid, strerror(-rc)); + // If the route already exists, continue polling + if (rc == -EEXIST) { + rc = 0; + } else { + return rc; + } + } + // TODO: Polling logic for downstream EID + } + + return rc; +} + int main(int argc, char **argv) { struct ctx ctxi = { 0 }, *ctx = &ctxi; From 1668942f2efed9bad9d657b12e54226aac1e8445 Mon Sep 17 00:00:00 2001 From: Jeremy Kerr Date: Thu, 21 Aug 2025 12:01:30 +0800 Subject: [PATCH 07/29] mctpd: minor endpoint_allocate_eid fixups Just check pool_start against a valid address; we would have already used the dyn range in the allocation. Return early on no pool, instead of a large if-block. Signed-off-by: Jeremy Kerr --- src/mctpd.c | 38 +++++++++++++++++--------------------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/src/mctpd.c b/src/mctpd.c index 1008113..f0d2099 100644 --- a/src/mctpd.c +++ b/src/mctpd.c @@ -4482,8 +4482,7 @@ static int endpoint_allocate_eid(struct peer *peer) mctp_eid_t allocated_pool_start = 0; int rc = 0; - if (peer->pool_start >= peer->ctx->dyn_eid_max || - peer->pool_start <= 0) { + if (!mctp_eid_is_valid_unicast(peer->pool_start)) { warnx("Invalid pool start %d", peer->pool_start); return -1; } @@ -4500,28 +4499,25 @@ static int endpoint_allocate_eid(struct peer *peer) peer->pool_size = allocated_pool_size; peer->pool_start = allocated_pool_start; + if (!peer->pool_size) + return 0; + // add gateway route for all bridge's downstream eids - if (peer->pool_size > 0) { - struct mctp_fq_addr gw_addr = { 0 }; - gw_addr.net = peer->net; - gw_addr.eid = peer->eid; - rc = mctp_nl_route_add(peer->ctx->nl, peer->pool_start, - peer->pool_size - 1, peer->phys.ifindex, - &gw_addr, peer->mtu); - if (rc < 0) { - warnx("Failed to add gateway route for EID %d: %s", - gw_addr.eid, strerror(-rc)); - // If the route already exists, continue polling - if (rc == -EEXIST) { - rc = 0; - } else { - return rc; - } - } - // TODO: Polling logic for downstream EID + struct mctp_fq_addr gw_addr = { 0 }; + gw_addr.net = peer->net; + gw_addr.eid = peer->eid; + rc = mctp_nl_route_add(peer->ctx->nl, peer->pool_start, + peer->pool_size - 1, peer->phys.ifindex, + &gw_addr, peer->mtu); + if (rc < 0 && rc != -EEXIST) { + warnx("Failed to add gateway route for EID %d: %s", gw_addr.eid, + strerror(-rc)); + return rc; } - return rc; + // TODO: Polling logic for downstream EID + + return 0; } int main(int argc, char **argv) From 82859ae0d62469af3561c9ce9cd7c4d0756d15ed Mon Sep 17 00:00:00 2001 From: Jeremy Kerr Date: Fri, 22 Aug 2025 10:12:16 +0800 Subject: [PATCH 08/29] mctpd: rename allocate_eid functions The MCTP command is "Allocate Endpoint IDs", where the plural provides a reference to bridge pool ranges, rather than a single EID. Update the function names to reflect this. Signed-off-by: Jeremy Kerr --- src/mctpd.c | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/mctpd.c b/src/mctpd.c index f0d2099..2e798a5 100644 --- a/src/mctpd.c +++ b/src/mctpd.c @@ -259,7 +259,7 @@ static int del_local_eid(struct ctx *ctx, uint32_t net, int eid); static int add_net(struct ctx *ctx, uint32_t net); static void del_net(struct net *net); static int add_interface(struct ctx *ctx, int ifindex); -static int endpoint_allocate_eid(struct peer *peer); +static int endpoint_allocate_eids(struct peer *peer); static const sd_bus_vtable bus_endpoint_obmc_vtable[]; static const sd_bus_vtable bus_endpoint_cc_vtable[]; @@ -2458,7 +2458,7 @@ static int method_assign_endpoint(sd_bus_message *call, void *data, goto err; if (peer->pool_size > 0) { - rc = endpoint_allocate_eid(peer); + rc = endpoint_allocate_eids(peer); if (rc < 0) { warnx("Failed to allocate downstream EIDs"); } else { @@ -4404,12 +4404,10 @@ static void free_config(struct ctx *ctx) free(ctx->config_filename); } -static int endpoint_send_allocate_endpoint_id(struct peer *peer, - mctp_eid_t eid_start, - uint8_t eid_pool_size, - mctp_ctrl_cmd_allocate_eids_op op, - uint8_t *allocated_pool_size, - mctp_eid_t *allocated_pool_start) +static int endpoint_send_allocate_endpoint_ids( + struct peer *peer, mctp_eid_t eid_start, uint8_t eid_pool_size, + mctp_ctrl_cmd_allocate_eids_op op, uint8_t *allocated_pool_size, + mctp_eid_t *allocated_pool_start) { struct sockaddr_mctp_ext addr; struct mctp_ctrl_cmd_allocate_eids req = { 0 }; @@ -4476,7 +4474,7 @@ static int endpoint_send_allocate_endpoint_id(struct peer *peer, return rc; } -static int endpoint_allocate_eid(struct peer *peer) +static int endpoint_allocate_eids(struct peer *peer) { uint8_t allocated_pool_size = 0; mctp_eid_t allocated_pool_start = 0; @@ -4486,7 +4484,7 @@ static int endpoint_allocate_eid(struct peer *peer) warnx("Invalid pool start %d", peer->pool_start); return -1; } - rc = endpoint_send_allocate_endpoint_id( + rc = endpoint_send_allocate_endpoint_ids( peer, peer->pool_start, peer->pool_size, mctp_ctrl_cmd_allocate_eids_alloc_eids, &allocated_pool_size, &allocated_pool_start); From 10fc698395e2c2d7295132324faecb71aa082b8d Mon Sep 17 00:00:00 2001 From: Faizan Ali Date: Wed, 25 Jun 2025 17:56:52 +0530 Subject: [PATCH 09/29] Update mctp document with mctp bridge support * updated mctpd.md with new mctp bridge support for dynamic eid assignment from AssignEndpoint d-bus call Signed-off-by: Faizan Ali --- docs/mctpd.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/mctpd.md b/docs/mctpd.md index b4a5cf0..3bab1d6 100644 --- a/docs/mctpd.md +++ b/docs/mctpd.md @@ -126,7 +126,11 @@ busctl call au.com.codeconstruct.MCTP1 \ Similar to SetupEndpoint, but will always assign an EID rather than querying for existing ones. Will return `new = false` when an endpoint is already known to -`mctpd`. +`mctpd`. If the endpoint is an MCTP bridge (indicated by requesting a pool size +in its Set Endpoint ID response), this method attempts to allocate a contiguous +range of EIDs for the bridge's downstream endpoints. If sufficient contiguous EIDs +are not available within the dynamic allocation pool for the network, only the +bridge's own EID will be assigned, and downstream EID allocation will fail. #### `.AssignEndpointStatic`: `ayy` → `yisb` From b385d45398677f07c676acd88aea0d02b030b156 Mon Sep 17 00:00:00 2001 From: Faizan Ali Date: Tue, 1 Jul 2025 01:45:11 +0530 Subject: [PATCH 10/29] Add tests for dynamic bridge eid and downstream eid assignment Add new test for validating AssignEndpoint D-Bus method to verify bridge endpoint EID allocation being contiguous to its downstream eids. Add Allocate Endpoint control message support with new endpoint property for allocated pool size also assign dynamic eid contiguous to bridge during Allocate Endpoint control message. Signed-off-by: Faizan Ali --- tests/mctpenv/__init__.py | 26 +++++++++++++++++++++++++- tests/test_mctpd.py | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/tests/mctpenv/__init__.py b/tests/mctpenv/__init__.py index 8f82bf9..523c4c1 100644 --- a/tests/mctpenv/__init__.py +++ b/tests/mctpenv/__init__.py @@ -303,6 +303,9 @@ def __init__(self, iface, lladdr, ep_uuid = None, eid = 0, types = None): self.eid = eid self.types = types or [0] self.bridged_eps = [] + self.pool_size = 0 + self.allocated_pool = None # or (start, size) + # keyed by (type, type-specific-instance) self.commands = {} @@ -348,7 +351,12 @@ async def handle_mctp_control(self, sock, addr, data): # Set Endpoint ID (op, eid) = data[2:] self.eid = eid - data = bytes(hdr + [0x00, 0x00, self.eid, 0x00]) + pool_size = len(self.bridged_eps) + alloc_status = 0x00 + # request a pool if we have one + if pool_size: + alloc_status |= 0x01 + data = bytes(hdr + [0x00, alloc_status, self.eid, pool_size]) await sock.send(raddr, data) elif opcode == 2: @@ -367,6 +375,22 @@ async def handle_mctp_control(self, sock, addr, data): data = bytes(hdr + [0x00, len(types)] + types) await sock.send(raddr, data) + elif opcode == 8: + # Allocate Endpoint IDs + (_, _, _, pool_size, pool_start) = data + alloc_status = 0x00 + if self.allocated_pool is not None: + alloc_status = 0x01 + else: + self.allocated_pool = (pool_start, pool_size) + # Assign sequential EIDs starting from pool_start + for (n, ep) in enumerate(self.bridged_eps[:pool_size]): + ep.eid = self.allocated_pool[0] + n + + data = bytes(hdr + [0x00, alloc_status, + self.allocated_pool[1], self.allocated_pool[0]]) + await sock.send(raddr, data) + else: await sock.send(raddr, bytes(hdr + [0x05])) # unsupported command diff --git a/tests/test_mctpd.py b/tests/test_mctpd.py index c9b2aaa..50d89b1 100644 --- a/tests/test_mctpd.py +++ b/tests/test_mctpd.py @@ -926,3 +926,39 @@ async def test_config_dyn_eid_range_max(nursery, dbus, sysnet): res = await mctpd.stop_mctpd() assert res == 0 + +""" Test bridge endpoint dynamic EID assignment and downstream +endpoint EID allocation + +Tests that: +- Bridge endpoint can be assigned a dynamic EID +- Downstream endpoints get contiguous EIDs after bridge's own eid +""" +async def test_assign_dynamic_bridge_eid(dbus, mctpd): + iface = mctpd.system.interfaces[0] + mctp = await mctpd_mctp_iface_obj(dbus, iface) + ep = mctpd.network.endpoints[0] + pool_size = 2 + + # Set up bridged endpoints as undiscovered EID 0 + for i in range(pool_size): + br_ep = Endpoint(iface, bytes(), types=[0, 2]) + ep.add_bridged_ep(br_ep) + mctpd.network.add_endpoint(br_ep) + + mctp = await mctpd_mctp_iface_obj(dbus, iface) + + # dynamic EID assigment for dev1 + (eid, _, path, new) = await mctp.call_assign_endpoint(ep.lladdr) + + assert new + assert ep.allocated_pool == (eid + 1, pool_size) + + # check if we can assign non-bridged endpoint dev2, eid from + #bridge's already assigned pool + dev2 = Endpoint(iface, bytes([0x1e])) + mctpd.network.add_endpoint(dev2) + with pytest.raises(asyncdbus.errors.DBusError) as ex: + await mctp.call_assign_endpoint_static(dev2.lladdr, ep.eid + 1) + + assert str(ex.value) == "Request failed" From 44bd7506faa3b3de8eaa231d26a4a020fc5d8aa6 Mon Sep 17 00:00:00 2001 From: Faizan Ali Date: Fri, 18 Jul 2025 04:21:11 +0530 Subject: [PATCH 11/29] Add interface au.com.codeconstruct.MCTP.Bridge1 New endpoint object interface au.com.codeconstruct.MCTP.Bridge1 which will capture details of bridge type endpoint such as pool start, pool end. Update test framework with new test methods to validate bridge pool assignemnt. [Minor rebase rework from Jeremy Kerr ; don't fail the allocation on signal emit failure] Signed-off-by: Faizan Ali --- docs/mctpd.md | 16 ++++++ src/mctpd.c | 53 ++++++++++++++++++++ tests/test_mctpd.py | 117 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 186 insertions(+) diff --git a/docs/mctpd.md b/docs/mctpd.md index 3bab1d6..4806b0e 100644 --- a/docs/mctpd.md +++ b/docs/mctpd.md @@ -219,6 +219,22 @@ busctl call au.com.codeconstruct.MCTP1 \ Removes the MCTP endpoint from `mctpd`, and deletes routes and neighbour entries. +### MCTP bridge interface: `au.com.codeconstruct.MCTP.Bridge1` interface +For any endpoint which also happens to be an MCTP Bridge, if dynamic eid is +assgined to it via d-bus method `.AssignEndpoint`, such endpoint's pool +allocation details would be reflected into `au.com.codeconstruct.MCTP.Bridge1` +interface of bridge's endpoint object. + +### `.PoolEnd`: `y` + +A constant property representing last EID in the contiguous range allocated +for downstream endpoints. + +### `.PoolStart`: `y` + +A constant property representing first EID in the contiguous range allocated +for downstream endpoints. + ## Configuration `mctpd` reads configuration data from a TOML file, typically `/etc/mctpd.conf`. diff --git a/src/mctpd.c b/src/mctpd.c index 2e798a5..305e2ff 100644 --- a/src/mctpd.c +++ b/src/mctpd.c @@ -46,6 +46,7 @@ #define MCTP_DBUS_PATH_LINKS "/au/com/codeconstruct/mctp1/interfaces" #define CC_MCTP_DBUS_IFACE_BUSOWNER "au.com.codeconstruct.MCTP.BusOwner1" #define CC_MCTP_DBUS_IFACE_ENDPOINT "au.com.codeconstruct.MCTP.Endpoint1" +#define CC_MCTP_DBUS_IFACE_BRIDGE "au.com.codeconstruct.MCTP.Bridge1" #define CC_MCTP_DBUS_IFACE_TESTING "au.com.codeconstruct.MCTPTesting" #define MCTP_DBUS_NAME "au.com.codeconstruct.MCTP1" #define MCTP_DBUS_IFACE_ENDPOINT "xyz.openbmc_project.MCTP.Endpoint" @@ -152,6 +153,7 @@ struct peer { bool published; sd_bus_slot *slot_obmc_endpoint; sd_bus_slot *slot_cc_endpoint; + sd_bus_slot *slot_bridge; sd_bus_slot *slot_uuid; char *path; @@ -263,6 +265,7 @@ static int endpoint_allocate_eids(struct peer *peer); static const sd_bus_vtable bus_endpoint_obmc_vtable[]; static const sd_bus_vtable bus_endpoint_cc_vtable[]; +static const sd_bus_vtable bus_endpoint_bridge[]; static const sd_bus_vtable bus_endpoint_uuid_vtable[]; __attribute__((format(printf, 1, 2))) static void bug_warn(const char *fmt, ...) @@ -1770,6 +1773,7 @@ static void free_peers(struct ctx *ctx) free(peer->path); sd_bus_slot_unref(peer->slot_obmc_endpoint); sd_bus_slot_unref(peer->slot_cc_endpoint); + sd_bus_slot_unref(peer->slot_bridge); sd_bus_slot_unref(peer->slot_uuid); free(peer); } @@ -2840,6 +2844,8 @@ static int unpublish_peer(struct peer *peer) peer->slot_obmc_endpoint = NULL; sd_bus_slot_unref(peer->slot_cc_endpoint); peer->slot_cc_endpoint = NULL; + sd_bus_slot_unref(peer->slot_bridge); + peer->slot_bridge = NULL; sd_bus_slot_unref(peer->slot_uuid); peer->slot_uuid = NULL; peer->published = false; @@ -3224,6 +3230,28 @@ static int bus_endpoint_get_prop(sd_bus *bus, const char *path, return rc; } +static int bus_bridge_get_prop(sd_bus *bus, const char *path, + const char *interface, const char *property, + sd_bus_message *reply, void *userdata, + sd_bus_error *berr) +{ + struct peer *peer = userdata; + int rc; + + if (strcmp(property, "PoolStart") == 0) { + rc = sd_bus_message_append(reply, "y", peer->pool_start); + } else if (strcmp(property, "PoolEnd") == 0) { + uint8_t pool_end = peer->pool_start + peer->pool_size - 1; + rc = sd_bus_message_append(reply, "y", pool_end); + } else { + warnx("Unknown bridge property '%s' for %s iface %s", property, + path, interface); + rc = -ENOENT; + } + + return rc; +} + static int bus_network_get_prop(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, @@ -3423,6 +3451,21 @@ static const sd_bus_vtable bus_endpoint_cc_vtable[] = { SD_BUS_VTABLE_END }; +static const sd_bus_vtable bus_endpoint_bridge[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_PROPERTY("PoolStart", + "y", + bus_bridge_get_prop, + 0, + SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("PoolEnd", + "y", + bus_bridge_get_prop, + 0, + SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_VTABLE_END +}; + static const sd_bus_vtable bus_link_vtable[] = { SD_BUS_VTABLE_START(0), SD_BUS_WRITABLE_PROPERTY("Role", @@ -4513,6 +4556,16 @@ static int endpoint_allocate_eids(struct peer *peer) return rc; } + sd_bus_add_object_vtable(peer->ctx->bus, &peer->slot_bridge, peer->path, + CC_MCTP_DBUS_IFACE_BRIDGE, bus_endpoint_bridge, + peer); + rc = sd_bus_emit_interfaces_added(peer->ctx->bus, peer->path, + CC_MCTP_DBUS_IFACE_BRIDGE, NULL); + if (rc < 0) { + warnx("Failed to emit add %s signal for endpoint %d : %s", + CC_MCTP_DBUS_IFACE_BRIDGE, peer->eid, strerror(-rc)); + } + // TODO: Polling logic for downstream EID return 0; diff --git a/tests/test_mctpd.py b/tests/test_mctpd.py index 50d89b1..37933c3 100644 --- a/tests/test_mctpd.py +++ b/tests/test_mctpd.py @@ -20,6 +20,7 @@ MCTPD_MCTP_P = '/au/com/codeconstruct/mctp1' MCTPD_MCTP_I = 'au.com.codeconstruct.MCTP.BusOwner1' MCTPD_ENDPOINT_I = 'au.com.codeconstruct.MCTP.Endpoint1' +MCTPD_ENDPOINT_BRIDGE_I = 'au.com.codeconstruct.MCTP.Bridge1' DBUS_OBJECT_MANAGER_I = 'org.freedesktop.DBus.ObjectManager' DBUS_PROPERTIES_I = 'org.freedesktop.DBus.Properties' @@ -962,3 +963,119 @@ async def test_assign_dynamic_bridge_eid(dbus, mctpd): await mctp.call_assign_endpoint_static(dev2.lladdr, ep.eid + 1) assert str(ex.value) == "Request failed" + +""" Test that we truncate the requested pool size to + the max_pool_size config """ +async def test_assign_dynamic_eid_limited_pool(nursery, dbus, sysnet): + max_pool_size = 1 + config = f""" + [bus-owner] + max_pool_size = {max_pool_size} + """ + + mctpd = MctpdWrapper(dbus, sysnet, config = config) + await mctpd.start_mctpd(nursery) + + iface = mctpd.system.interfaces[0] + ep = mctpd.network.endpoints[0] + mctp = await mctpd_mctp_iface_obj(dbus, iface) + + # Set up bridged endpoints as undiscovered EID 0 + for i in range(0, 2): + br_ep = Endpoint(iface, bytes(), types=[0, 2]) + ep.add_bridged_ep(br_ep) + mctpd.network.add_endpoint(br_ep) + + # dynamic EID assigment for dev1 + (eid, _, path, new) = await mctp.call_assign_endpoint(ep.lladdr) + + assert new + + bridge_obj = await dbus.get_proxy_object(MCTPD_C, path) + props_iface = await bridge_obj.get_interface(DBUS_PROPERTIES_I) + pool_end = await props_iface.call_get(MCTPD_ENDPOINT_BRIDGE_I, "PoolEnd") + pool_size = pool_end.value - eid + assert pool_size == max_pool_size + + res = await mctpd.stop_mctpd() + assert res == 0 + +""" Test that no pool is assigned for requested pool size from + unavailable pool space""" +async def test_assign_dynamic_unavailable_pool(nursery, dbus, sysnet): + (min_dyn_eid, max_dyn_eid) = (8, 12) + config = f""" + [bus-owner] + dynamic_eid_range = [{min_dyn_eid}, {max_dyn_eid}] + """ + + mctpd = MctpdWrapper(dbus, sysnet, config = config) + await mctpd.start_mctpd(nursery) + + iface = mctpd.system.interfaces[0] + ep = mctpd.network.endpoints[0] + mctp = await mctpd_mctp_iface_obj(dbus, iface) + + # Set up bridged endpoints as undiscovered EID 0 + for i in range(0, 2): + br_ep = Endpoint(iface, bytes(), types=[0, 2]) + ep.add_bridged_ep(br_ep) + mctpd.network.add_endpoint(br_ep) + + # consume middle eid from the range to dev2 + dev2 = Endpoint(iface, bytes([0x09])) + mctpd.network.add_endpoint(dev2) + (eid, _, path, new) = await mctp.call_assign_endpoint_static( + dev2.lladdr, + 10 + ) + assert new + + # dynamic EID assigment for dev1 + (eid, _, path, new) = await mctp.call_assign_endpoint(ep.lladdr) + assert new + # Interface should not be present for unavailable pool space + with pytest.raises(asyncdbus.errors.InterfaceNotFoundError): + bridge_obj = await dbus.get_proxy_object(MCTPD_C, path) + await bridge_obj.get_interface(MCTPD_ENDPOINT_BRIDGE_I) + + res = await mctpd.stop_mctpd() + assert res == 0 + +"""During Allocate Endpoint ID exchange, return completion code failure +to indicate no pool has been assigned to the bridge""" +async def test_assign_dynamic_eid_allocation_failure(dbus, mctpd): + class BridgeEndpoint(Endpoint): + async def handle_mctp_control(self, sock, src_addr, msg): + flags, opcode = msg[0:2] + if opcode != 0x8: + return await super().handle_mctp_control(sock, src_addr, msg) + dst_addr = MCTPSockAddr.for_ep_resp(self, src_addr, sock.addr_ext) + + msg = bytes([ + flags & 0x1f, # Rsp + 0x08, # opcode: Allocate Endpoint ID + 0x01, # cc: failure + 0x01, # allocation rejected + 0x00, # pool size + 0x00, # pool start + ]) + await sock.send(dst_addr, msg) + + iface = mctpd.system.interfaces[0] + ep = BridgeEndpoint(iface, bytes([0x1e])) + mctpd.network.add_endpoint(ep) + # Set up downstream endpoints as undiscovered EID 0 + for i in range(0, 2): + br_ep = Endpoint(iface, bytes(), types=[0, 2]) + ep.add_bridged_ep(br_ep) + mctpd.network.add_endpoint(br_ep) + mctp = await mctpd_mctp_iface_obj(dbus, iface) + + # dynamic EID assigment for dev1 + (eid, _, path, new) = await mctp.call_assign_endpoint(ep.lladdr) + assert new + # Interface should not be present for failed pool allocation + with pytest.raises(asyncdbus.errors.InterfaceNotFoundError): + bridge_obj = await dbus.get_proxy_object(MCTPD_C, path) + await bridge_obj.get_interface(MCTPD_ENDPOINT_BRIDGE_I) From 00ed3ee50f937856367a26e10ac6fe3ccd8e4125 Mon Sep 17 00:00:00 2001 From: Jeremy Kerr Date: Wed, 20 Aug 2025 17:44:02 +0800 Subject: [PATCH 12/29] tests: mctpd: split dynamic bridge test into smaller components Currently, test_assign_dynamic_bridge_eid test both the bridge assignment, and conflicts against static EIDs. Instead, split this into two smaller tests, which provide a base for future bridge-conflict tests. Signed-off-by: Jeremy Kerr --- tests/test_mctpd.py | 42 ++++++++++++++++++++++++++++++++---------- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/tests/test_mctpd.py b/tests/test_mctpd.py index 37933c3..48b6cd2 100644 --- a/tests/test_mctpd.py +++ b/tests/test_mctpd.py @@ -943,26 +943,48 @@ async def test_assign_dynamic_bridge_eid(dbus, mctpd): # Set up bridged endpoints as undiscovered EID 0 for i in range(pool_size): - br_ep = Endpoint(iface, bytes(), types=[0, 2]) + br_ep = Endpoint(iface, bytes(), types=[0]) ep.add_bridged_ep(br_ep) mctpd.network.add_endpoint(br_ep) - mctp = await mctpd_mctp_iface_obj(dbus, iface) - # dynamic EID assigment for dev1 (eid, _, path, new) = await mctp.call_assign_endpoint(ep.lladdr) assert new assert ep.allocated_pool == (eid + 1, pool_size) - # check if we can assign non-bridged endpoint dev2, eid from - #bridge's already assigned pool - dev2 = Endpoint(iface, bytes([0x1e])) - mctpd.network.add_endpoint(dev2) - with pytest.raises(asyncdbus.errors.DBusError) as ex: - await mctp.call_assign_endpoint_static(dev2.lladdr, ep.eid + 1) +""" Test that static allocations are not permitted, if they would conflict +with a bridge pool""" +async def test_bridge_ep_conflict_static(dbus, mctpd): + iface = mctpd.system.interfaces[0] + mctp = await mctpd_mctp_iface_obj(dbus, iface) + ep = mctpd.network.endpoints[0] + n_bridged = 3 - assert str(ex.value) == "Request failed" + # add downstream devices + for i in range(n_bridged): + br_ep = Endpoint(iface, bytes()) + ep.add_bridged_ep(br_ep) + + (eid, _, path, new) = await mctp.call_assign_endpoint(ep.lladdr) + assert ep.allocated_pool == (eid + 1, n_bridged) + + # ensure no static assignment can be made from the bridged range + for i in range(n_bridged): + dev = Endpoint(iface, bytes([0x30 + i])) + mctpd.network.add_endpoint(dev) + with pytest.raises(asyncdbus.errors.DBusError): + await mctp.call_assign_endpoint_static(dev.lladdr, ep.eid + 1 + i) + + # ... but we're okay with the EID following + dev = Endpoint(iface, bytes([0x30 + n_bridged])) + mctpd.network.add_endpoint(dev) + static_eid = ep.eid + 1 + n_bridged + (eid, _, _, _) = await mctp.call_assign_endpoint_static( + dev.lladdr, static_eid + ) + + assert eid == static_eid """ Test that we truncate the requested pool size to the max_pool_size config """ From 72b1c2aa25b137ab8d1c3c51bff4b94647b45389 Mon Sep 17 00:00:00 2001 From: Jeremy Kerr Date: Wed, 20 Aug 2025 18:11:46 +0800 Subject: [PATCH 13/29] tests: mctpd: add bridge conflict-from-learnt-eid test In addition to the static assignments, we want to ensure that LearnEndpoint does not result in EID conflicts. Signed-off-by: Jeremy Kerr --- tests/test_mctpd.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/tests/test_mctpd.py b/tests/test_mctpd.py index 48b6cd2..5403dae 100644 --- a/tests/test_mctpd.py +++ b/tests/test_mctpd.py @@ -986,6 +986,37 @@ async def test_bridge_ep_conflict_static(dbus, mctpd): assert eid == static_eid +""" Test that learnt allocations (ie, pre-assigned device EIDs) are not +permitted, if they would conflict with a bridge pool """ +async def test_bridge_ep_conflict_learn(dbus, mctpd): + iface = mctpd.system.interfaces[0] + mctp = await mctpd_mctp_iface_obj(dbus, iface) + ep = mctpd.network.endpoints[0] + n_bridged = 3 + + # add downstream devices + for i in range(n_bridged): + br_ep = Endpoint(iface, bytes()) + ep.add_bridged_ep(br_ep) + + (eid, _, path, new) = await mctp.call_assign_endpoint(ep.lladdr) + assert ep.allocated_pool == (eid + 1, n_bridged) + + # ensure no learnt assignment can be made from the bridged range + for i in range(n_bridged): + dev = Endpoint(iface, bytes([0x30 + i]), eid=ep.eid + 1 + i) + mctpd.network.add_endpoint(dev) + with pytest.raises(asyncdbus.errors.DBusError): + await mctp.call_learn_endpoint(dev.lladdr) + + # ... but we're okay with the EID following + dev_eid = ep.eid + 1 + n_bridged + dev = Endpoint(iface, bytes([0x30 + n_bridged]), eid=dev_eid) + mctpd.network.add_endpoint(dev) + (eid, _, _, _) = await mctp.call_learn_endpoint(dev.lladdr) + + assert eid == dev_eid + """ Test that we truncate the requested pool size to the max_pool_size config """ async def test_assign_dynamic_eid_limited_pool(nursery, dbus, sysnet): From 27e5cc4dab783b73d28526a591bba173c06aaa03 Mon Sep 17 00:00:00 2001 From: Jeremy Kerr Date: Thu, 21 Aug 2025 13:36:01 +0800 Subject: [PATCH 14/29] tests: mctpd: ensure that lack of bridge range space does not prevent ep assignment We want to ensure that running out of bridge range space does not cause a failure to allocate a non-bridge EID. We speculatively allocate before we determine bridge/non-bridge status, so this may cause issues. Signed-off-by: Jeremy Kerr --- tests/test_mctpd.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tests/test_mctpd.py b/tests/test_mctpd.py index 5403dae..5eedff7 100644 --- a/tests/test_mctpd.py +++ b/tests/test_mctpd.py @@ -1132,3 +1132,28 @@ async def handle_mctp_control(self, sock, src_addr, msg): with pytest.raises(asyncdbus.errors.InterfaceNotFoundError): bridge_obj = await dbus.get_proxy_object(MCTPD_C, path) await bridge_obj.get_interface(MCTPD_ENDPOINT_BRIDGE_I) + +""" Test assigning a non-bridge endpoint, when we don't have capacity for +the speculatively-allocated bridge range""" +async def test_assign_without_bridge_range(dbus, sysnet, nursery): + (dyn_eid_min, dyn_eid_max) = (10, 20) + max_pool_size = (dyn_eid_max - dyn_eid_min) + 1 + config = f""" + [bus-owner] + dynamic_eid_range = [{dyn_eid_min}, {dyn_eid_max}] + max_pool_size = {max_pool_size} + """ + + mctpd = MctpdWrapper(dbus, sysnet, config = config) + await mctpd.start_mctpd(nursery) + + iface = mctpd.system.interfaces[0] + ep = mctpd.network.endpoints[0] + + mctp = await mctpd_mctp_iface_obj(dbus, iface) + + (eid, _, _, _) = await mctp.call_assign_endpoint(ep.lladdr) + + assert eid == dyn_eid_min + res = await mctpd.stop_mctpd() + assert res == 0 From b81d58fd02980458bcbbe67775fe390eeb6b1352 Mon Sep 17 00:00:00 2001 From: Jeremy Kerr Date: Thu, 21 Aug 2025 15:48:42 +0800 Subject: [PATCH 15/29] mctpd: return EEXIST for address conflicts in bridge ranges add_peer() returns -EEXIST if a proposed EID is already allocated to a peer, but -EADDRNOTAVAIL if it is allocated to a bridge. Callers are expecting EEXIST for conflicts, so use that instead. Signed-off-by: Jeremy Kerr --- src/mctpd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mctpd.c b/src/mctpd.c index 305e2ff..338aec5 100644 --- a/src/mctpd.c +++ b/src/mctpd.c @@ -1627,7 +1627,7 @@ static int add_peer(struct ctx *ctx, const dest_phys *dest, mctp_eid_t eid, * pool space else never allow. */ if (!net_learn & is_eid_in_bridge_pool(n, ctx, eid)) { - return -EADDRNOTAVAIL; + return -EEXIST; } } From c5a6bc991f4b7afab2ca3b82a501506c75c69384 Mon Sep 17 00:00:00 2001 From: Jeremy Kerr Date: Thu, 21 Aug 2025 15:49:53 +0800 Subject: [PATCH 16/29] tests: mctpd: check that SetupEndpoint will not allocate to a bridge range Signed-off-by: Jeremy Kerr --- tests/test_mctpd.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tests/test_mctpd.py b/tests/test_mctpd.py index 5eedff7..a907b23 100644 --- a/tests/test_mctpd.py +++ b/tests/test_mctpd.py @@ -1017,6 +1017,31 @@ async def test_bridge_ep_conflict_learn(dbus, mctpd): assert eid == dev_eid +""" Test that learnt allocations (ie, pre-assigned device EIDs) are not +permitted through SetupEndpoint, if they would conflict with a bridge pool """ +async def test_bridge_ep_conflict_setup(dbus, mctpd): + iface = mctpd.system.interfaces[0] + mctp = await mctpd_mctp_iface_obj(dbus, iface) + ep = mctpd.network.endpoints[0] + n_bridged = 3 + + # add downstream devices + for i in range(n_bridged): + br_ep = Endpoint(iface, bytes()) + ep.add_bridged_ep(br_ep) + + (eid, _, path, new) = await mctp.call_assign_endpoint(ep.lladdr) + assert ep.allocated_pool == (eid + 1, n_bridged) + pool_range = range(ep.allocated_pool[0], ep.allocated_pool[1] + 1) + + # ensure no SetupEndpoint assignment can be made from the bridged range; + # these should get reassigned elsewhere. + for i in range(n_bridged): + dev = Endpoint(iface, bytes([0x30 + i]), eid=ep.eid + 1 + i) + mctpd.network.add_endpoint(dev) + (eid, _, _, _) = await mctp.call_setup_endpoint(dev.lladdr) + assert eid not in pool_range + """ Test that we truncate the requested pool size to the max_pool_size config """ async def test_assign_dynamic_eid_limited_pool(nursery, dbus, sysnet): From 7686f6cc92e557c0367ad2fa542cfbe459f79d45 Mon Sep 17 00:00:00 2001 From: Jeremy Kerr Date: Thu, 21 Aug 2025 09:40:50 +0800 Subject: [PATCH 17/29] docs: mctpd: minor bridge doc fixes Spacing fixes, and a simplification for the Bridge1 interface description. Re-order pool properties to describe in start -> end order. Signed-off-by: Jeremy Kerr --- docs/mctpd.md | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/docs/mctpd.md b/docs/mctpd.md index 4806b0e..97b9b04 100644 --- a/docs/mctpd.md +++ b/docs/mctpd.md @@ -128,9 +128,9 @@ Similar to SetupEndpoint, but will always assign an EID rather than querying for existing ones. Will return `new = false` when an endpoint is already known to `mctpd`. If the endpoint is an MCTP bridge (indicated by requesting a pool size in its Set Endpoint ID response), this method attempts to allocate a contiguous -range of EIDs for the bridge's downstream endpoints. If sufficient contiguous EIDs -are not available within the dynamic allocation pool for the network, only the -bridge's own EID will be assigned, and downstream EID allocation will fail. +range of EIDs for the bridge's downstream endpoints. If sufficient contiguous +EIDs are not available within the dynamic allocation pool for the network, only +the bridge's own EID will be assigned, and downstream EID allocation will fail. #### `.AssignEndpointStatic`: `ayy` → `yisb` @@ -220,19 +220,20 @@ busctl call au.com.codeconstruct.MCTP1 \ Removes the MCTP endpoint from `mctpd`, and deletes routes and neighbour entries. ### MCTP bridge interface: `au.com.codeconstruct.MCTP.Bridge1` interface -For any endpoint which also happens to be an MCTP Bridge, if dynamic eid is -assgined to it via d-bus method `.AssignEndpoint`, such endpoint's pool -allocation details would be reflected into `au.com.codeconstruct.MCTP.Bridge1` -interface of bridge's endpoint object. -### `.PoolEnd`: `y` +MCTP endpoints that are set up as a bridge device (and therefore have an +EID pool allocated to them, for downstream devices) also carry the +`MCTP.Bridge1` interface. This provides details of the allocated EID pool, via +two properties: + +### `.PoolStart`: `y` -A constant property representing last EID in the contiguous range allocated +A constant property representing the first EID in the contiguous range allocated for downstream endpoints. -### `.PoolStart`: `y` +### `.PoolEnd`: `y` -A constant property representing first EID in the contiguous range allocated +A constant property representing the last EID in the contiguous range allocated for downstream endpoints. ## Configuration From a985b9cc66b9282e0074e5fabb2cdb71361053d6 Mon Sep 17 00:00:00 2001 From: Jeremy Kerr Date: Thu, 21 Aug 2025 09:41:54 +0800 Subject: [PATCH 18/29] docs: mctpd: don't specify EID ranges as contiguous The spec currenty requires this, but that may change. So, don't bind the dbus API to requiring contiguous EIDs. Signed-off-by: Jeremy Kerr --- docs/mctpd.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/mctpd.md b/docs/mctpd.md index 97b9b04..0ddad10 100644 --- a/docs/mctpd.md +++ b/docs/mctpd.md @@ -228,13 +228,13 @@ two properties: ### `.PoolStart`: `y` -A constant property representing the first EID in the contiguous range allocated -for downstream endpoints. +A constant property representing the first EID in the range allocated for +downstream endpoints. ### `.PoolEnd`: `y` -A constant property representing the last EID in the contiguous range allocated -for downstream endpoints. +A constant property representing the last EID in the range allocated for +downstream endpoints. ## Configuration From 87b7debaf7e384652df5a4be0ece455b55fb7cb0 Mon Sep 17 00:00:00 2001 From: Jeremy Kerr Date: Thu, 21 Aug 2025 14:08:44 +0800 Subject: [PATCH 19/29] docs: Add note about bridge allocations on BusOwner1.LearnEndpoint method Signed-off-by: Jeremy Kerr --- docs/mctpd.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/mctpd.md b/docs/mctpd.md index 0ddad10..f60f568 100644 --- a/docs/mctpd.md +++ b/docs/mctpd.md @@ -152,6 +152,14 @@ Like SetupEndpoint but will not assign EIDs, will only query endpoints for a current EID. The `new` return value is set to `false` for an already known endpoint, or `true` when an endpoint's EID is newly discovered. +Because we are not issuing a Set Endpoint ID as part of the LearnEndpoint call, +we do not have any details of the endpoint's bridge pool range. So, +LearnEndpoint is unsuitable for use with bridge endpoints - it cannot provide +the bridge with its own EID pool. `mctpd` will warn if the device type +reports as a bridge. + +Bridge endpoints should be initialised with `AssignEndpoint` instead. + ## Network objects: `/au/com/codeconstruct/networks/` These objects represent MCTP networks which have been added use `mctp link` From 7e121bd7f85494efe42ce8941eee5d50c752b4d5 Mon Sep 17 00:00:00 2001 From: Jeremy Kerr Date: Thu, 21 Aug 2025 12:18:16 +0800 Subject: [PATCH 20/29] mctpd: move allocate EIDs logging internal to endpoint_allocate_eids() Some failure paths log internally, others do not. Make this consistent, and do all logging inside the function. Signed-off-by: Jeremy Kerr --- src/mctpd.c | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/src/mctpd.c b/src/mctpd.c index 338aec5..1488ec0 100644 --- a/src/mctpd.c +++ b/src/mctpd.c @@ -2461,20 +2461,8 @@ static int method_assign_endpoint(sd_bus_message *call, void *data, if (!peer_path) goto err; - if (peer->pool_size > 0) { - rc = endpoint_allocate_eids(peer); - if (rc < 0) { - warnx("Failed to allocate downstream EIDs"); - } else { - if (peer->ctx->verbose) { - fprintf(stderr, - "Downstream EIDs assigned from %d to %d : pool size %d\n", - peer->pool_start, - peer->pool_start + peer->pool_size - 1, - peer->pool_size); - } - } - } + if (peer->pool_size > 0) + endpoint_allocate_eids(peer); return sd_bus_reply_method_return(call, "yisb", peer->eid, peer->net, peer_path, 1); @@ -4532,6 +4520,7 @@ static int endpoint_allocate_eids(struct peer *peer) mctp_ctrl_cmd_allocate_eids_alloc_eids, &allocated_pool_size, &allocated_pool_start); if (rc) { + warnx("Failed to allocate downstream EIDs"); //reset peer pool peer->pool_size = 0; peer->pool_start = 0; @@ -4566,6 +4555,14 @@ static int endpoint_allocate_eids(struct peer *peer) CC_MCTP_DBUS_IFACE_BRIDGE, peer->eid, strerror(-rc)); } + if (peer->ctx->verbose) { + fprintf(stderr, + "Bridge (eid %d) assigned pool [%d, %d], size %d\n", + peer->eid, peer->pool_start, + peer->pool_start + peer->pool_size - 1, + peer->pool_size); + } + // TODO: Polling logic for downstream EID return 0; From ec91de46f94eb71d2f7e96e47d29f54ca5434998 Mon Sep 17 00:00:00 2001 From: Jeremy Kerr Date: Thu, 21 Aug 2025 11:18:45 +0800 Subject: [PATCH 21/29] mctpd: allow bridge allocation with SetupEndpoint Currently, we only allow bridge pool allocation through an AssignEndpoint call, as that is guaranteed to involve a Set Endpoint ID command, required to start the pool allocation process. LearnEndpoint is intended to never modify endpoint state, so we keep that as-is. SetupEndpoint has always been a convenience method, intended to do a LearnEndpoint if possible, or fall back to AssignEndpoint if not. Because of this, we are not making any assurances about preserving state with SetupEndpoint. With the new bridge support, the current distinction between AssignEndpoint (which will allocate a bridge pool) and SetupEndpoint (which will not) is a potential point of confusion. Instead, allow bridge allocations through SetupEndpoint. We have a new conditional path here: we try the LearnEndpoint (ie, a Get Endpoint ID, to see if we can use that EID) first, but add a new check to determine if this is a bridge EID type. Is so, we force the fallback to Set Endpoint ID, which will allow a bridge EID allocation too. Signed-off-by: Jeremy Kerr --- src/mctp-control-spec.h | 22 +++++++++++++++++++++ src/mctpd.c | 41 ++++++++++++++++++++++++++++++--------- tests/mctpenv/__init__.py | 5 ++++- tests/test_mctpd.py | 27 ++++++++++++++++++++++++++ 4 files changed, 85 insertions(+), 10 deletions(-) diff --git a/src/mctp-control-spec.h b/src/mctp-control-spec.h index db6e004..7911f71 100644 --- a/src/mctp-control-spec.h +++ b/src/mctp-control-spec.h @@ -279,6 +279,28 @@ struct mctp_ctrl_resp_allocate_eids { #define MCTP_SET_EID_ACCEPTED 0x0 #define MCTP_SET_EID_REJECTED 0x1 +/* MCTP Get Endpoint ID request and response fields + * See DSP0236 v1.3.0 Table 15. + */ +#define MCTP_GET_EID_EP_TYPE_SHIFT 4 +#define MCTP_GET_EID_EP_TYPE_MASK 0x03 +#define GET_MCTP_GET_EID_EP_TYPE(field) \ + (((field) >> MCTP_GET_EID_EP_TYPE_SHIFT) & MCTP_GET_EID_EP_TYPE_MASK) +#define MCTP_GET_EID_EP_TYPE_EP 0 +#define MCTP_GET_EID_EP_TYPE_BRIDGE 1 + +#define MCTP_GET_EID_EID_TYPE_SHIFT 0 +#define MCTP_GET_EID_EID_TYPE_MASK 0x03 +#define GET_MCTP_GET_EID_EID_TYPE(field) \ + (((field) >> MCTP_GET_EID_EID_TYPE_SHIFT) & MCTP_GET_EID_EID_TYPE_MASK) +#define MCTP_GET_EID_EID_TYPE_DYNAMIC 0 +/* Static EID is supported, may or may not match current */ +#define MCTP_GET_EID_EID_TYPE_STATIC 1 +/* Current eid is the same as static */ +#define MCTP_GET_EID_EID_TYPE_STATIC_SAME 2 +/* Current eid is different from static */ +#define MCTP_GET_EID_EID_TYPE_STATIC_DIFFERENT 3 + #define MCTP_EID_ALLOCATION_STATUS_SHIFT 0x0 #define MCTP_EID_ALLOCATION_STATUS_MASK 0x3 #define SET_MCTP_EID_ALLOCATION_STATUS(status) \ diff --git a/src/mctpd.c b/src/mctpd.c index 1488ec0..fd8b9dd 100644 --- a/src/mctpd.c +++ b/src/mctpd.c @@ -2383,15 +2383,27 @@ static int method_setup_endpoint(sd_bus_message *call, void *data, /* Get Endpoint ID */ rc = get_endpoint_peer(ctx, berr, dest, &peer, NULL); if (rc >= 0 && peer) { - if (ctx->verbose) + uint8_t ep_type = GET_MCTP_GET_EID_EP_TYPE(peer->endpoint_type); + if (ep_type == MCTP_GET_EID_EP_TYPE_BRIDGE) { fprintf(stderr, - "%s returning from get_endpoint_peer %s\n", - __func__, peer_tostr(peer)); - peer_path = path_from_peer(peer); - if (!peer_path) - goto err; - return sd_bus_reply_method_return(call, "yisb", peer->eid, - peer->net, peer_path, 0); + "SetupEndpoint, eid %d: Get EID " + "response indicated a bridge with existing " + "EID, reassigning\n", + peer->eid); + remove_peer(peer); + peer = NULL; + } else { + if (ctx->verbose) + fprintf(stderr, + "%s returning from get_endpoint_peer %s\n", + __func__, peer_tostr(peer)); + peer_path = path_from_peer(peer); + if (!peer_path) + goto err; + return sd_bus_reply_method_return(call, "yisb", + peer->eid, peer->net, + peer_path, 0); + } } else if (rc == -EEXIST) { // EEXISTS is OK, we will assign a new eid instead. } else if (rc < 0) { @@ -2400,10 +2412,13 @@ static int method_setup_endpoint(sd_bus_message *call, void *data, } /* Set Endpoint ID */ - rc = endpoint_assign_eid(ctx, berr, dest, &peer, 0, false); + rc = endpoint_assign_eid(ctx, berr, dest, &peer, 0, true); if (rc < 0) goto err; + if (peer->pool_size) + endpoint_allocate_eids(peer); + peer_path = path_from_peer(peer); if (!peer_path) goto err; @@ -2582,6 +2597,14 @@ static int method_learn_endpoint(sd_bus_message *call, void *data, if (!peer) return sd_bus_reply_method_return(call, "yisb", 0, 0, "", 0); + uint8_t ep_type = GET_MCTP_GET_EID_EP_TYPE(peer->endpoint_type); + if (ep_type == MCTP_GET_EID_EP_TYPE_BRIDGE) { + warnx("LearnEndpoint, eid %d: Get EID response " + "indicated a bridge with existing EID, " + "but no pool is assignable", + peer->eid); + } + peer_path = path_from_peer(peer); if (!peer_path) goto err; diff --git a/tests/mctpenv/__init__.py b/tests/mctpenv/__init__.py index 523c4c1..8321ab1 100644 --- a/tests/mctpenv/__init__.py +++ b/tests/mctpenv/__init__.py @@ -361,7 +361,10 @@ async def handle_mctp_control(self, sock, addr, data): elif opcode == 2: # Get Endpoint ID - data = bytes(hdr + [0x00, self.eid, 0x00, 0x00]) + ep_type = 0 + if len(self.bridged_eps) > 0: + ep_type = 0x1 << 4 + data = bytes(hdr + [0x00, self.eid, ep_type, 0x00]) await sock.send(raddr, data) elif opcode == 3: diff --git a/tests/test_mctpd.py b/tests/test_mctpd.py index a907b23..7cd9c0f 100644 --- a/tests/test_mctpd.py +++ b/tests/test_mctpd.py @@ -1042,6 +1042,33 @@ async def test_bridge_ep_conflict_setup(dbus, mctpd): (eid, _, _, _) = await mctp.call_setup_endpoint(dev.lladdr) assert eid not in pool_range +""" Test that mctpd will reassign a bridge endpoints (pre-configured) EID +if necessary to satisfy the bridge pool allocation""" +async def test_bridge_setup_reassign(dbus, mctpd): + iface = mctpd.system.interfaces[0] + mctp = await mctpd_mctp_iface_obj(dbus, iface) + + # ep: regular endpoint, will conflict with a bridge pool + ep = mctpd.network.endpoints[0] + static_eid = 10 + (eid, _, _, _) = await mctp.call_assign_endpoint_static( + ep.lladdr, + static_eid + ) + + assert eid == static_eid + + # br: our bridge + conflict_eid = 9 + br = Endpoint(iface, bytes([ep.lladdr[0] + 1]), eid=conflict_eid) + br.add_bridged_ep(Endpoint(iface, bytes())) + mctpd.network.add_endpoint(br) + + (eid, _, _, _) = await mctp.call_setup_endpoint(br.lladdr) + assert eid != conflict_eid + assert br.allocated_pool is not None + assert br.allocated_pool[0] == eid + 1 + """ Test that we truncate the requested pool size to the max_pool_size config """ async def test_assign_dynamic_eid_limited_pool(nursery, dbus, sysnet): From dce893e3f6edb2feb39fb3122642408873372bab Mon Sep 17 00:00:00 2001 From: Jeremy Kerr Date: Thu, 21 Aug 2025 15:37:13 +0800 Subject: [PATCH 22/29] mctpd: prevent add-then-remove during SetupEndpoint reassignment Currently, if an endpoint needs reassigning due to a bridge conflict, SetupEndpoint may publish an endpoint, then remove it. This is because we're calling get_endpoint_peer() for the Get Endpoint ID function (which adds the peer), but later check for bridge compatibility. Prevent this by open-coding the parts we need from get_endpoint_peer, and only performing the add once we have a valid peer. This requires a bit of re-work that is particular to bridge allocation in the SetupEndpoint case. Signed-off-by: Jeremy Kerr --- src/mctpd.c | 102 ++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 71 insertions(+), 31 deletions(-) diff --git a/src/mctpd.c b/src/mctpd.c index fd8b9dd..ca39c5d 100644 --- a/src/mctpd.c +++ b/src/mctpd.c @@ -2360,10 +2360,14 @@ static int method_setup_endpoint(sd_bus_message *call, void *data, sd_bus_error *berr) { dest_phys desti = { 0 }, *dest = &desti; + uint8_t ep_type, medium_spec; const char *peer_path = NULL; struct link *link = data; struct ctx *ctx = link->ctx; struct peer *peer = NULL; + bool new = true; + mctp_eid_t eid; + uint32_t net; int rc; dest->ifindex = link->ifindex; @@ -2380,38 +2384,74 @@ static int method_setup_endpoint(sd_bus_message *call, void *data, return sd_bus_error_setf(berr, SD_BUS_ERROR_INVALID_ARGS, "Bad physaddr"); + net = mctp_nl_net_byindex(ctx->nl, dest->ifindex); + if (!net) { + rc = -EINVAL; + goto err; + } + /* Get Endpoint ID */ - rc = get_endpoint_peer(ctx, berr, dest, &peer, NULL); - if (rc >= 0 && peer) { - uint8_t ep_type = GET_MCTP_GET_EID_EP_TYPE(peer->endpoint_type); - if (ep_type == MCTP_GET_EID_EP_TYPE_BRIDGE) { - fprintf(stderr, - "SetupEndpoint, eid %d: Get EID " - "response indicated a bridge with existing " - "EID, reassigning\n", - peer->eid); - remove_peer(peer); - peer = NULL; + rc = query_get_endpoint_id(ctx, dest, &eid, &ep_type, &medium_spec); + if (rc) + goto err; + + /* does it exist already? */ + peer = find_peer_by_phys(ctx, dest); + + /* we have a few cases: + * + * - no peer, no eid + * --> set up both + * - no peer, eid, not a bridge: + * --> create a peer with the given EID + * - no peer, eid, bridge: + * --> reassign, including bridge + * - peer, eid (but not matching) + * --> change EID if possible, or reassign + * - peer, no eid: + * --> remove peer, reassign + */ + + bool is_bridge = GET_MCTP_GET_EID_EP_TYPE(ep_type) == + MCTP_GET_EID_EP_TYPE_BRIDGE; + + printf("%s: peer %p, eid %d, is_bridge %d ep type %x\n", __func__, peer, + eid, is_bridge, ep_type); + + if (peer) { + /* TODO: we could check the bridge allocation through the + * Get Allocation Information op of Allocate Endpoint IDs, + * and be slightly more accurate with persisting the EID... + */ + if (eid && peer->eid == eid && is_bridge == !!peer->pool_size) { + /* all matching: no action required */ + new = false; + goto out; + } + /* we have some difference in EID / bridge config, remove and + * reassign */ + remove_peer(peer); + peer = NULL; + } + + /* simple allocation: try to use existing EID */ + if (eid && !is_bridge) { + rc = add_peer(ctx, dest, eid, net, &peer, false); + if (rc == -EEXIST) { + /* proposed EID already present on a different peer, + * fall back to assigning */ + } else if (rc < 0) { + goto err; } else { - if (ctx->verbose) - fprintf(stderr, - "%s returning from get_endpoint_peer %s\n", - __func__, peer_tostr(peer)); - peer_path = path_from_peer(peer); - if (!peer_path) + peer->endpoint_type = ep_type; + peer->medium_spec = medium_spec; + rc = setup_added_peer(peer); + if (rc) goto err; - return sd_bus_reply_method_return(call, "yisb", - peer->eid, peer->net, - peer_path, 0); + goto out; } - } else if (rc == -EEXIST) { - // EEXISTS is OK, we will assign a new eid instead. - } else if (rc < 0) { - // Unhandled error, fail. - goto err; } - /* Set Endpoint ID */ rc = endpoint_assign_eid(ctx, berr, dest, &peer, 0, true); if (rc < 0) goto err; @@ -2419,14 +2459,14 @@ static int method_setup_endpoint(sd_bus_message *call, void *data, if (peer->pool_size) endpoint_allocate_eids(peer); +out: peer_path = path_from_peer(peer); - if (!peer_path) + if (!peer_path) { + rc = -EPROTO; goto err; - if (ctx->verbose) - fprintf(stderr, "%s returning from endpoint_assign_eid %s\n", - __func__, peer_tostr(peer)); + } return sd_bus_reply_method_return(call, "yisb", peer->eid, peer->net, - peer_path, 1); + peer_path, new); err: set_berr(ctx, rc, berr); From 9a13f15e8687404a00bf76859a29e861b9da7a87 Mon Sep 17 00:00:00 2001 From: Jeremy Kerr Date: Thu, 21 Aug 2025 16:03:05 +0800 Subject: [PATCH 23/29] mctpd: remove unnecessary `else` block The if will return in all paths, no need for the `else`. Signed-off-by: Jeremy Kerr --- src/mctpd.c | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/mctpd.c b/src/mctpd.c index ca39c5d..88b75bd 100644 --- a/src/mctpd.c +++ b/src/mctpd.c @@ -1621,15 +1621,13 @@ static int add_peer(struct ctx *ctx, const dest_phys *dest, mctp_eid_t eid, } *ret_peer = peer; return 0; - } else { - /* only LearnEndpoint methods of au.com.codeconstruct.MCTP.Network1 - * interface will approve peer structure if eid belongs to a bridge - * pool space else never allow. - */ - if (!net_learn & is_eid_in_bridge_pool(n, ctx, eid)) { - return -EEXIST; - } } + /* only LearnEndpoint methods of au.com.codeconstruct.MCTP.Network1 + * interface will approve peer structure if eid belongs to a bridge + * pool space else never allow. + */ + if (!net_learn & is_eid_in_bridge_pool(n, ctx, eid)) + return -EEXIST; if (ctx->num_peers == MAX_PEER_SIZE) return -ENOMEM; From 0460303c41c9570141408a91500d4169235b4456 Mon Sep 17 00:00:00 2001 From: Jeremy Kerr Date: Mon, 25 Aug 2025 11:06:58 +0800 Subject: [PATCH 24/29] mctpd: use boolean operator for bridge pool check This shouldn't be a bitwise test. Signed-off-by: Jeremy Kerr --- src/mctpd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mctpd.c b/src/mctpd.c index 88b75bd..8c6766e 100644 --- a/src/mctpd.c +++ b/src/mctpd.c @@ -1626,7 +1626,7 @@ static int add_peer(struct ctx *ctx, const dest_phys *dest, mctp_eid_t eid, * interface will approve peer structure if eid belongs to a bridge * pool space else never allow. */ - if (!net_learn & is_eid_in_bridge_pool(n, ctx, eid)) + if (!net_learn && is_eid_in_bridge_pool(n, ctx, eid)) return -EEXIST; if (ctx->num_peers == MAX_PEER_SIZE) From 29b403ba0ca605979f56b15aa80cdca441584b9b Mon Sep 17 00:00:00 2001 From: Jeremy Kerr Date: Mon, 25 Aug 2025 11:07:44 +0800 Subject: [PATCH 25/29] mctpd: constify is_eid_in_bridge_pool Guarantee that we're not altering ctx/net data. Signed-off-by: Jeremy Kerr --- src/mctpd.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mctpd.c b/src/mctpd.c index 8c6766e..f7a64e0 100644 --- a/src/mctpd.c +++ b/src/mctpd.c @@ -1584,11 +1584,11 @@ static int endpoint_send_set_endpoint_id(const struct peer *peer, } // Checks if given EID belongs to any bridge's pool range -static bool is_eid_in_bridge_pool(struct net *n, struct ctx *ctx, +static bool is_eid_in_bridge_pool(const struct net *n, const struct ctx *ctx, mctp_eid_t eid) { for (int i = ctx->dyn_eid_min; i <= eid; i++) { - struct peer *peer = n->peers[i]; + const struct peer *peer = n->peers[i]; if (peer && peer->pool_size > 0) { if (eid >= peer->pool_start && eid < peer->pool_start + peer->pool_size) { From 4e6d7db11a9ea02702f05d5941ef6e6bad337499 Mon Sep 17 00:00:00 2001 From: Jeremy Kerr Date: Fri, 22 Aug 2025 09:53:32 +0800 Subject: [PATCH 26/29] mctpd: Rename net_learn argument for add_peer, and explain in comments `net_learn` only specifies the usage scenario, not the behaviour. Signed-off-by: Jeremy Kerr --- src/mctpd.c | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/mctpd.c b/src/mctpd.c index f7a64e0..b3c4c60 100644 --- a/src/mctpd.c +++ b/src/mctpd.c @@ -239,7 +239,7 @@ static int emit_interface_removed(struct link *link); static int emit_net_added(struct ctx *ctx, struct net *net); static int emit_net_removed(struct ctx *ctx, struct net *net); static int add_peer(struct ctx *ctx, const dest_phys *dest, mctp_eid_t eid, - uint32_t net, struct peer **ret_peer, bool net_learn); + uint32_t net, struct peer **ret_peer, bool allow_bridged); static int add_peer_from_addr(struct ctx *ctx, const struct sockaddr_mctp_ext *addr, struct peer **ret_peer); @@ -1600,10 +1600,13 @@ static bool is_eid_in_bridge_pool(const struct net *n, const struct ctx *ctx, return false; } -/* Returns the newly added peer. - * Error is -EEXISTS if it exists */ +/* Returns the newly added peer. If @allow_bridged is set, we do not conflict + * with EIDs that are within bridge pool allocations. + * + * Error is -EEXISTS if it exists + */ static int add_peer(struct ctx *ctx, const dest_phys *dest, mctp_eid_t eid, - uint32_t net, struct peer **ret_peer, bool net_learn) + uint32_t net, struct peer **ret_peer, bool allow_bridged) { struct peer *peer, **tmp; struct net *n; @@ -1622,11 +1625,10 @@ static int add_peer(struct ctx *ctx, const dest_phys *dest, mctp_eid_t eid, *ret_peer = peer; return 0; } - /* only LearnEndpoint methods of au.com.codeconstruct.MCTP.Network1 - * interface will approve peer structure if eid belongs to a bridge - * pool space else never allow. + /* In some cases, we want to allow adding a peer that exists within + * a bridged range - typically when the peer is behind that bridge. */ - if (!net_learn && is_eid_in_bridge_pool(n, ctx, eid)) + if (!allow_bridged && is_eid_in_bridge_pool(n, ctx, eid)) return -EEXIST; if (ctx->num_peers == MAX_PEER_SIZE) From 1805d0be3146402c017f3f734828f75c0b30a84f Mon Sep 17 00:00:00 2001 From: Jeremy Kerr Date: Fri, 22 Aug 2025 11:38:27 +0800 Subject: [PATCH 27/29] mctpd: allow for pool allocations that are smaller than our max limit Currently, we attempt to allocate bridge pool ranges of the size of our max pool configuration setting, and then trim after we know the requested pool size. If the max allocation is not available, we do not provide *any* EID range to the requesting bridge. However, it's entirely likely that the bridge will request a pool that is smaller than our maximum. We should not reject that allocation, as there is space available. Instead of insisting on allocating the max, just pre-allocate the largest space up to the max. When we then learn the bridge pool size, offer the allocation that we made. If this is smaller than the preallocation, we trim. If it is larger, we just offer what is allocated. This changes a failure case, where the tests expect the less-than-max allocation to fail. No need to preserve this behaviour, as we can actually offer a workable pool at this point. Signed-off-by: Jeremy Kerr --- src/mctpd.c | 21 +++------------ tests/test_mctpd.py | 63 ++++++++++++++++++++++++++++++++++++++------- 2 files changed, 57 insertions(+), 27 deletions(-) diff --git a/src/mctpd.c b/src/mctpd.c index b3c4c60..4dc5d35 100644 --- a/src/mctpd.c +++ b/src/mctpd.c @@ -1943,19 +1943,6 @@ static int endpoint_assign_eid(struct ctx *ctx, sd_bus_error *berr, return -EADDRNOTAVAIL; } - /* Only allow complete pools for now. In future we could reserve - * this range, in the assumption that the subsequent pool - * request (in the Set Endpoint ID response) will fit in this - * reservation. - */ - if (alloc.extent < alloc_size) { - warnx("Cannot allocate sufficient EIDs (+pool %d) on net %d for %s" - " (largest span %d at %d)", - alloc_size, net, dest_phys_tostr(dest), - alloc.extent, alloc.start); - alloc.extent = 0; - } - new_eid = alloc.start; rc = add_peer(ctx, dest, new_eid, net, &peer, false); @@ -1983,12 +1970,12 @@ static int endpoint_assign_eid(struct ctx *ctx, sd_bus_error *berr, } if (req_pool_size > peer->pool_size) { - warnx("EID %d: requested pool size (%d) > pool size available (%d)", + warnx("EID %d: requested pool size (%d) > pool size available (%d), limiting.", peer->eid, req_pool_size, peer->pool_size); - req_pool_size = peer->pool_size; + } else { + // peer will likely have requested less than the available range + peer->pool_size = req_pool_size; } - // peer will likely have requested less than the available range - peer->pool_size = req_pool_size; if (!peer->pool_size) peer->pool_start = 0; diff --git a/tests/test_mctpd.py b/tests/test_mctpd.py index 7cd9c0f..4a353ab 100644 --- a/tests/test_mctpd.py +++ b/tests/test_mctpd.py @@ -1105,10 +1105,10 @@ async def test_assign_dynamic_eid_limited_pool(nursery, dbus, sysnet): res = await mctpd.stop_mctpd() assert res == 0 -""" Test that no pool is assigned for requested pool size from - unavailable pool space""" -async def test_assign_dynamic_unavailable_pool(nursery, dbus, sysnet): - (min_dyn_eid, max_dyn_eid) = (8, 12) +""" Test that a limited pool is assigned if we run out of space for a full +allocation""" +async def test_bridge_pool_assign_limited(nursery, dbus, sysnet): + (min_dyn_eid, max_dyn_eid) = (8, 13) config = f""" [bus-owner] dynamic_eid_range = [{min_dyn_eid}, {max_dyn_eid}] @@ -1121,8 +1121,9 @@ async def test_assign_dynamic_unavailable_pool(nursery, dbus, sysnet): ep = mctpd.network.endpoints[0] mctp = await mctpd_mctp_iface_obj(dbus, iface) - # Set up bridged endpoints as undiscovered EID 0 - for i in range(0, 2): + # Set up bridged endpoints as undiscovered EID 0; three bridged EPs, + # which is larger than the available space + for i in range(0, 3): br_ep = Endpoint(iface, bytes(), types=[0, 2]) ep.add_bridged_ep(br_ep) mctpd.network.add_endpoint(br_ep) @@ -1139,10 +1140,10 @@ async def test_assign_dynamic_unavailable_pool(nursery, dbus, sysnet): # dynamic EID assigment for dev1 (eid, _, path, new) = await mctp.call_assign_endpoint(ep.lladdr) assert new - # Interface should not be present for unavailable pool space - with pytest.raises(asyncdbus.errors.InterfaceNotFoundError): - bridge_obj = await dbus.get_proxy_object(MCTPD_C, path) - await bridge_obj.get_interface(MCTPD_ENDPOINT_BRIDGE_I) + assert ep.allocated_pool is not None + # we should have the largest range possible; the 8,9-9 range is smaller + # than the 11,12-13 + assert ep.allocated_pool == (12, 2) res = await mctpd.stop_mctpd() assert res == 0 @@ -1209,3 +1210,45 @@ async def test_assign_without_bridge_range(dbus, sysnet, nursery): assert eid == dyn_eid_min res = await mctpd.stop_mctpd() assert res == 0 + +""" Test that we can still allocate a bridge pool even though we may not have +the maximum EID range available. The bridge pool's full allocation is still +possible, since it is smaller than the configured max""" +async def test_bridge_pool_range_limited(dbus, sysnet, nursery): + # configure for: + # 10: bridge A + # 11-13: bridge A pool + # 14: bridge B + # 15-17: bridge B pool + (dyn_eid_min, dyn_eid_max) = (10, 17) + bridge_downstreams = 3 + # max pool size would consume more than half of the range, so bridge B + # cannot be allocated this max + max_pool_size = 5 + config = f""" + [bus-owner] + dynamic_eid_range = [{dyn_eid_min}, {dyn_eid_max}] + max_pool_size = {max_pool_size} + """ + + mctpd = MctpdWrapper(dbus, sysnet, config = config) + await mctpd.start_mctpd(nursery) + + iface = mctpd.system.interfaces[0] + bridges = [ + Endpoint(iface, bytes([0x30])), + Endpoint(iface, bytes([0x31])), + ] + for bridge in bridges: + mctpd.network.add_endpoint(bridge) + for i in range(bridge_downstreams): + bridge.add_bridged_ep(Endpoint(iface, bytes())) + + iface_obj = await mctpd_mctp_iface_obj(dbus, iface) + for bridge in bridges: + (eid, _, _, _) = await iface_obj.call_assign_endpoint(bridge.lladdr) + assert bridge.allocated_pool is not None + assert bridge.allocated_pool[1] == 3 + + res = await mctpd.stop_mctpd() + assert res == 0 From 64de199bdf5c2feb3815de1e6366c083633e866e Mon Sep 17 00:00:00 2001 From: Jeremy Kerr Date: Mon, 25 Aug 2025 08:50:58 +0800 Subject: [PATCH 28/29] tests: remove unused Endpoint.pool_size member Signed-off-by: Jeremy Kerr --- tests/mctpenv/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/mctpenv/__init__.py b/tests/mctpenv/__init__.py index 8321ab1..6a082b1 100644 --- a/tests/mctpenv/__init__.py +++ b/tests/mctpenv/__init__.py @@ -303,7 +303,6 @@ def __init__(self, iface, lladdr, ep_uuid = None, eid = 0, types = None): self.eid = eid self.types = types or [0] self.bridged_eps = [] - self.pool_size = 0 self.allocated_pool = None # or (start, size) # keyed by (type, type-specific-instance) From f4870bc00a1bbe1b63a63ee92877d35533b4e531 Mon Sep 17 00:00:00 2001 From: Jeremy Kerr Date: Fri, 22 Aug 2025 11:57:03 +0800 Subject: [PATCH 29/29] CHANGELOG: Add entry for bridge support Signed-off-by: Jeremy Kerr --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7fddfc5..d86c85a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). 5. When in endpoint mode, `mctpd` now handles to Set Endpoint ID messages, assigning an EID to local interfaces. +6. `mctpd` now handles downstream MCTP bridges, which may request an EID + pool from their Set Endpoint ID response. It will attempt an EID allocation + from the dynamic range, and pass this to the bridge using a subsequent + Allocate Endpoint IDs command. + ## [2.2] - 2025-07-28 ### Fixed