Skip to content

Commit b18a8cd

Browse files
committed
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(yvu) 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 <nidhin.ms@intel.com>
1 parent ce42b09 commit b18a8cd

2 files changed

Lines changed: 320 additions & 0 deletions

File tree

src/mctpd.c

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,8 @@ struct link {
140140
struct ctx *ctx;
141141
};
142142

143+
struct vdm_type_support;
144+
143145
struct peer {
144146
uint32_t net;
145147
mctp_eid_t eid;
@@ -177,6 +179,9 @@ struct peer {
177179
uint8_t *message_types;
178180
size_t num_message_types;
179181

182+
struct vdm_type_support *vdm_types;
183+
size_t num_vdm_types;
184+
180185
// From Get Endpoint ID
181186
uint8_t endpoint_type;
182187
uint8_t medium_spec;
@@ -2013,6 +2018,7 @@ static int remove_peer(struct peer *peer)
20132018

20142019
n->peers[peer->eid] = NULL;
20152020
free(peer->message_types);
2021+
free(peer->vdm_types);
20162022
free(peer->uuid);
20172023

20182024
for (idx = 0; idx < ctx->num_peers; idx++) {
@@ -2512,6 +2518,108 @@ static int query_get_peer_msgtypes(struct peer *peer)
25122518
return rc;
25132519
}
25142520

2521+
static int query_get_peer_vdm_types(struct peer *peer)
2522+
{
2523+
struct mctp_ctrl_resp_get_vdm_support *resp = NULL;
2524+
struct vdm_type_support *cur_vdm_type, *new_vdm;
2525+
struct mctp_ctrl_cmd_get_vdm_support req;
2526+
size_t buf_size, expect_size, new_size;
2527+
struct sockaddr_mctp_ext addr;
2528+
uint8_t *buf = NULL;
2529+
uint16_t *cmd_set;
2530+
uint8_t iid;
2531+
int rc;
2532+
2533+
peer->num_vdm_types = 0;
2534+
free(peer->vdm_types);
2535+
peer->vdm_types = NULL;
2536+
2537+
req.ctrl_hdr.command_code = MCTP_CTRL_CMD_GET_VENDOR_MESSAGE_SUPPORT;
2538+
req.vendor_id_set_selector = 0;
2539+
2540+
while (req.vendor_id_set_selector !=
2541+
MCTP_GET_VDM_SUPPORT_NO_MORE_CAP_SET) {
2542+
iid = mctp_next_iid(peer->ctx);
2543+
2544+
mctp_ctrl_msg_hdr_init_req(
2545+
&req.ctrl_hdr, iid,
2546+
MCTP_CTRL_CMD_GET_VENDOR_MESSAGE_SUPPORT);
2547+
rc = endpoint_query_peer(peer, MCTP_CTRL_HDR_MSG_TYPE, &req,
2548+
sizeof(req), &buf, &buf_size, &addr);
2549+
if (rc < 0)
2550+
goto out;
2551+
2552+
/* Check for minimum length of PCIe VDM*/
2553+
expect_size = sizeof(*resp);
2554+
rc = mctp_ctrl_validate_response(
2555+
buf, buf_size, expect_size, peer_tostr_short(peer), iid,
2556+
MCTP_CTRL_CMD_GET_VENDOR_MESSAGE_SUPPORT);
2557+
if (rc)
2558+
goto out;
2559+
2560+
resp = (void *)buf;
2561+
if (resp->vendor_id_format !=
2562+
MCTP_GET_VDM_SUPPORT_PCIE_FORMAT_ID &&
2563+
resp->vendor_id_format !=
2564+
MCTP_GET_VDM_SUPPORT_IANA_FORMAT_ID) {
2565+
warnx("%s: bad vendor_id_format 0x%02x dest %s",
2566+
__func__, resp->vendor_id_format,
2567+
peer_tostr(peer));
2568+
rc = -ENOMSG;
2569+
goto out;
2570+
}
2571+
2572+
if (resp->vendor_id_format ==
2573+
MCTP_GET_VDM_SUPPORT_IANA_FORMAT_ID) {
2574+
/* Accomodate 2 bytes for IANA VID */
2575+
expect_size += sizeof(uint16_t);
2576+
}
2577+
2578+
if (buf_size != expect_size) {
2579+
warnx("%s: bad reply length. got %zu, expected %zu dest %s",
2580+
__func__, buf_size, expect_size,
2581+
peer_tostr(peer));
2582+
rc = -ENOMSG;
2583+
goto out;
2584+
}
2585+
2586+
new_size = (peer->num_vdm_types + 1) *
2587+
sizeof(struct vdm_type_support);
2588+
new_vdm = realloc(peer->vdm_types, new_size);
2589+
if (!new_vdm) {
2590+
rc = -ENOMEM;
2591+
goto out;
2592+
}
2593+
peer->vdm_types = new_vdm;
2594+
cur_vdm_type = peer->vdm_types + peer->num_vdm_types;
2595+
cur_vdm_type->format = resp->vendor_id_format;
2596+
2597+
if (resp->vendor_id_format ==
2598+
MCTP_GET_VDM_SUPPORT_IANA_FORMAT_ID) {
2599+
cur_vdm_type->vendor_id.iana =
2600+
be32toh(resp->vendor_id_data_iana);
2601+
} else {
2602+
cur_vdm_type->vendor_id.pcie =
2603+
be16toh(resp->vendor_id_data_pcie);
2604+
}
2605+
// Assume IANA and adjust if PCIE
2606+
cmd_set = (uint16_t *)(resp + 1);
2607+
if (resp->vendor_id_format ==
2608+
MCTP_GET_VDM_SUPPORT_PCIE_FORMAT_ID) {
2609+
cmd_set--;
2610+
}
2611+
cur_vdm_type->cmd_set = be16toh(*cmd_set);
2612+
peer->num_vdm_types++;
2613+
2614+
/* Use the next selector from the response. 0xFF indicates no more entries */
2615+
req.vendor_id_set_selector = resp->vendor_id_set_selector;
2616+
}
2617+
rc = 0;
2618+
out:
2619+
free(buf);
2620+
return rc;
2621+
}
2622+
25152623
static int peer_set_uuid(struct peer *peer, const uint8_t uuid[16])
25162624
{
25172625
if (!peer->uuid) {
@@ -2946,6 +3054,7 @@ static int method_learn_endpoint(sd_bus_message *call, void *data,
29463054
static int query_peer_properties(struct peer *peer)
29473055
{
29483056
const unsigned int max_retries = 4;
3057+
bool supports_vdm = false;
29493058
int rc;
29503059

29513060
for (unsigned int i = 0; i < max_retries; i++) {
@@ -2974,6 +3083,39 @@ static int query_peer_properties(struct peer *peer)
29743083
}
29753084
}
29763085

3086+
for (unsigned int i = 0; i < peer->num_message_types; i++) {
3087+
if (peer->message_types[i] ==
3088+
MCTP_GET_VDM_SUPPORT_IANA_FORMAT_ID ||
3089+
peer->message_types[i] ==
3090+
MCTP_GET_VDM_SUPPORT_PCIE_FORMAT_ID) {
3091+
supports_vdm = true;
3092+
break;
3093+
}
3094+
}
3095+
3096+
for (unsigned int i = 0; supports_vdm && i < max_retries; i++) {
3097+
rc = query_get_peer_vdm_types(peer);
3098+
// Success
3099+
if (rc == 0)
3100+
break;
3101+
3102+
// On timeout, retry
3103+
if (rc == -ETIMEDOUT) {
3104+
if (peer->ctx->verbose)
3105+
warnx("Retrying to get vendor message types for %s. Attempt %u",
3106+
peer_tostr(peer), i + 1);
3107+
rc = 0;
3108+
continue;
3109+
}
3110+
3111+
if (rc < 0) {
3112+
warnx("Error getting vendor message types for %s. Ignoring error %d %s",
3113+
peer_tostr(peer), rc, strerror(-rc));
3114+
rc = 0;
3115+
break;
3116+
}
3117+
}
3118+
29773119
for (unsigned int i = 0; i < max_retries; i++) {
29783120
rc = query_get_peer_uuid(peer);
29793121

@@ -3866,6 +4008,42 @@ static int bus_endpoint_get_prop(sd_bus *bus, const char *path,
38664008
rc = sd_bus_message_append_array(reply, 'y',
38674009
peer->message_types,
38684010
peer->num_message_types);
4011+
} else if (strcmp(property, "VendorDefinedMessageTypes") == 0) {
4012+
rc = sd_bus_message_open_container(reply, 'a', "(yvu)");
4013+
if (rc < 0)
4014+
return rc;
4015+
4016+
for (size_t i = 0; i < peer->num_vdm_types; i++) {
4017+
struct vdm_type_support *vdm = &peer->vdm_types[i];
4018+
rc = sd_bus_message_open_container(reply, 'r', "yvu");
4019+
if (rc < 0)
4020+
return rc;
4021+
4022+
rc = sd_bus_message_append(reply, "y", vdm->format);
4023+
if (rc < 0)
4024+
return rc;
4025+
4026+
if (vdm->format == VID_FORMAT_PCIE) {
4027+
rc = sd_bus_message_append(reply, "v", "q",
4028+
vdm->vendor_id.pcie);
4029+
} else {
4030+
rc = sd_bus_message_append(reply, "v", "u",
4031+
vdm->vendor_id.iana);
4032+
}
4033+
if (rc < 0)
4034+
return rc;
4035+
4036+
rc = sd_bus_message_append(reply, "u",
4037+
(uint32_t)vdm->cmd_set);
4038+
if (rc < 0)
4039+
return rc;
4040+
4041+
rc = sd_bus_message_close_container(reply);
4042+
if (rc < 0)
4043+
return rc;
4044+
}
4045+
4046+
rc = sd_bus_message_close_container(reply);
38694047
} else if (strcmp(property, "UUID") == 0 && peer->uuid) {
38704048
const char *s = dfree(bytes_to_uuid(peer->uuid));
38714049
rc = sd_bus_message_append(reply, "s", s);
@@ -4055,6 +4233,11 @@ static const sd_bus_vtable bus_endpoint_obmc_vtable[] = {
40554233
bus_endpoint_get_prop,
40564234
0,
40574235
SD_BUS_VTABLE_PROPERTY_CONST),
4236+
SD_BUS_PROPERTY("VendorDefinedMessageTypes",
4237+
"a(yvu)",
4238+
bus_endpoint_get_prop,
4239+
0,
4240+
SD_BUS_VTABLE_PROPERTY_CONST),
40584241
SD_BUS_VTABLE_END
40594242
};
40604243

tests/test_mctpd.py

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -596,6 +596,143 @@ async def test_query_message_types(dbus, mctpd):
596596

597597
assert ep_types == query_types
598598

599+
""" Test that VendorDefinedMessageTypes property is queried and populated correctly """
600+
async def test_query_vdm_types(dbus, mctpd):
601+
class VDMEndpoint(Endpoint):
602+
async def handle_mctp_control(self, sock, addr, data):
603+
flags, opcode = data[0:2]
604+
if opcode != 0x06:
605+
return await super().handle_mctp_control(sock, addr, data)
606+
vdm_support = [[0, 0x1234, 0x5678], [1, 0xabcdef12, 0x3456]]
607+
iid = flags & 0x1f
608+
raddr = MCTPSockAddr.for_ep_resp(self, addr, sock.addr_ext)
609+
hdr = [iid, opcode]
610+
selector = data[2]
611+
if selector >= len(vdm_support):
612+
await sock.send(raddr, bytes(hdr + [0x02]))
613+
return
614+
cur_vdm = vdm_support[selector]
615+
selector = 0xFF if selector == (len(vdm_support) - 1) else selector + 1
616+
resp = hdr + [0x00, selector, cur_vdm[0]]
617+
if cur_vdm[0] == 0:
618+
resp = resp + list(cur_vdm[1].to_bytes(2, 'big'))
619+
else:
620+
resp = resp + list(cur_vdm[1].to_bytes(4, 'big'))
621+
resp = resp + list(cur_vdm[2].to_bytes(2, 'big'))
622+
await sock.send(raddr, bytes(resp))
623+
624+
iface = mctpd.system.interfaces[0]
625+
ep = VDMEndpoint(iface, bytes([0x1e]), eid = 15)
626+
mctpd.network.add_endpoint(ep)
627+
628+
mctp = await mctpd_mctp_iface_obj(dbus, iface)
629+
(eid, net, path, new) = await mctp.call_learn_endpoint(ep.lladdr)
630+
631+
assert eid == ep.eid
632+
633+
ep_obj = await mctpd_mctp_endpoint_common_obj(dbus, path)
634+
635+
# Query VendorDefinedMessageTypes property
636+
vdm_types = list(await ep_obj.get_vendor_defined_message_types())
637+
638+
# Verify we got 2 VDM types
639+
assert len(vdm_types) == 2
640+
641+
# Verify first VDM type: PCIe format (0), VID 0x1234, cmd_set 0x5678
642+
assert vdm_types[0][0] == 0 # format: PCIe
643+
assert vdm_types[0][1].value == 0x1234 # vendor_id (variant containing uint16)
644+
assert vdm_types[0][2] == 0x5678 # cmd_set
645+
646+
# Verify second VDM type: IANA format (1), VID 0xabcdef12, cmd_set 0x3456
647+
assert vdm_types[1][0] == 1 # format: IANA
648+
assert vdm_types[1][1].value == 0xabcdef12 # vendor_id (variant containing uint32)
649+
assert vdm_types[1][2] == 0x3456 # cmd_set
650+
651+
""" Test VDM query with invalid responses """
652+
async def test_query_vdm_types_invalid(dbus, mctpd):
653+
class InvalidVDMEndpointBase(Endpoint):
654+
async def handle_mctp_control(self, sock, addr, data):
655+
flags, opcode = data[0:2]
656+
if opcode != 0x06:
657+
return await super().handle_mctp_control(sock, addr, data)
658+
iid = flags & 0x1f
659+
raddr = MCTPSockAddr.for_ep_resp(self, addr, sock.addr_ext)
660+
hdr = [iid, opcode]
661+
selector = data[2]
662+
if selector != 0:
663+
await sock.send(raddr, bytes(hdr + [0x02]))
664+
return
665+
resp = hdr + [0x00, 0xFF] + self.get_invalid_vdm_data()
666+
await sock.send(raddr, bytes(resp))
667+
668+
def get_invalid_vdm_data(self):
669+
raise NotImplementedError
670+
671+
class InvalidPCIeLengthEndpoint(InvalidVDMEndpointBase):
672+
def get_invalid_vdm_data(self):
673+
# Format 0 (PCIe) but send 3 bytes for vendor_id (invalid)
674+
return [0, 0x12, 0x34, 0x56, 0x78, 0x90]
675+
676+
class InvalidIANALengthEndpoint(InvalidVDMEndpointBase):
677+
def get_invalid_vdm_data(self):
678+
# Format 1 (IANA) but send 3 bytes for vendor_id (invalid)
679+
return [1, 0xab, 0xcd, 0xef, 0x34, 0x56]
680+
681+
class InvalidFormatEndpoint(InvalidVDMEndpointBase):
682+
def get_invalid_vdm_data(self):
683+
# Format 2 (invalid - only 0 and 1 are valid)
684+
return [2, 0x12, 0x34, 0x56, 0x78]
685+
686+
class UnsupportedCommandEndpoint(Endpoint):
687+
async def handle_mctp_control(self, sock, addr, data):
688+
flags, opcode = data[0:2]
689+
if opcode != 0x06:
690+
return await super().handle_mctp_control(sock, addr, data)
691+
# Return error completion code: command not supported
692+
iid = flags & 0x1f
693+
raddr = MCTPSockAddr.for_ep_resp(self, addr, sock.addr_ext)
694+
resp = bytes([iid, opcode, 0x05]) # cc=0x05 (Unsupported command)
695+
await sock.send(raddr, resp)
696+
697+
iface = mctpd.system.interfaces[0]
698+
mctp = await mctpd_mctp_iface_obj(dbus, iface)
699+
700+
# Test 1: Invalid PCIe length
701+
ep1 = InvalidPCIeLengthEndpoint(iface, bytes([0x1e]), eid = 15)
702+
mctpd.network.add_endpoint(ep1)
703+
(eid1, net1, path1, new1) = await mctp.call_learn_endpoint(ep1.lladdr)
704+
assert eid1 == ep1.eid
705+
ep_obj1 = await mctpd_mctp_endpoint_common_obj(dbus, path1)
706+
vdm_types1 = list(await ep_obj1.get_vendor_defined_message_types())
707+
assert len(vdm_types1) == 0
708+
709+
# Test 2: Invalid IANA length
710+
ep2 = InvalidIANALengthEndpoint(iface, bytes([0x1f]), eid = 16)
711+
mctpd.network.add_endpoint(ep2)
712+
(eid2, net2, path2, new2) = await mctp.call_learn_endpoint(ep2.lladdr)
713+
assert eid2 == ep2.eid
714+
ep_obj2 = await mctpd_mctp_endpoint_common_obj(dbus, path2)
715+
vdm_types2 = list(await ep_obj2.get_vendor_defined_message_types())
716+
assert len(vdm_types2) == 0
717+
718+
# Test 3: Invalid format type
719+
ep3 = InvalidFormatEndpoint(iface, bytes([0x20]), eid = 17)
720+
mctpd.network.add_endpoint(ep3)
721+
(eid3, net3, path3, new3) = await mctp.call_learn_endpoint(ep3.lladdr)
722+
assert eid3 == ep3.eid
723+
ep_obj3 = await mctpd_mctp_endpoint_common_obj(dbus, path3)
724+
vdm_types3 = list(await ep_obj3.get_vendor_defined_message_types())
725+
assert len(vdm_types3) == 0
726+
727+
# Test 4: Unsupported command error
728+
ep4 = UnsupportedCommandEndpoint(iface, bytes([0x21]), eid = 18)
729+
mctpd.network.add_endpoint(ep4)
730+
(eid4, net4, path4, new4) = await mctp.call_learn_endpoint(ep4.lladdr)
731+
assert eid4 == ep4.eid
732+
ep_obj4 = await mctpd_mctp_endpoint_common_obj(dbus, path4)
733+
vdm_types4 = list(await ep_obj4.get_vendor_defined_message_types())
734+
assert len(vdm_types4) == 0
735+
599736
""" Network1.LocalEIDs should reflect locally-assigned EID state """
600737
async def test_network_local_eids_single(dbus, mctpd):
601738
iface = mctpd.system.interfaces[0]

0 commit comments

Comments
 (0)