| /* |
| * SPDX-FileCopyrightText: Copyright (c) 2023-2024 NVIDIA CORPORATION & |
| * AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include "mctp_endpoint_discovery.hpp" |
| |
| #include "common/types.hpp" |
| #include "common/utils.hpp" |
| #include "dBusAsyncUtils.hpp" |
| |
| #include <systemd/sd-bus.h> |
| |
| #include <nlohmann/json.hpp> |
| #include <phosphor-logging/lg2.hpp> |
| |
| #include <algorithm> |
| #include <fstream> |
| #include <iostream> |
| #include <map> |
| #include <string> |
| #include <string_view> |
| #include <vector> |
| |
| namespace mctp |
| { |
| const std::string emptyUUID = "00000000-0000-0000-0000-000000000000"; |
| |
| MctpDiscovery::MctpDiscovery( |
| sdbusplus::bus::bus& bus, mctp_socket::Handler& handler, |
| std::initializer_list<MctpDiscoveryHandlerIntf*> list) : |
| bus(bus), handler(handler), |
| mctpEndpointAddedSignal( |
| bus, |
| sdbusplus::bus::match::rules::interfacesAdded( |
| "/au/com/codeconstruct/mctp1"), |
| std::bind_front(&MctpDiscovery::discoverEndpoints, this)), |
| mctpEndpointRemovedSignal( |
| bus, |
| sdbusplus::bus::match::rules::interfacesRemoved( |
| "/au/com/codeconstruct/mctp1"), |
| std::bind_front(&MctpDiscovery::cleanEndpoints, this)), |
| handlers(list) |
| { |
| dbus::ObjectValueTree objects; |
| std::set<dbus::Service> mctpCtrlServices; |
| MctpInfos mctpInfos; |
| |
| try |
| { |
| const dbus::Interfaces ifaceList{"xyz.openbmc_project.MCTP.Endpoint"}; |
| auto getSubTreeResponse = utils::DBusHandler().getSubtree( |
| "/au/com/codeconstruct/mctp1", 0, ifaceList); |
| for (const auto& [objPath, mapperServiceMap] : getSubTreeResponse) |
| { |
| for (const auto& [serviceName, interfaces] : mapperServiceMap) |
| { |
| mctpCtrlServices.emplace(serviceName); |
| } |
| } |
| } |
| catch (const std::exception& e) |
| { |
| handleMctpEndpoints(mctpInfos); |
| return; |
| } |
| |
| for (const auto& service : mctpCtrlServices) |
| { |
| dbus::ObjectValueTree objects{}; |
| try |
| { |
| auto method = bus.new_method_call( |
| service.c_str(), "/au/com/codeconstruct/mctp1", |
| "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); |
| auto reply = bus.call(method); |
| reply.read(objects); |
| for (const auto& [objectPath, interfaces] : objects) |
| { |
| populateMctpInfo(interfaces, mctpInfos); |
| |
| // watch PropertiesChanged signal from |
| // au.com.codeconstruct.MCTP.Endpoint1 PDI |
| if (enableMatches.find(objectPath.str) == enableMatches.end()) |
| { |
| enableMatches.emplace( |
| objectPath.str, |
| sdbusplus::bus::match_t( |
| bus, |
| sdbusplus::bus::match::rules::propertiesChanged( |
| objectPath.str, |
| "au.com.codeconstruct.MCTP.Endpoint1"), |
| std::bind_front(&MctpDiscovery::refreshEndpoints, |
| this))); |
| } |
| } |
| } |
| catch (const std::exception& e) |
| { |
| continue; |
| } |
| } |
| |
| handleMctpEndpoints(mctpInfos); |
| } |
| |
| void MctpDiscovery::populateMctpInfo(const dbus::InterfaceMap& interfaces, |
| MctpInfos& mctpInfos) |
| { |
| uuid_t uuid{}; |
| int type = 0; |
| int protocol = 0; |
| std::vector<uint8_t> address{}; |
| std::string bindingType; |
| try |
| { |
| for (const auto& [intfName, properties] : interfaces) |
| { |
| if (intfName == uuidEndpointIntfName) |
| { |
| uuid = std::get<std::string>(properties.at("UUID")); |
| } |
| |
| if (intfName == unixSocketIntfName) |
| { |
| type = std::get<size_t>(properties.at("Type")); |
| protocol = std::get<size_t>(properties.at("Protocol")); |
| address = |
| std::get<std::vector<uint8_t>>(properties.at("Address")); |
| } |
| } |
| |
| if (uuid.empty()) |
| { |
| return; |
| } |
| |
| if (interfaces.contains(mctpBindingIntfName)) |
| { |
| const auto& properties = interfaces.at(mctpBindingIntfName); |
| if (properties.contains("BindingType")) |
| { |
| bindingType = |
| std::get<std::string>(properties.at("BindingType")); |
| } |
| } |
| if (interfaces.contains(mctpEndpointIntfName)) |
| { |
| const auto& properties = interfaces.at(mctpEndpointIntfName); |
| if (properties.contains("EID") && |
| properties.contains("SupportedMessageTypes") && |
| properties.contains("NetworkId")) |
| { |
| auto eid = std::get<uint8_t>(properties.at("EID")); |
| if constexpr (FILTER_NULL_MCTP_EID) |
| { |
| // MCTP EID 0 is a special Null EID as per MCTP DMTF |
| // specification doc |
| if (eid == 0) |
| { |
| return; |
| } |
| } |
| auto mctpTypes = std::get<std::vector<uint8_t>>( |
| properties.at("SupportedMessageTypes")); |
| |
| std::string mediumType{}; |
| |
| auto hasMediumType = properties.find("MediumType"); |
| if (hasMediumType != properties.end()) |
| { |
| mediumType = std::get<std::string>(hasMediumType->second); |
| } |
| |
| auto networkId = std::get<uint32_t>(properties.at("NetworkId")); |
| if (std::find(mctpTypes.begin(), mctpTypes.end(), |
| mctpTypeVDM) != mctpTypes.end()) |
| { |
| handler.registerMctpEndpoint(eid, type, protocol, address); |
| mctpInfos.emplace_back(std::make_tuple( |
| eid, uuid, mediumType, networkId, bindingType)); |
| } |
| } |
| } |
| } |
| catch (const std::exception& e) |
| { |
| lg2::error("Error while getting properties.", "ERROR", e); |
| } |
| } |
| |
| void MctpDiscovery::discoverEndpoints(sdbusplus::message::message& msg) |
| { |
| constexpr std::string_view mctpEndpointIntfName{ |
| "xyz.openbmc_project.MCTP.Endpoint"}; |
| MctpInfos mctpInfos{}; |
| |
| sdbusplus::message::object_path objPath; |
| dbus::InterfaceMap interfaces; |
| msg.read(objPath, interfaces); |
| |
| populateMctpInfo(interfaces, mctpInfos); |
| |
| // watch PropertiesChanged signal from au.com.codeconstruct.MCTP.Endpoint1 |
| // PDI |
| if (enableMatches.find(objPath.str) == enableMatches.end()) |
| { |
| enableMatches.emplace( |
| objPath.str, |
| sdbusplus::bus::match_t( |
| bus, |
| sdbusplus::bus::match::rules::propertiesChanged( |
| objPath.str, "au.com.codeconstruct.MCTP.Endpoint1"), |
| std::bind_front(&MctpDiscovery::refreshEndpoints, this))); |
| } |
| handleMctpEndpoints(mctpInfos); |
| } |
| |
| void MctpDiscovery::handleMctpEndpoints(const MctpInfos& mctpInfos) |
| { |
| for (MctpDiscoveryHandlerIntf* handler : handlers) |
| { |
| if (handler) |
| { |
| handler->handleMctpEndpoints(mctpInfos); |
| } |
| } |
| } |
| |
| requester::Coroutine |
| MctpDiscovery::deviceStateChangeTask(const std::string objPath) |
| { |
| while (!mctpQueuedSignals[objPath].empty()) |
| { |
| sdbusplus::message::message& msg = mctpQueuedSignals[objPath].front(); |
| lg2::info( |
| "deviceStateChangeTask mctpQueuedSignals for PATH={OBJ_PATH} size= {SIZE}", |
| "OBJ_PATH", objPath, "SIZE", mctpQueuedSignals[objPath].size()); |
| |
| std::string interface; |
| dbus::PropertyMap properties; |
| dbus::PropertyMap allProperties; |
| std::string sender = msg.get_sender(); |
| |
| msg.read(interface, properties); |
| auto prop = properties.find("Connectivity"); |
| if (prop != properties.end()) |
| { |
| auto connectivity = std::get<std::string>(prop->second); |
| lg2::info( |
| "Received au.com.codeconstruct.MCTP.Endpoint1 propertiesChanged signal for " |
| "Connectivity=={CONN} at PATH={OBJ_PATH} from sender={SENDER}", |
| "CONN", connectivity, "OBJ_PATH", objPath, "SENDER", sender); |
| try |
| { |
| auto mapperResponse = co_await utils::coGetServiceMap( |
| objPath, dbus::Interfaces{}); |
| if (mapperResponse.size() == 0) |
| { |
| mctpQueuedSignals[objPath].pop(); |
| lg2::error( |
| "deviceStateChangeTask: coGetServiceMap failed for PATH={OBJ_PATH}", |
| "OBJ_PATH", objPath); |
| co_return NSM_SW_SUCCESS; |
| } |
| std::string service = mapperResponse.begin()->first; |
| lg2::info("service of PATH={OBJ_PATH} is {SERVICE}", "OBJ_PATH", |
| objPath, "SERVICE", service); |
| allProperties = co_await utils::coGetAllDbusProperty(service, |
| objPath); |
| } |
| catch (const std::exception& e) |
| { |
| lg2::error( |
| "refreshEndpoints: failed to get MctpInfo from PATH={OBJ_PATH},{ERROR}", |
| "OBJ_PATH", objPath, "ERROR", e); |
| mctpQueuedSignals[objPath].pop(); |
| co_return NSM_SW_SUCCESS; |
| } |
| |
| uint32_t eid{}; |
| uint32_t networkId{}; |
| std::string mediumType{}; |
| std::string uuid{}; |
| std::string bindingType{}; |
| std::vector<uint8_t> mctpTypes{}; |
| |
| if (allProperties.contains("EID")) |
| { |
| eid = std::get<uint32_t>(allProperties.at("EID")); |
| } |
| if constexpr (FILTER_NULL_MCTP_EID) |
| { |
| // MCTP EID 0 is a special Null EID as per MCTP DMTF |
| // specification doc |
| if (eid == 0) |
| { |
| mctpQueuedSignals[objPath].pop(); |
| co_return NSM_SW_SUCCESS; |
| } |
| } |
| |
| if (allProperties.contains("NetworkId")) |
| { |
| networkId = std::get<uint32_t>(allProperties.at("NetworkId")); |
| } |
| if (allProperties.contains("MediumType")) |
| { |
| mediumType = |
| std::get<std::string>(allProperties.at("MediumType")); |
| } |
| if (allProperties.contains("UUID")) |
| |
| { |
| uuid = std::get<std::string>(allProperties.at("UUID")); |
| } |
| if (allProperties.contains("BindingType")) |
| { |
| bindingType = |
| std::get<std::string>(allProperties.at("BindingType")); |
| } |
| if (allProperties.contains("SupportedMessageTypes")) |
| { |
| mctpTypes = std::get<std::vector<uint8_t>>( |
| allProperties.at("SupportedMessageTypes")); |
| } |
| |
| MctpInfo mctpInfo = std::make_tuple(eid, uuid, mediumType, |
| networkId, bindingType); |
| for (MctpDiscoveryHandlerIntf* handler : handlers) |
| { |
| if (connectivity == "Available") |
| { |
| if (std::find(mctpTypes.begin(), mctpTypes.end(), |
| mctpTypeVDM) != mctpTypes.end()) |
| { |
| co_await handler->onlineMctpEndpoint(mctpInfo); |
| } |
| } |
| else |
| { |
| co_await handler->offlineMctpEndpoint(mctpInfo); |
| } |
| } |
| } |
| mctpQueuedSignals[objPath].pop(); |
| lg2::info("mctpQueuedSignals for PATH={OBJ_PATH} size= {SIZE}", |
| "OBJ_PATH", objPath, "SIZE", |
| mctpQueuedSignals[objPath].size()); |
| } |
| // coverity[missing_return] |
| co_return NSM_SW_SUCCESS; |
| } |
| void MctpDiscovery::refreshEndpoints(sdbusplus::message::message& msg) |
| { |
| std::string interface; |
| dbus::PropertyMap properties; |
| dbus::PropertyMap allProperties; |
| std::string objPath = msg.get_path(); |
| std::string sender = msg.get_sender(); |
| msg.read(interface, properties); |
| |
| // move back read cursor to beginning of message before puting on the queue |
| sd_bus_message_rewind(msg.get(), true); |
| |
| auto prop = properties.find("Connectivity"); |
| if (prop != properties.end()) |
| { |
| auto connectivity = std::get<std::string>(prop->second); |
| lg2::info( |
| "Received au.com.codeconstruct.MCTP.Endpoint1 propertiesChanged signal for " |
| "Connectivity=={CONN} at PATH={OBJ_PATH} from sender={SENDER}", |
| "CONN", connectivity, "OBJ_PATH", objPath, "SENDER", sender); |
| |
| mctpQueuedSignals[objPath].emplace(msg); |
| |
| if (deviceStateChangeTaskHandles.find(objPath) == |
| deviceStateChangeTaskHandles.end()) |
| { |
| auto co = deviceStateChangeTask(objPath); |
| deviceStateChangeTaskHandles[objPath] = co.handle; |
| return; |
| } |
| |
| if (deviceStateChangeTaskHandles[objPath]) |
| { |
| if (!deviceStateChangeTaskHandles[objPath].done()) |
| { |
| return; |
| } |
| deviceStateChangeTaskHandles[objPath].destroy(); |
| } |
| |
| auto co = deviceStateChangeTask(objPath); |
| deviceStateChangeTaskHandles[objPath] = co.handle; |
| if (deviceStateChangeTaskHandles[objPath].done()) |
| { |
| deviceStateChangeTaskHandles[objPath] = nullptr; |
| } |
| } |
| } |
| |
| void MctpDiscovery::cleanEndpoints( |
| [[maybe_unused]] sdbusplus::message::message& msg) |
| { |
| // place holder: implement the function once mctp-ctrl service support |
| // the InterfacesRemoved signal |
| } |
| |
| } // namespace mctp |