From cb182beba134f7cfbde88787813868ce44420a40 Mon Sep 17 00:00:00 2001 From: Nidhin MS Date: Thu, 19 Feb 2026 11:17:12 +0530 Subject: [PATCH] mctpd: Add VendorDefinedMessageTypes property Implement Get VDM Support (0x06) control command to query vendor defined message capabilities from peers. Expose via D-Bus property a(yvq) containing format, vendor_id (PCIe 16-bit or IANA 32-bit), and command set. Includes positive test for both formats and negative tests for invalid responses (wrong lengths, invalid format, unsupported command). Signed-off-by: Nidhin MS --- src/mctp-control-spec.h | 14 ++- src/mctpd.c | 205 ++++++++++++++++++++++++++++++++++++-- tests/mctpenv/__init__.py | 24 ++++- tests/test_mctpd.py | 112 +++++++++++++++++++++ 4 files changed, 339 insertions(+), 16 deletions(-) diff --git a/src/mctp-control-spec.h b/src/mctp-control-spec.h index 493ac578..0b345995 100644 --- a/src/mctp-control-spec.h +++ b/src/mctp-control-spec.h @@ -114,14 +114,20 @@ struct mctp_ctrl_resp_get_vdm_support { uint8_t completion_code; uint8_t vendor_id_set_selector; uint8_t vendor_id_format; - union { - uint16_t vendor_id_data_pcie; - uint32_t vendor_id_data_iana; - }; /* following bytes are dependent on vendor id format * and shall be interpreted by appropriate binding handler */ } __attribute__((__packed__)); +struct mctp_vdm_pcie_data { + uint16_t vendor_id; + uint16_t cmd_set; +} __attribute__((__packed__)); + +struct mctp_vdm_iana_data { + uint32_t enterprise_number; + uint16_t cmd_set; +} __attribute__((__packed__)); + struct mctp_pci_ctrl_resp_get_vdm_support { struct mctp_ctrl_msg_hdr ctrl_hdr; uint8_t completion_code; diff --git a/src/mctpd.c b/src/mctpd.c index a7036ad5..06745b84 100644 --- a/src/mctpd.c +++ b/src/mctpd.c @@ -148,6 +148,8 @@ struct link { struct ctx *ctx; }; +struct vdm_type_support; + struct peer { uint32_t net; mctp_eid_t eid; @@ -185,6 +187,9 @@ struct peer { uint8_t *message_types; size_t num_message_types; + struct vdm_type_support *vdm_types; + size_t num_vdm_types; + // From Get Endpoint ID uint8_t endpoint_type; uint8_t medium_spec; @@ -1060,8 +1065,9 @@ handle_control_get_vdm_type_support(struct ctx *ctx, int sd, struct mctp_ctrl_resp_get_vdm_support *resp = NULL; struct mctp_ctrl_cmd_get_vdm_support *req = NULL; size_t resp_len, max_rsp_len, vdm_count; + struct mctp_vdm_pcie_data *vdm_pcie; + struct mctp_vdm_iana_data *vdm_iana; struct vdm_type_support *cur_vdm; - uint16_t *cmd_type_ptr; uint8_t *resp_buf; int rc; @@ -1073,7 +1079,7 @@ handle_control_get_vdm_type_support(struct ctx *ctx, int sd, req = (void *)buf; vdm_count = ctx->num_supported_vdm_types; // Allocate space for 32 bit VID + 16 bit cmd set - max_rsp_len = sizeof(*resp) + sizeof(uint16_t); + max_rsp_len = sizeof(*resp) + sizeof(struct mctp_vdm_iana_data); resp_len = max_rsp_len; resp_buf = malloc(max_rsp_len); if (!resp_buf) { @@ -1081,7 +1087,6 @@ handle_control_get_vdm_type_support(struct ctx *ctx, int sd, return -ENOMEM; } resp = (void *)resp_buf; - cmd_type_ptr = (uint16_t *)(resp + 1); mctp_ctrl_msg_hdr_init_resp(&resp->ctrl_hdr, req->ctrl_hdr); if (req->vendor_id_set_selector >= vdm_count) { @@ -1103,17 +1108,17 @@ handle_control_get_vdm_type_support(struct ctx *ctx, int sd, resp->vendor_id_format = cur_vdm->format; if (cur_vdm->format == VID_FORMAT_PCIE) { - // 4 bytes was reserved for VID, but PCIe VID uses only 2 bytes. - cmd_type_ptr--; - resp_len = max_rsp_len - sizeof(uint16_t); - resp->vendor_id_data_pcie = - htobe16(cur_vdm->vendor_id.pcie); + vdm_pcie = (void *)(resp + 1); + resp_len = sizeof(*resp) + + sizeof(struct mctp_vdm_pcie_data); + vdm_pcie->vendor_id = htobe16(cur_vdm->vendor_id.pcie); + vdm_pcie->cmd_set = htobe16(cur_vdm->cmd_set); } else { - resp->vendor_id_data_iana = + vdm_iana = (void *)(resp + 1); + vdm_iana->enterprise_number = htobe32(cur_vdm->vendor_id.iana); + vdm_iana->cmd_set = htobe16(cur_vdm->cmd_set); } - - *cmd_type_ptr = htobe16(cur_vdm->cmd_set); } rc = reply_message(ctx, sd, resp, resp_len, addr); @@ -2085,6 +2090,7 @@ static int remove_peer(struct peer *peer) n->peers[peer->eid] = NULL; free(peer->message_types); + free(peer->vdm_types); free(peer->uuid); for (idx = 0; idx < ctx->num_peers; idx++) { @@ -2127,6 +2133,7 @@ static void free_peers(struct ctx *ctx) for (size_t i = 0; i < ctx->num_peers; i++) { struct peer *peer = ctx->peers[i]; free(peer->message_types); + free(peer->vdm_types); free(peer->uuid); free(peer->path); free(peer->bridge_ep_poll.sources); @@ -2585,6 +2592,106 @@ static int query_get_peer_msgtypes(struct peer *peer) return rc; } +static int query_get_peer_vdm_types(struct peer *peer) +{ + struct vdm_type_support *cur_vdm_type, *new_vdm, *vdm_types = NULL; + size_t buf_size, expect_size, new_size, num_vdm_types = 0; + struct mctp_ctrl_resp_get_vdm_support *resp = NULL; + struct mctp_ctrl_cmd_get_vdm_support req; + struct mctp_vdm_pcie_data *vdm_pcie; + struct mctp_vdm_iana_data *vdm_iana; + struct sockaddr_mctp_ext addr; + uint8_t iid, fmt, *buf = NULL; + int rc; + + req.ctrl_hdr.command_code = MCTP_CTRL_CMD_GET_VENDOR_MESSAGE_SUPPORT; + req.vendor_id_set_selector = 0; + + while (true) { + iid = mctp_next_iid(peer->ctx); + + mctp_ctrl_msg_hdr_init_req( + &req.ctrl_hdr, iid, + MCTP_CTRL_CMD_GET_VENDOR_MESSAGE_SUPPORT); + if (buf) { + free(buf); + buf = NULL; + } + rc = endpoint_query_peer(peer, MCTP_CTRL_HDR_MSG_TYPE, &req, + sizeof(req), &buf, &buf_size, &addr); + if (rc < 0) + break; + + expect_size = sizeof(*resp); + rc = mctp_ctrl_validate_response( + buf, buf_size, expect_size, peer_tostr_short(peer), iid, + MCTP_CTRL_CMD_GET_VENDOR_MESSAGE_SUPPORT); + if (rc) + break; + + resp = (void *)buf; + fmt = resp->vendor_id_format; + if (fmt == MCTP_GET_VDM_SUPPORT_PCIE_FORMAT_ID) { + expect_size = sizeof(*resp) + + sizeof(struct mctp_vdm_pcie_data); + } else if (fmt == MCTP_GET_VDM_SUPPORT_IANA_FORMAT_ID) { + expect_size = sizeof(*resp) + + sizeof(struct mctp_vdm_iana_data); + } else { + warnx("%s: bad vendor_id_format 0x%02x dest %s", + __func__, fmt, peer_tostr(peer)); + rc = -ENOMSG; + break; + } + if (buf_size != expect_size) { + warnx("%s: bad reply length. got %zu, expected %zu dest %s", + __func__, buf_size, expect_size, + peer_tostr(peer)); + rc = -ENOMSG; + break; + } + + new_size = + (num_vdm_types + 1) * sizeof(struct vdm_type_support); + new_vdm = realloc(vdm_types, new_size); + if (!new_vdm) { + rc = -ENOMEM; + break; + } + vdm_types = new_vdm; + cur_vdm_type = vdm_types + num_vdm_types; + cur_vdm_type->format = fmt; + + if (fmt == MCTP_GET_VDM_SUPPORT_IANA_FORMAT_ID) { + vdm_iana = (struct mctp_vdm_iana_data *)(resp + 1); + cur_vdm_type->vendor_id.iana = + be32toh(vdm_iana->enterprise_number); + cur_vdm_type->cmd_set = be16toh(vdm_iana->cmd_set); + } else { + vdm_pcie = (struct mctp_vdm_pcie_data *)(resp + 1); + cur_vdm_type->vendor_id.pcie = + be16toh(vdm_pcie->vendor_id); + cur_vdm_type->cmd_set = be16toh(vdm_pcie->cmd_set); + } + num_vdm_types++; + if (resp->vendor_id_set_selector == + MCTP_GET_VDM_SUPPORT_NO_MORE_CAP_SET) { + peer->vdm_types = vdm_types; + vdm_types = NULL; + peer->num_vdm_types = num_vdm_types; + rc = 0; + break; + } + + /* Use the next selector from the response. 0xFF indicates no more entries */ + req.vendor_id_set_selector = resp->vendor_id_set_selector; + } + + free(buf); + free(vdm_types); + return rc; +} + static int peer_set_uuid(struct peer *peer, const uint8_t uuid[16]) { if (!peer->uuid) { @@ -3019,6 +3126,7 @@ static int method_learn_endpoint(sd_bus_message *call, void *data, static int query_peer_properties(struct peer *peer) { const unsigned int max_retries = 4; + bool supports_vdm = false; int rc; for (unsigned int i = 0; i < max_retries; i++) { @@ -3047,6 +3155,41 @@ static int query_peer_properties(struct peer *peer) } } + for (unsigned int i = 0; i < peer->num_message_types; i++) { + if (peer->message_types[i] == + MCTP_GET_VDM_SUPPORT_IANA_FORMAT_ID || + peer->message_types[i] == + MCTP_GET_VDM_SUPPORT_PCIE_FORMAT_ID) { + supports_vdm = true; + break; + } + } + + if (supports_vdm) { + for (unsigned int i = 0; i < max_retries; i++) { + rc = query_get_peer_vdm_types(peer); + + if (rc == 0) + break; + + // On timeout, retry + if (rc == -ETIMEDOUT) { + if (peer->ctx->verbose) + warnx("Retrying to get vendor message types for %s. Attempt %u", + peer_tostr(peer), i + 1); + rc = 0; + continue; + } + + if (rc < 0) { + warnx("Error getting vendor message types for %s. Ignoring error %d %s", + peer_tostr(peer), rc, strerror(-rc)); + rc = 0; + break; + } + } + } + for (unsigned int i = 0; i < max_retries; i++) { rc = query_get_peer_uuid(peer); @@ -3939,6 +4082,41 @@ static int bus_endpoint_get_prop(sd_bus *bus, const char *path, rc = sd_bus_message_append_array(reply, 'y', peer->message_types, peer->num_message_types); + } else if (strcmp(property, "VendorDefinedMessageTypes") == 0) { + rc = sd_bus_message_open_container(reply, 'a', "(yvq)"); + if (rc < 0) + return rc; + + for (size_t i = 0; i < peer->num_vdm_types; i++) { + struct vdm_type_support *vdm = &peer->vdm_types[i]; + rc = sd_bus_message_open_container(reply, 'r', "yvq"); + if (rc < 0) + return rc; + + rc = sd_bus_message_append(reply, "y", vdm->format); + if (rc < 0) + return rc; + + if (vdm->format == VID_FORMAT_PCIE) { + rc = sd_bus_message_append(reply, "v", "q", + vdm->vendor_id.pcie); + } else { + rc = sd_bus_message_append(reply, "v", "u", + vdm->vendor_id.iana); + } + if (rc < 0) + return rc; + + rc = sd_bus_message_append(reply, "q", vdm->cmd_set); + if (rc < 0) + return rc; + + rc = sd_bus_message_close_container(reply); + if (rc < 0) + return rc; + } + + rc = sd_bus_message_close_container(reply); } else if (strcmp(property, "UUID") == 0 && peer->uuid) { const char *s = dfree(bytes_to_uuid(peer->uuid)); rc = sd_bus_message_append(reply, "s", s); @@ -4128,6 +4306,11 @@ static const sd_bus_vtable bus_endpoint_obmc_vtable[] = { bus_endpoint_get_prop, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("VendorDefinedMessageTypes", + "a(yvq)", + bus_endpoint_get_prop, + 0, + SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_VTABLE_END }; diff --git a/tests/mctpenv/__init__.py b/tests/mctpenv/__init__.py index fb7d8a01..d1de8d00 100644 --- a/tests/mctpenv/__init__.py +++ b/tests/mctpenv/__init__.py @@ -348,12 +348,15 @@ def to_buf(self): class Endpoint: - def __init__(self, iface, lladdr, ep_uuid=None, eid=0, types=None): + def __init__( + self, iface, lladdr, ep_uuid=None, eid=0, types=None, vdm_msg_types=None + ): self.iface = iface self.lladdr = lladdr self.uuid = ep_uuid or uuid.uuid1() self.eid = eid self.types = types or [0] + self.vdm_msg_types = vdm_msg_types or [] self.bridged_eps = [] self.allocated_pool = None # or (start, size) @@ -428,6 +431,25 @@ async def handle_mctp_control(self, sock, addr, data): data = bytes(hdr + [0x00, len(types)] + types) await sock.send(raddr, data) + elif opcode == 6: + # Get Vendor Defined Message Support + vdm_support = self.vdm_msg_types + selector = data[2] + if selector >= len(vdm_support): + await sock.send(raddr, bytes(hdr + [0x02])) + return + vdm_format, vendor_id, cmd_set = vdm_support[selector] + next_selector = ( + 0xFF if selector == (len(vdm_support) - 1) else selector + 1 + ) + resp = bytes(hdr + [0x00, next_selector, vdm_format]) + if vdm_format == 0: + resp += vendor_id.to_bytes(2, 'big') + elif vdm_format == 1: + resp += vendor_id.to_bytes(4, 'big') + resp += cmd_set.to_bytes(2, 'big') + await sock.send(raddr, resp) + elif opcode == 8: # Allocate Endpoint IDs (_, _, _, pool_size, pool_start) = data diff --git a/tests/test_mctpd.py b/tests/test_mctpd.py index ab2dce65..0bee9565 100644 --- a/tests/test_mctpd.py +++ b/tests/test_mctpd.py @@ -637,6 +637,118 @@ async def test_query_message_types(dbus, mctpd): assert ep_types == query_types +async def test_query_vdm_types(dbus, mctpd): + """Test that VendorDefinedMessageTypes is queried and populated.""" + iface = mctpd.system.interfaces[0] + vdm_support = [[0, 0x1234, 0x5678], [1, 0xABCDEF12, 0x3456]] + ep = Endpoint(iface, bytes([0x1E]), eid=15, vdm_msg_types=vdm_support) + mctpd.network.add_endpoint(ep) + + mctp = await mctpd_mctp_iface_obj(dbus, iface) + (eid, net, path, new) = await mctp.call_learn_endpoint(ep.lladdr) + + assert eid == ep.eid + + ep_obj = await mctpd_mctp_endpoint_common_obj(dbus, path) + + # Query VendorDefinedMessageTypes property + vdm_types = list(await ep_obj.get_vendor_defined_message_types()) + + # Verify we got 2 VDM types + assert len(vdm_types) == 2 + + # Verify first VDM type: PCIe format (0), VID 0x1234, cmd_set 0x5678 + assert vdm_types[0][0] == 0 # format: PCIe + assert ( + vdm_types[0][1].value == 0x1234 + ) # vendor_id (variant containing uint16) + assert vdm_types[0][2] == 0x5678 # cmd_set + + # Verify second VDM type: IANA format (1), VID 0xabcdef12, cmd_set 0x3456 + assert vdm_types[1][0] == 1 # format: IANA + assert ( + vdm_types[1][1].value == 0xABCDEF12 + ) # vendor_id (variant containing uint32) + assert vdm_types[1][2] == 0x3456 # cmd_set + + +class InvalidVDMEndpointBase(Endpoint): + async def handle_mctp_control(self, sock, addr, data): + flags, opcode = data[0:2] + if opcode != 0x06: + return await super().handle_mctp_control(sock, addr, data) + iid = flags & 0x1F + raddr = MCTPSockAddr.for_ep_resp(self, addr, sock.addr_ext) + hdr = [iid, opcode] + resp = hdr + [0x00, 0xFF] + self.get_invalid_vdm_data() + await sock.send(raddr, bytes(resp)) + + def get_invalid_vdm_data(self): + raise NotImplementedError + + +class InvalidPCIeLengthEndpoint(InvalidVDMEndpointBase): + def get_invalid_vdm_data(self): + # Format 0 (PCIe) but send 1 bytes for vendor_id (invalid) + return [0, 0x12, 0x78, 0x90] + + +class InvalidIANALengthEndpoint(InvalidVDMEndpointBase): + def get_invalid_vdm_data(self): + # Format 1 (IANA) but send 3 bytes for vendor_id (invalid) + return [1, 0xAB, 0xCD, 0xEF, 0x34, 0x56] + + +class InvalidFormatEndpoint(InvalidVDMEndpointBase): + def get_invalid_vdm_data(self): + # Format 2 (invalid - only 0 and 1 are valid) + return [2, 0x12, 0x34, 0x56, 0x78] + + +async def _assert_vdm_types_empty(dbus, mctp, ep): + (eid, _, path, _) = await mctp.call_learn_endpoint(ep.lladdr) + assert eid == ep.eid + ep_obj = await mctpd_mctp_endpoint_common_obj(dbus, path) + vdm_types = list(await ep_obj.get_vendor_defined_message_types()) + assert len(vdm_types) == 0 + + +async def test_query_vdm_types_invalid_pcie_length(dbus, mctpd): + iface = mctpd.system.interfaces[0] + mctp = await mctpd_mctp_iface_obj(dbus, iface) + + ep = InvalidPCIeLengthEndpoint(iface, bytes([0x1E]), eid=15) + mctpd.network.add_endpoint(ep) + await _assert_vdm_types_empty(dbus, mctp, ep) + + +async def test_query_vdm_types_invalid_iana_length(dbus, mctpd): + iface = mctpd.system.interfaces[0] + mctp = await mctpd_mctp_iface_obj(dbus, iface) + + ep = InvalidIANALengthEndpoint(iface, bytes([0x1F]), eid=16) + mctpd.network.add_endpoint(ep) + await _assert_vdm_types_empty(dbus, mctp, ep) + + +async def test_query_vdm_types_invalid_format(dbus, mctpd): + iface = mctpd.system.interfaces[0] + mctp = await mctpd_mctp_iface_obj(dbus, iface) + + ep = InvalidFormatEndpoint(iface, bytes([0x20]), eid=17) + mctpd.network.add_endpoint(ep) + await _assert_vdm_types_empty(dbus, mctp, ep) + + +async def test_query_vdm_types_unsupported(dbus, mctpd): + iface = mctpd.system.interfaces[0] + mctp = await mctpd_mctp_iface_obj(dbus, iface) + + ep = Endpoint(iface, bytes([0x21]), eid=18, vdm_msg_types=None) + mctpd.network.add_endpoint(ep) + await _assert_vdm_types_empty(dbus, mctp, ep) + + async def test_network_local_eids_single(dbus, mctpd): """Network1.LocalEIDs should reflect locally-assigned EID state""" iface = mctpd.system.interfaces[0]