| #include "MctpUtil.hpp" |
| |
| #include "absl/strings/match.h" |
| |
| #include "Utils.hpp" |
| #include "VariantVisitors.hpp" |
| |
| #include <boost/asio/steady_timer.hpp> |
| #include <boost/container/flat_map.hpp> |
| #include <phosphor-logging/lg2.hpp> |
| #include <sdbusplus/bus/match.hpp> |
| |
| #include <chrono> |
| #include <filesystem> |
| #include <string> |
| |
| std::map<std::string, SensorData> mctpEndpointConfigMap; |
| |
| enum class State : uint8_t |
| { |
| None, |
| Add, |
| Remove |
| }; |
| |
| struct EndpointState |
| { |
| State state = State::None; |
| bool querying = false; |
| std::string emConfigPath; |
| }; |
| |
| static std::map<std::string, EndpointState> endpointStates; |
| |
| static std::unique_ptr<sdbusplus::bus::match_t> associationMatch = nullptr; |
| static std::unique_ptr<sdbusplus::bus::match_t> associationRemoveMatch = |
| nullptr; |
| |
| static void performEmConfigQuery( |
| const std::shared_ptr<sdbusplus::asio::connection>& conn, |
| const std::string& endpointPath, const std::string& emConfigPath) |
| { |
| conn->async_method_call( |
| [conn, endpointPath, emConfigPath](const boost::system::error_code& ec, |
| const ManagedObjectType& objects) { |
| auto& state = endpointStates[endpointPath]; |
| state.querying = false; |
| |
| State lastState = state.state; |
| state.state = State::None; // Reset for next query |
| |
| if (lastState == State::Remove) |
| { |
| lg2::info("Ignoring query reply for removed endpoint {ENDPOINT}", |
| "ENDPOINT", endpointPath); |
| auto it = mctpEndpointConfigMap.find(endpointPath); |
| if (it != mctpEndpointConfigMap.end()) |
| { |
| mctpEndpointConfigMap.erase(it); |
| } |
| return; |
| } |
| |
| if (ec) |
| { |
| lg2::error("Failed to get managed objects for {PATH}: {ERROR}", |
| "PATH", emConfigPath, "ERROR", ec.message()); |
| return; |
| } |
| |
| auto objIt = |
| objects.find(sdbusplus::message::object_path(emConfigPath)); |
| |
| if (objIt == objects.end()) |
| { |
| lg2::warning("EM config path {PATH} not found in managed objects", |
| "PATH", emConfigPath); |
| return; |
| } |
| |
| if (!objIt->second.empty()) |
| { |
| lg2::info( |
| "Recorded MCTP endpoint {ENDPOINT} with config from {PATH}", |
| "ENDPOINT", endpointPath, "PATH", emConfigPath); |
| mctpEndpointConfigMap[endpointPath] = objIt->second; |
| lg2::info("DEBUG: Map populated, new size: {SIZE}", "SIZE", |
| mctpEndpointConfigMap.size()); |
| } |
| |
| if (lastState == State::Add) |
| { |
| lg2::info( |
| "DEBUG: Triggering deferred query for endpoint {ENDPOINT}", |
| "ENDPOINT", endpointPath); |
| state.querying = true; |
| performEmConfigQuery(conn, endpointPath, emConfigPath); |
| } |
| }, |
| "xyz.openbmc_project.EntityManager", "/xyz/openbmc_project/inventory", |
| "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); |
| } |
| |
| static void triggerDeferredQueries( |
| const std::shared_ptr<sdbusplus::asio::connection>& conn) |
| { |
| for (auto& [path, state] : endpointStates) |
| { |
| if (path == "__global__") |
| { |
| continue; |
| } |
| |
| if (state.state == State::Add) |
| { |
| state.querying = true; |
| state.state = State::None; |
| performEmConfigQuery(conn, path, state.emConfigPath); |
| } |
| else if (state.state == State::Remove) |
| { |
| state.state = State::None; |
| auto it = mctpEndpointConfigMap.find(path); |
| if (it != mctpEndpointConfigMap.end()) |
| { |
| mctpEndpointConfigMap.erase(it); |
| } |
| } |
| } |
| } |
| |
| void setupMctpEndpointListener( |
| const std::shared_ptr<sdbusplus::asio::connection>& conn) |
| { |
| // Match 1: Listen for Associations from mctp-reactor |
| const std::string associationMatchSpec = |
| "type='signal',interface='org.freedesktop.DBus.ObjectManager',member='InterfacesAdded',arg0path='/au/com/codeconstruct/mctp1/'"; |
| |
| associationMatch = std::make_unique<sdbusplus::bus::match_t>( |
| static_cast<sdbusplus::bus_t&>(*conn), associationMatchSpec, |
| [conn](sdbusplus::message_t& msg) { |
| sdbusplus::message::object_path path; |
| boost::container::flat_map< |
| std::string, |
| boost::container::flat_map< |
| std::string, |
| std::variant<std::string, std::vector<std::string>>>> |
| interfaces; |
| msg.read(path, interfaces); |
| |
| auto it = interfaces.find("xyz.openbmc_project.Association"); |
| if (it == interfaces.end()) |
| { |
| return; |
| } |
| |
| auto propIt = it->second.find("endpoints"); |
| if (propIt == it->second.end()) |
| { |
| return; |
| } |
| |
| const auto* endpoints = |
| std::get_if<std::vector<std::string>>(&propIt->second); |
| if (!endpoints || endpoints->empty()) |
| { |
| return; |
| } |
| |
| std::string emConfigPath = endpoints->front(); |
| |
| std::string endpointPath = path.str; |
| constexpr std::string_view suffix = "/configured_by"; |
| if (endpointPath.ends_with("/configured_by")) |
| { |
| endpointPath = |
| endpointPath.substr(0, endpointPath.size() - suffix.length()); |
| } |
| |
| auto& state = endpointStates[endpointPath]; |
| state.emConfigPath = emConfigPath; |
| |
| if (endpointStates["__global__"].querying || state.querying) |
| { |
| state.state = State::Add; |
| return; |
| } |
| |
| state.querying = true; |
| performEmConfigQuery(conn, endpointPath, emConfigPath); |
| }); |
| |
| const std::string associationRemoveMatchSpec = |
| "type='signal',interface='org.freedesktop.DBus.ObjectManager',member='InterfacesRemoved',arg0path='/au/com/codeconstruct/mctp1/'"; |
| |
| associationRemoveMatch = std::make_unique<sdbusplus::bus::match_t>( |
| static_cast<sdbusplus::bus_t&>(*conn), associationRemoveMatchSpec, |
| [](sdbusplus::message_t& msg) { |
| sdbusplus::message::object_path path; |
| std::vector<std::string> interfaces; |
| msg.read(path, interfaces); |
| |
| std::string endpointPath = path.str; |
| constexpr std::string_view suffix = "/configured_by"; |
| if (endpointPath.ends_with("/configured_by")) |
| { |
| endpointPath = |
| endpointPath.substr(0, endpointPath.size() - suffix.length()); |
| } |
| |
| auto& state = endpointStates[endpointPath]; |
| |
| if (endpointStates["__global__"].querying || state.querying) |
| { |
| state.state = State::Remove; |
| } |
| else |
| { |
| auto it = mctpEndpointConfigMap.find(endpointPath); |
| if (it != mctpEndpointConfigMap.end()) |
| { |
| lg2::info("Removing MCTP endpoint {ENDPOINT} from map", |
| "ENDPOINT", endpointPath); |
| mctpEndpointConfigMap.erase(it); |
| } |
| } |
| }); |
| |
| // Prime the cache: Query existing Associations |
| endpointStates["__global__"].querying = true; |
| auto barrier = std::make_shared<int>(0); |
| |
| #ifdef UNIT_TEST |
| // Inject 5 second delay ONLY for tests to hit the race condition |
| auto timer = |
| std::make_shared<boost::asio::steady_timer>(conn->get_io_context()); |
| timer->expires_after(std::chrono::seconds(5)); |
| timer->async_wait([conn, barrier](const boost::system::error_code& ec) { |
| if (ec) |
| return; |
| #endif |
| |
| constexpr auto associationInterfaces = |
| std::to_array({"xyz.openbmc_project.Association"}); |
| conn->async_method_call( |
| [conn, barrier](const boost::system::error_code& ec, |
| const GetSubTreeType& subtree) { |
| if (ec) |
| { |
| lg2::error( |
| "Failed to get associations from Object Mapper: {ERROR}", |
| "ERROR", ec.message()); |
| endpointStates["__global__"].querying = false; |
| return; |
| } |
| |
| if (subtree.empty()) |
| { |
| endpointStates["__global__"].querying = false; |
| return; |
| } |
| |
| for (const auto& [path, services] : subtree) |
| { |
| if (!path.ends_with("/configured_by")) |
| { |
| continue; |
| } |
| |
| std::string endpointPath = path; |
| constexpr std::string_view suffix = "/configured_by"; |
| endpointPath = endpointPath.substr(0, endpointPath.size() - |
| suffix.length()); |
| |
| (*barrier)++; // Increment barrier! |
| |
| conn->async_method_call( |
| [conn, barrier, endpointPath]( |
| const boost::system::error_code& ec, |
| const std::variant<std::vector<std::string>>& value) { |
| auto cleanup = [&]() { |
| (*barrier)--; |
| if (*barrier == 0) |
| { |
| endpointStates["__global__"].querying = false; |
| triggerDeferredQueries(conn); |
| } |
| }; |
| |
| if (ec) |
| { |
| lg2::error( |
| "Failed to get endpoints for {PATH}: {ERROR}", |
| "PATH", endpointPath, "ERROR", ec.message()); |
| cleanup(); |
| return; |
| } |
| |
| const auto* endpoints = |
| std::get_if<std::vector<std::string>>(&value); |
| if (!endpoints || endpoints->empty()) |
| { |
| cleanup(); |
| return; |
| } |
| |
| std::string emConfigPath = endpoints->front(); |
| |
| auto& state = endpointStates[endpointPath]; |
| state.emConfigPath = emConfigPath; |
| |
| state.querying = true; |
| performEmConfigQuery(conn, endpointPath, emConfigPath); |
| |
| cleanup(); |
| }, |
| "xyz.openbmc_project.ObjectMapper", path, |
| "org.freedesktop.DBus.Properties", "Get", |
| "xyz.openbmc_project.Association", "endpoints"); |
| } |
| }, |
| "xyz.openbmc_project.ObjectMapper", |
| "/xyz/openbmc_project/object_mapper", |
| "xyz.openbmc_project.ObjectMapper", "GetSubTree", |
| "/au/com/codeconstruct/mctp1", 0, associationInterfaces); |
| |
| #ifdef UNIT_TEST |
| }); |
| #endif |
| } |
| |
| BusInfo extractBusInfo(const SensorData& configData) |
| { |
| BusInfo busInfo; |
| auto extractProp = [](const auto& properties, BusInfo& info, |
| const std::string& key) { |
| auto it = properties.find(key); |
| if (it != properties.end()) |
| { |
| info[key] = std::visit(VariantToStringVisitor(), it->second); |
| } |
| }; |
| |
| for (const auto& [intf, props] : configData) |
| { |
| if (absl::StrContains(intf, |
| "xyz.openbmc_project.Configuration.BusInfo")) |
| { |
| std::string busType; |
| auto it = props.find("BusType"); |
| if (it != props.end()) |
| { |
| busType = std::visit(VariantToStringVisitor(), it->second); |
| } |
| |
| if (busType == "USB") |
| { |
| extractProp(props, busInfo, "RootHubPath"); |
| extractProp(props, busInfo, "Port"); |
| extractProp(props, busInfo, "Configuration"); |
| extractProp(props, busInfo, "InterfaceNum"); |
| |
| busInfo["BusType"] = busType; |
| } |
| else if (!busType.empty()) |
| { |
| lg2::warning("Unsupported BusType {TYPE}", "TYPE", busType); |
| } |
| } |
| } |
| return busInfo; |
| } |
| |
| void cleanupMctpEndpointListener() |
| { |
| associationMatch.reset(); |
| associationRemoveMatch.reset(); |
| } |