From b5e7421b8903f547122ac6097878afbc5dc45821 Mon Sep 17 00:00:00 2001 From: Nidhin MS Date: Wed, 3 Jun 2026 06:23:39 +0000 Subject: [PATCH] mctpd: Add AttemptDiscoveryNotify D-Bus method for endpoint role Expose AttemptDiscoveryNotify on au.com.codeconstruct.MCTP.Endpoint1 on the per-interface D-Bus path when the link role is ENDPOINT_ROLE_ENDPOINT. The method accepts a physical hardware address and sends a physical-addressed Discovery Notify control message (opcode 0x0D) to the target, allowing an endpoint to proactively trigger discovery by the bus owner. Also fix mctp_ctrl_validate_response to use strict less-than when comparing exp_size against sizeof(struct mctp_ctrl_resp). The previous <= incorrectly rejected responses whose complete structure is exactly the base response size, such as Discovery Notify. Signed-off-by: Nidhin MS --- docs/mctpd.md | 40 +++++++++++++- src/mctpd.c | 104 ++++++++++++++++++++++++++++++++--- tests/mctp_test_utils.py | 8 +++ tests/mctpenv/__init__.py | 5 ++ tests/test_mctpd_endpoint.py | 11 ++++ 5 files changed, 158 insertions(+), 10 deletions(-) diff --git a/docs/mctpd.md b/docs/mctpd.md index bab76b6f..eb7bef90 100644 --- a/docs/mctpd.md +++ b/docs/mctpd.md @@ -120,7 +120,8 @@ configured value is `Unknown`. Other platform setup infrastructure may use this to configure the initial MCTP state of the platform. When the interface `Role` is `BusOwner`, the MCTP interface object will -also host the `BusOwner1` dbus interface: +also host the `BusOwner1` dbus interface. When the `Role` is `Endpoint`, +the MCTP interface object will also host the `Endpoint1` dbus interface. The `NetworkId` property represents the network on which this interface is present. @@ -217,6 +218,43 @@ reports as a bridge. Bridge endpoints should be initialised with `AssignEndpoint` instead. +### Endpoint interface: `au.com.codeconstruct.MCTP.Endpoint1` interface + +When the interface `Role` is `Endpoint`, the MCTP interface object also hosts +the `au.com.codeconstruct.MCTP.Endpoint1` D-Bus interface. This exposes +endpoint-role functions on the per-interface D-Bus path. + +``` +NAME TYPE SIGNATURE RESULT/VALUE FLAGS +au.com.codeconstruct.MCTP.Interface1 interface - - - +.NetworkId property u 1 emits-change +.Role property s "Endpoint" emits-change writable +au.com.codeconstruct.MCTP.Endpoint1 interface - - - +.AttemptDiscoveryNotify method ay - - +``` + +#### `.AttemptDiscoveryNotify`: `ay` + +Some physical transports (such as MCTP-over-I3C) require the endpoint to +send a Discovery Notify to announce its presence before the bus owner will +enumerate it. On those interfaces this method can be called before the +endpoint will be assigned an EID. + +`AttemptDiscoveryNotify ` + + - `` Physical hardware address of the bus owner. For MCTP-over-I3C + this is the 6-byte Provisional ID (PID) of the bus owner. + +An example for MCTP-over-I3C, sending a Discovery Notify using the bus owner's +6-byte PID `00 6c 90 01 23 45`: + +```shell +busctl call au.com.codeconstruct.MCTP1 \ + /au/com/codeconstruct/mctp1/interfaces/mctpi3c0 \ + au.com.codeconstruct.MCTP.Endpoint1 \ + AttemptDiscoveryNotify ay 6 0x00 0x6c 0x90 0x01 0x23 0x45 +``` + ## Network objects: `/au/com/codeconstruct/networks/` These objects represent MCTP networks which have been added use `mctp link` diff --git a/src/mctpd.c b/src/mctpd.c index 9cd16f3a..f174618e 100644 --- a/src/mctpd.c +++ b/src/mctpd.c @@ -147,6 +147,7 @@ struct link { char *path; sd_bus_slot *slot_iface; sd_bus_slot *slot_busowner; + sd_bus_slot *slot_endpoint; sd_event_source *role_defer; struct ctx *ctx; @@ -385,6 +386,7 @@ 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[]; +static const sd_bus_vtable bus_link_endpoint_vtable[]; __attribute__((format(printf, 1, 2))) static void bug_warn(const char *fmt, ...) { @@ -1658,7 +1660,7 @@ static int mctp_ctrl_validate_response(struct mctp_ctrl_cmd *cmd, { struct mctp_ctrl_resp *rsp; - if (exp_size <= sizeof(*rsp)) { + if (exp_size < sizeof(*rsp)) { warnx("invalid expected response size!"); return -EINVAL; } @@ -4017,6 +4019,70 @@ static int method_register_vdm_type_support(sd_bus_message *call, void *data, return rc; } +static int method_attempt_discovery_notify(sd_bus_message *call, void *data, + sd_bus_error *berr) +{ + struct mctp_ctrl_resp_discovery_notify *resp = NULL; + struct mctp_ctrl_cmd_discovery_notify req = { 0 }; + dest_phys desti = { 0 }, *dest = &desti; + struct mctp_ctrl_cmd cmd = { 0 }; + struct link *link = data; + uint8_t iid; + int rc; + + struct ctx *ctx = link->ctx; + + dest->ifindex = link->ifindex; + if (dest->ifindex <= 0) + return sd_bus_error_setf(berr, SD_BUS_ERROR_INVALID_ARGS, + "Unknown MCTP interface"); + + rc = message_read_hwaddr(call, dest); + if (rc < 0) { + set_berr(ctx, rc, berr); + return rc; + } + + rc = validate_dest_phys(ctx, dest); + if (rc < 0) + return sd_bus_error_setf(berr, SD_BUS_ERROR_INVALID_ARGS, + "Bad physaddr"); + + iid = mctp_next_iid(ctx); + mctp_ctrl_msg_hdr_init_req(&req.ctrl_hdr, iid, + MCTP_CTRL_CMD_DISCOVERY_NOTIFY); + mctp_ctrl_cmd_init_from_req_type(&cmd, req); + + rc = endpoint_query_phys(ctx, dest, &cmd); + if (rc < 0) + goto err; + + rc = mctp_ctrl_validate_response(&cmd, sizeof(*resp), + dest_phys_tostr(dest), iid, + MCTP_CTRL_CMD_DISCOVERY_NOTIFY); + if (rc) + goto err; + + mctp_ctrl_cmd_free(&cmd); + return sd_bus_reply_method_return(call, ""); +err: + mctp_ctrl_cmd_free(&cmd); + set_berr(ctx, rc, berr); + return rc; +} + +// clang-format off +static const sd_bus_vtable bus_link_endpoint_vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_METHOD_WITH_ARGS("AttemptDiscoveryNotify", + SD_BUS_ARGS("ay", physaddr), + SD_BUS_NO_RESULT, + method_attempt_discovery_notify, + 0), + SD_BUS_VTABLE_END, +}; +// clang-format on + // clang-format off static const sd_bus_vtable bus_link_owner_vtable[] = { SD_BUS_VTABLE_START(0), @@ -4214,14 +4280,21 @@ static int link_set_role(sd_event_source *ev, void *userdata) sd_event_source_unref(link->role_defer); link->role_defer = NULL; - if (link->role != ENDPOINT_ROLE_BUS_OWNER) - return 0; - - rc = sd_bus_add_object_vtable(link->ctx->bus, &link->slot_busowner, - link->path, CC_MCTP_DBUS_IFACE_BUSOWNER, - bus_link_owner_vtable, link); - if (rc) - warnx("adding link owner vtable failed: %d", rc); + if (link->role == ENDPOINT_ROLE_BUS_OWNER) { + rc = sd_bus_add_object_vtable(link->ctx->bus, + &link->slot_busowner, link->path, + CC_MCTP_DBUS_IFACE_BUSOWNER, + bus_link_owner_vtable, link); + if (rc) + warnx("adding link owner vtable failed: %d", rc); + } else if (link->role == ENDPOINT_ROLE_ENDPOINT) { + rc = sd_bus_add_object_vtable(link->ctx->bus, + &link->slot_endpoint, link->path, + CC_MCTP_DBUS_IFACE_ENDPOINT, + bus_link_endpoint_vtable, link); + if (rc) + warnx("adding link endpoint vtable failed: %d", rc); + } return 0; } @@ -4712,6 +4785,7 @@ static void free_link(struct link *link) sd_event_source_disable_unref(link->role_defer); sd_bus_slot_unref(link->slot_iface); sd_bus_slot_unref(link->slot_busowner); + sd_bus_slot_unref(link->slot_endpoint); free(link->path); free(link->sysfs_path); free(link); @@ -4781,6 +4855,8 @@ static int rename_interface(struct ctx *ctx, struct link *link, int ifindex) link->slot_iface = NULL; sd_bus_slot_unref(link->slot_busowner); link->slot_busowner = NULL; + sd_bus_slot_unref(link->slot_endpoint); + link->slot_endpoint = NULL; free(link->path); /* set new path and re-add */ @@ -4794,6 +4870,11 @@ static int rename_interface(struct ctx *ctx, struct link *link, int ifindex) link->path, CC_MCTP_DBUS_IFACE_BUSOWNER, bus_link_owner_vtable, link); + } else if (link->role == ENDPOINT_ROLE_ENDPOINT) { + sd_bus_add_object_vtable(link->ctx->bus, &link->slot_endpoint, + link->path, + CC_MCTP_DBUS_IFACE_ENDPOINT, + bus_link_endpoint_vtable, link); } emit_interface_added(link); @@ -5167,6 +5248,11 @@ static int add_interface(struct ctx *ctx, int ifindex) link->path, CC_MCTP_DBUS_IFACE_BUSOWNER, bus_link_owner_vtable, link); + } else if (link->role == ENDPOINT_ROLE_ENDPOINT) { + sd_bus_add_object_vtable(link->ctx->bus, &link->slot_endpoint, + link->path, + CC_MCTP_DBUS_IFACE_ENDPOINT, + bus_link_endpoint_vtable, link); } if (link->phys_binding == MCTP_PHYS_BINDING_PCIE_VDM) { diff --git a/tests/mctp_test_utils.py b/tests/mctp_test_utils.py index 338c1282..bc65ccab 100644 --- a/tests/mctp_test_utils.py +++ b/tests/mctp_test_utils.py @@ -21,6 +21,14 @@ async def mctpd_mctp_iface_control_obj(dbus, iface): return await obj.get_interface("au.com.codeconstruct.MCTP.Interface1") +async def mctpd_mctp_iface_endpoint_obj(dbus, iface): + obj = await dbus.get_proxy_object( + 'au.com.codeconstruct.MCTP1', + '/au/com/codeconstruct/mctp1/interfaces/' + iface.name, + ) + return await obj.get_interface('au.com.codeconstruct.MCTP.Endpoint1') + + async def mctpd_mctp_endpoint_obj(dbus, path, iface): obj = await dbus.get_proxy_object( 'au.com.codeconstruct.MCTP1', diff --git a/tests/mctpenv/__init__.py b/tests/mctpenv/__init__.py index 7478819b..5fc32784 100644 --- a/tests/mctpenv/__init__.py +++ b/tests/mctpenv/__init__.py @@ -536,6 +536,11 @@ async def handle_mctp_control(self, sock, addr, data): ) await sock.send(raddr, data) + elif opcode == 0x0D: + # Discovery Notify — respond with completion_code = 0 + data = bytes(hdr + [0x00]) + await sock.send(raddr, data) + else: await sock.send( raddr, bytes(hdr + [0x05]) diff --git a/tests/test_mctpd_endpoint.py b/tests/test_mctpd_endpoint.py index f29a967a..f29a7d77 100644 --- a/tests/test_mctpd_endpoint.py +++ b/tests/test_mctpd_endpoint.py @@ -2,6 +2,7 @@ import asyncdbus from mctp_test_utils import ( mctpd_mctp_iface_control_obj, + mctpd_mctp_iface_endpoint_obj, mctpd_mctp_endpoint_control_obj, ) from mctpenv import ( @@ -218,3 +219,13 @@ async def test_simple(self, dbus, mctpd): mctpd.network.mctp_socket, MCTPControlCommand(True, 0, 0x0B) ) assert rsp.hex(' ') == '00 0b 05' + + +async def test_attempt_discovery_notify(dbus, mctpd): + """AttemptDiscoveryNotify sends Discovery Notify to the bus owner.""" + iface = mctpd.system.interfaces[0] + bo = mctpd.network.endpoints[0] + + ep = await mctpd_mctp_iface_endpoint_obj(dbus, iface) + # mock bus owner responds with completion_code = 0x00 + await ep.call_attempt_discovery_notify(bo.lladdr)