Skip to content

Commit fa05dfb

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 fa05dfb

File tree

2 files changed

+329
-0
lines changed

2 files changed

+329
-0
lines changed

src/mctpd.c

Lines changed: 192 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++) {
@@ -2055,6 +2061,7 @@ static void free_peers(struct ctx *ctx)
20552061
for (size_t i = 0; i < ctx->num_peers; i++) {
20562062
struct peer *peer = ctx->peers[i];
20572063
free(peer->message_types);
2064+
free(peer->vdm_types);
20582065
free(peer->uuid);
20592066
free(peer->path);
20602067
sd_bus_slot_unref(peer->slot_obmc_endpoint);
@@ -2512,6 +2519,116 @@ static int query_get_peer_msgtypes(struct peer *peer)
25122519
return rc;
25132520
}
25142521

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

29513069
for (unsigned int i = 0; i < max_retries; i++) {
@@ -2974,6 +3092,39 @@ static int query_peer_properties(struct peer *peer)
29743092
}
29753093
}
29763094

3095+
for (unsigned int i = 0; i < peer->num_message_types; i++) {
3096+
if (peer->message_types[i] ==
3097+
MCTP_GET_VDM_SUPPORT_IANA_FORMAT_ID ||
3098+
peer->message_types[i] ==
3099+
MCTP_GET_VDM_SUPPORT_PCIE_FORMAT_ID) {
3100+
supports_vdm = true;
3101+
break;
3102+
}
3103+
}
3104+
3105+
for (unsigned int i = 0; supports_vdm && i < max_retries; i++) {
3106+
rc = query_get_peer_vdm_types(peer);
3107+
// Success
3108+
if (rc == 0)
3109+
break;
3110+
3111+
// On timeout, retry
3112+
if (rc == -ETIMEDOUT) {
3113+
if (peer->ctx->verbose)
3114+
warnx("Retrying to get vendor message types for %s. Attempt %u",
3115+
peer_tostr(peer), i + 1);
3116+
rc = 0;
3117+
continue;
3118+
}
3119+
3120+
if (rc < 0) {
3121+
warnx("Error getting vendor message types for %s. Ignoring error %d %s",
3122+
peer_tostr(peer), rc, strerror(-rc));
3123+
rc = 0;
3124+
break;
3125+
}
3126+
}
3127+
29773128
for (unsigned int i = 0; i < max_retries; i++) {
29783129
rc = query_get_peer_uuid(peer);
29793130

@@ -3866,6 +4017,42 @@ static int bus_endpoint_get_prop(sd_bus *bus, const char *path,
38664017
rc = sd_bus_message_append_array(reply, 'y',
38674018
peer->message_types,
38684019
peer->num_message_types);
4020+
} else if (strcmp(property, "VendorDefinedMessageTypes") == 0) {
4021+
rc = sd_bus_message_open_container(reply, 'a', "(yvu)");
4022+
if (rc < 0)
4023+
return rc;
4024+
4025+
for (size_t i = 0; i < peer->num_vdm_types; i++) {
4026+
struct vdm_type_support *vdm = &peer->vdm_types[i];
4027+
rc = sd_bus_message_open_container(reply, 'r', "yvu");
4028+
if (rc < 0)
4029+
return rc;
4030+
4031+
rc = sd_bus_message_append(reply, "y", vdm->format);
4032+
if (rc < 0)
4033+
return rc;
4034+
4035+
if (vdm->format == VID_FORMAT_PCIE) {
4036+
rc = sd_bus_message_append(reply, "v", "q",
4037+
vdm->vendor_id.pcie);
4038+
} else {
4039+
rc = sd_bus_message_append(reply, "v", "u",
4040+
vdm->vendor_id.iana);
4041+
}
4042+
if (rc < 0)
4043+
return rc;
4044+
4045+
rc = sd_bus_message_append(reply, "u",
4046+
(uint32_t)vdm->cmd_set);
4047+
if (rc < 0)
4048+
return rc;
4049+
4050+
rc = sd_bus_message_close_container(reply);
4051+
if (rc < 0)
4052+
return rc;
4053+
}
4054+
4055+
rc = sd_bus_message_close_container(reply);
38694056
} else if (strcmp(property, "UUID") == 0 && peer->uuid) {
38704057
const char *s = dfree(bytes_to_uuid(peer->uuid));
38714058
rc = sd_bus_message_append(reply, "s", s);
@@ -4055,6 +4242,11 @@ static const sd_bus_vtable bus_endpoint_obmc_vtable[] = {
40554242
bus_endpoint_get_prop,
40564243
0,
40574244
SD_BUS_VTABLE_PROPERTY_CONST),
4245+
SD_BUS_PROPERTY("VendorDefinedMessageTypes",
4246+
"a(yvu)",
4247+
bus_endpoint_get_prop,
4248+
0,
4249+
SD_BUS_VTABLE_PROPERTY_CONST),
40584250
SD_BUS_VTABLE_END
40594251
};
40604252

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)