phosphor: entity-manager: Migrate internal patches
These patches were previously residing in an internal repository. There
is no need for them to be hidden and we need to share them with external
partners as part of a more open gBMC.
Tested: All patches applied correctly.
See go/paste/5602069745500160 for patch logs.
See go/paste/5879083056496640 for compile log.
Google-Bug-Id: 261056991
Change-Id: I776fc64387db94f052f3730a540e9aef2e18432d
Signed-off-by: Vlad Sytchenko <vsytch@google.com>
diff --git a/recipes-phosphor/configuration/entity-manager/0001-fru_device-limit-the-fru-scan-range-to-0x50-0x57.patch b/recipes-phosphor/configuration/entity-manager/0001-fru_device-limit-the-fru-scan-range-to-0x50-0x57.patch
new file mode 100644
index 0000000..d0f4b87
--- /dev/null
+++ b/recipes-phosphor/configuration/entity-manager/0001-fru_device-limit-the-fru-scan-range-to-0x50-0x57.patch
@@ -0,0 +1,35 @@
+From 811062fc0d28c16690a20c8678188004498acb2b Mon Sep 17 00:00:00 2001
+From: Tom Tung <shes050117@gmail.com>
+Date: Wed, 12 Oct 2022 00:37:33 +0800
+Subject: [PATCH] fru_device: limit the fru scan range to [0x50, 0x57]
+
+Patch Tracking Bug: b/258045676
+Upstream info / review: https://gerrit.openbmc.org/c/openbmc/entity-manager/+/56967
+Upstream-Status: Submitted (final approach still under discussion)
+Justification: We will find a way to config addresses to be scanned more easily
+ instead of upstreaming the same patch.
+
+Google-Bug-Id: 244114194
+Signed-off-by: Tom Tung <shes050117@gmail.com>
+Change-Id: I4838c346277caa05f5eb01345bfc793d0d8c846e
+---
+ src/fru_device.cpp | 3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+diff --git a/src/fru_device.cpp b/src/fru_device.cpp
+index 7f9f875..f0dd4c9 100644
+--- a/src/fru_device.cpp
++++ b/src/fru_device.cpp
+@@ -574,7 +574,8 @@ static void findI2CDevices(const std::vector<fs::path>& i2cBuses,
+ }
+
+ // fd is closed in this function in case the bus locks up
+- getBusFRUs(file, 0x03, 0x77, bus, device, powerIsOn, objServer);
++ // The FRU device is supposed to be in [0x50, 0x57] in Google.
++ getBusFRUs(file, 0x50, 0x57, bus, device, powerIsOn, objServer);
+
+ if (debug)
+ {
+--
+2.38.0.rc1.362.ged0d419d3c-goog
+
diff --git a/recipes-phosphor/configuration/entity-manager/0002-Publish-the-root-entity-object-path.patch b/recipes-phosphor/configuration/entity-manager/0002-Publish-the-root-entity-object-path.patch
new file mode 100644
index 0000000..c09dbe7
--- /dev/null
+++ b/recipes-phosphor/configuration/entity-manager/0002-Publish-the-root-entity-object-path.patch
@@ -0,0 +1,66 @@
+From 51df139c1e8b78d5286ac7bfb1171482acde15e6 Mon Sep 17 00:00:00 2001
+From: Jie Yang <jjy@google.com>
+Date: Mon, 16 Aug 2021 14:29:44 -0700
+Subject: [PATCH] Publish the root entity object path
+
+When scanning the system I2C topology, entity-manager can perceive the
+motherboard from multiple entity I2C EEPROM. The system I2C topology
+would be determined at run time. Any add-in boards/cards plugged into
+the system via connectors with I2C bus pass-through would be behind a
+motherboard I2C bus. This change makes the entity-manager "ReScan"
+method return the motherboard DBus inventory object path. The
+motherboard object then would be associated with other system resources.
+
+Tested:
+Tested on a system with motherboard + IO risers + PCIE boards. DBus call
+on entity-manager ReScan method returns the motherboard object path.
+
+busctl call xyz.openbmc_project.EntityManager \
+/xyz/openbmc_project/EntityManager xyz.openbmc_project.EntityManager \
+ReScan
+s "/xyz/openbmc_project/inventory/system/board/GSZ"
+
+Signed-off-by: Jie Yang <jjy@google.com>
+Change-Id: Ib71bb00635751cb55e86a406147a60f2493e844b
+
+---
+ src/entity_manager.cpp | 11 ++++++++++-
+ 1 file changed, 10 insertions(+), 1 deletion(-)
+
+diff --git a/src/entity_manager.cpp b/src/entity_manager.cpp
+index e024890..e028e09 100644
+--- a/src/entity_manager.cpp
++++ b/src/entity_manager.cpp
+@@ -51,6 +51,7 @@ constexpr const char* tempConfigDir = "/tmp/configuration/";
+ constexpr const char* lastConfiguration = "/tmp/configuration/last.json";
+ constexpr const char* currentConfiguration = "/var/configuration/system.json";
+ constexpr const char* globalSchema = "global.json";
++static std::string rootEntityObjPath;
+
+ const boost::container::flat_map<const char*, probe_type_codes, CmpStr>
+ probeTypes{{{"FALSE", probe_type_codes::FALSE_T},
+@@ -929,6 +930,13 @@ void postToDbus(const nlohmann::json& newConfiguration,
+ root.push(i);
+ }
+ }
++
++ // The motherboard would be the root of I2C topology
++ if (root.size() == 1)
++ {
++ rootEntityObjPath = std::get<0>(indexNodeMap[root.front()]);
++ }
++
+ while (!root.empty())
+ {
+ auto nd = root.front();
+@@ -1355,8 +1363,9 @@ int main()
+ io.post(
+ [&]() { propertiesChangedCallback(systemConfiguration, objServer); });
+
+- entityIface->register_method("ReScan", [&]() {
++ entityIface->register_method("ReScan", [&]() -> std::string {
+ propertiesChangedCallback(systemConfiguration, objServer);
++ return rootEntityObjPath;
+ });
+ entityIface->initialize();
+
diff --git a/recipes-phosphor/configuration/entity-manager/0002-entity-manager-Build-entity-inventory-association.patch b/recipes-phosphor/configuration/entity-manager/0002-entity-manager-Build-entity-inventory-association.patch
new file mode 100644
index 0000000..a714e9a
--- /dev/null
+++ b/recipes-phosphor/configuration/entity-manager/0002-entity-manager-Build-entity-inventory-association.patch
@@ -0,0 +1,423 @@
+From 2a4037b751af3c30cb29cba821df234f3c9aebfe Mon Sep 17 00:00:00 2001
+From: Josh Lehan <krellan@google.com>
+Date: Wed, 18 May 2022 15:24:02 -0700
+Subject: [PATCH] entity-manager: Build entity inventory association
+
+The entity inventory objects with physical connection would associate
+with each other. Build the association based on the I2C topology.
+
+As an illustration, assuming we have two entity config as follows:
+
+{
+ "Exposes": [
+ {
+ "Bus": "2",
+ "Type": "I2CConnector",
+ "Label": "PE0",
+ ...
+ },
+ ...
+ ]
+ "Name": "Motherboard",
+ "Type": "Board",
+ ...
+}
+
+{
+ "Exposes": [],
+ "Probe": [
+ "FOUND('Motherboard')",
+ "AND",
+ "xyz.openbmc_project.FruDevice({'PRODUCT_PRODUCT_NAME': 'Magic'})"
+ ],
+ "Name": "Magic",
+ "Type": "Board",
+ ...
+}
+
+The motherboard exposes an I2CConnector on I2C bus 2 that is a PCIE slot
+in this example. When the add-in Magic board eeprom is also probed on
+bus 2, this implementation will create association between the
+motherboard and the Magic board. Also it publishes the connector label
+as xyz.openbmc_project.Inventory.Decorator.LocationCode interface of the
+Magic board. The association and location code indicate that the Magic
+board is installed on motherboard via a connector with label PE0.
+
+This implementation can detect physical connectivity among entities that
+process an I2C EEPROM. The physical connectivity then can be applied to
+construct the machine topology.
+
+Tested:
+Tested on a system with motherboard + IO riser + PCIE board. The
+implementation can contruct expected association between motherboard and
+IO riser and also association between IO riser and PCIE board.
+
+Signed-off-by: Jie Yang <jjy@google.com>
+Change-Id: I9739f3ab123c94fb1b479ab51c148dbf45eaeea0
+
+---
+ include/utils.hpp | 27 +++++
+ src/entity_manager.cpp | 230 +++++++++++++++++++++++++++++++++++++++++
+ src/fru_device.cpp | 27 -----
+ src/perform_scan.cpp | 6 ++
+ 4 files changed, 263 insertions(+), 27 deletions(-)
+
+diff --git a/include/utils.hpp b/include/utils.hpp
+index 99e2f85..2b104c4 100644
+--- a/include/utils.hpp
++++ b/include/utils.hpp
+@@ -161,3 +161,30 @@ inline bool deviceHasLogging(const nlohmann::json& json)
+ /// \param dbusValue the property value being matched to a probe.
+ /// \return true if the dbusValue matched the probe otherwise false.
+ bool matchProbe(const nlohmann::json& probe, const DBusValueVariant& dbusValue);
++
++inline int getRootBus(size_t bus)
++{
++ auto ec = std::error_code();
++ auto path = std::filesystem::read_symlink(
++ std::filesystem::path("/sys/bus/i2c/devices/i2c-" +
++ std::to_string(bus) + "/mux_device"),
++ ec);
++ if (ec)
++ {
++ return -1;
++ }
++
++ std::string filename = path.filename();
++ auto findBus = filename.find('-');
++ if (findBus == std::string::npos)
++ {
++ return -1;
++ }
++ return std::stoi(filename.substr(0, findBus));
++}
++
++inline bool isMuxBus(size_t bus)
++{
++ return is_symlink(std::filesystem::path(
++ "/sys/bus/i2c/devices/i2c-" + std::to_string(bus) + "/mux_device"));
++}
+diff --git a/src/entity_manager.cpp b/src/entity_manager.cpp
+index 7d85d53..e024890 100644
+--- a/src/entity_manager.cpp
++++ b/src/entity_manager.cpp
+@@ -41,6 +41,7 @@
+ #include <functional>
+ #include <iostream>
+ #include <map>
++#include <queue>
+ #include <regex>
+ #include <variant>
+ constexpr const char* hostConfigurationDirectory = SYSCONF_DIR "configurations";
+@@ -565,6 +566,27 @@ void postToDbus(const nlohmann::json& newConfiguration,
+ sdbusplus::asio::object_server& objServer)
+
+ {
++
++ // I2C bus -> {(entity path, entity name, entity type)}
++ // I2C bus is the entity EEPROM I2C bus that is usually probed from
++ // FruDevice service. If a entity is added in a machine through a connector
++ // with I2C pass-through and does not process an EEPROM, user can hardcode
++ // the I2C bus in the config.
++ using BusEntityPathName = boost::container::flat_map<
++ uint64_t,
++ std::vector<std::tuple<std::string, std::string, std::string>>>;
++
++ // I2C bus -> {(connector path, entity name, entity type, label)}
++ // I2C bus is the bus passing though the connector. The bus is usually
++ // hardcoded in the config. Now it does not support bus behind a mux that
++ // determined in the runtime
++ using BusSlotProerties = boost::container::flat_map<
++ uint64_t, std::vector<std::tuple<std::string, std::string, std::string,
++ std::string>>>;
++
++ BusEntityPathName i2cEntities;
++ BusSlotProerties i2cSlots;
++
+ // iterate through boards
+ for (const auto& [boardId, boardConfig] : newConfiguration.items())
+ {
+@@ -612,6 +634,16 @@ void postToDbus(const nlohmann::json& newConfiguration,
+
+ populateInterfaceFromJson(systemConfiguration, jsonPointerPath,
+ boardIface, boardValues, objServer);
++
++ // Build I2C:Entity map
++ auto findBoardBus = boardValues.find("Bus");
++ if (findBoardBus != boardValues.end() &&
++ findBoardBus->type() == nlohmann::json::value_t::number_unsigned)
++ {
++ i2cEntities[findBoardBus->get<uint64_t>()].emplace_back(
++ std::make_tuple(boardName, boardKeyOrig, boardType));
++ }
++
+ jsonPointerPath += "/";
+ // iterate through board properties
+ for (const auto& [propName, propValue] : boardValues.items())
+@@ -679,6 +711,26 @@ void postToDbus(const nlohmann::json& newConfiguration,
+ ifacePath += "/";
+ ifacePath += itemName;
+
++ // The connector slot on board with an I2C bus pass-through is
++ // exposed a feature in the entity config. Now use an abstracted
++ // type including PCIE slots, IO ports, et.al. The same I2C
++ // bus would be seen in a connected downstream entity. That an
++ // I2C bus is observed in two entities indicates connectivity
++ // between those entities.
++ auto findBus = item.find("Bus");
++ if (itemType == "I2CConnector" && findBus != item.end() &&
++ findBus->type() == nlohmann::json::value_t::number_unsigned)
++ {
++ auto findLabel = item.find("Label");
++ if (findLabel != item.end())
++ {
++ std::string itemLabel = findLabel->get<std::string>();
++ i2cSlots[findBus->get<uint64_t>()].emplace_back(
++ std::make_tuple(ifacePath, boardKeyOrig, boardType,
++ itemLabel));
++ }
++ }
++
+ std::shared_ptr<sdbusplus::asio::dbus_interface> itemIface =
+ createInterface(objServer, ifacePath,
+ "xyz.openbmc_project.Configuration." + itemType,
+@@ -759,6 +811,184 @@ void postToDbus(const nlohmann::json& newConfiguration,
+ }
+ }
+ }
++
++ // All the DBus objects and interfaces are created. Try to construct entity
++ // inventory objects association based on I2C topology
++ // Node -> entity object path, entity name, entity type indicating entites
++ // Edge -> connector object path, entity name, entity type, connector
++ // label indicating connectors
++ using Node = std::tuple<std::string, std::string, std::string>;
++ using Edge = std::tuple<std::string, std::string, std::string, std::string>;
++ using Graph = boost::container::flat_map<
++ uint32_t, boost::container::flat_map<uint32_t, uint32_t>>;
++
++ // label the nodes and edges
++ uint32_t ndx = 0, egx = 0;
++ boost::container::flat_map<uint32_t, Node> indexNodeMap;
++ boost::container::flat_map<Node, uint32_t> nodeIndexMap;
++ boost::container::flat_map<uint32_t, Edge> indexEdgeMap;
++ boost::container::flat_map<Edge, uint32_t> edgeIndexMap;
++ Graph graph;
++ for (const auto& i2cEntity : i2cEntities)
++ {
++ auto i2c = i2cEntity.first;
++
++ // Check if the entity EEPROM I2C bus also points to a connector
++ while (i2cSlots.find(i2c) == i2cSlots.end() && isMuxBus(i2c))
++ {
++ auto rootBus = getRootBus(i2c);
++ if (rootBus < 0)
++ {
++ break;
++ }
++ i2c = rootBus;
++ }
++ if (i2cSlots.find(i2c) == i2cSlots.end())
++ {
++ continue;
++ }
++ for (const auto& entity : i2cEntity.second)
++ {
++ for (const auto& slot : i2cSlots[i2c])
++ {
++ auto slotPath = std::get<0>(slot);
++ auto entityPath =
++ slotPath.substr(0, slotPath.find_last_of('/'));
++ Node connectedEntity = std::make_tuple(
++ entityPath, std::get<1>(slot), std::get<2>(slot));
++
++ // no self-connection
++ if (connectedEntity != entity)
++ {
++ uint32_t inNodeId, outNodeId, eId;
++ if (nodeIndexMap.find(connectedEntity) !=
++ nodeIndexMap.end())
++ {
++ inNodeId = nodeIndexMap[connectedEntity];
++ }
++ else
++ {
++ inNodeId = ndx++;
++ nodeIndexMap.emplace(connectedEntity, inNodeId);
++ indexNodeMap.emplace(inNodeId, connectedEntity);
++ }
++
++ if (nodeIndexMap.find(entity) != nodeIndexMap.end())
++ {
++ outNodeId = nodeIndexMap[entity];
++ }
++ else
++ {
++ outNodeId = ndx++;
++ nodeIndexMap.emplace(entity, outNodeId);
++ indexNodeMap.emplace(outNodeId, entity);
++ }
++
++ if (edgeIndexMap.find(slot) != edgeIndexMap.end())
++ {
++ eId = edgeIndexMap[slot];
++ }
++ else
++ {
++ eId = egx++;
++ edgeIndexMap.emplace(slot, eId);
++ indexEdgeMap.emplace(eId, slot);
++ }
++ graph[outNodeId].emplace(inNodeId, eId);
++ }
++ }
++ }
++ }
++
++ // Rebuild the graph using adjacent matrix. The graph is a DAG with a single
++ // sink -- the root chassis. Edge weight is 1.
++ std::vector<std::vector<bool>> matrix =
++ std::vector<std::vector<bool>>(ndx, std::vector<bool>(ndx, false));
++
++ // Out-degree of each node. Use it to find the sink of graph.
++ std::vector<uint32_t> out(ndx, 0);
++ for (const auto& edges : graph)
++ {
++ for (const auto& end : edges.second)
++ {
++ matrix[edges.first][end.first] = true;
++ out[edges.first]++;
++ }
++ }
++
++ // BFS. Find all desired edges to construct entity associations.
++ // If serial board connection exists like root -> board_1 -> board_2
++ // associate root with board_1, also board1 with board2. In some user
++ // cases, board_1 and board_2 should be directly associated to root
++ std::queue<uint32_t> root;
++ std::vector<std::tuple<uint32_t, uint32_t, uint32_t>> associations;
++ for (uint32_t i = 0; i < out.size(); i++)
++ {
++ if (out[i] == 0)
++ {
++ root.push(i);
++ }
++ }
++ while (!root.empty())
++ {
++ auto nd = root.front();
++ for (size_t i = 0; i < ndx; i++)
++ {
++ if (!matrix[i][nd])
++ {
++ continue;
++ }
++ matrix[i][nd] = false;
++
++ // Only one edge between the node and sink. Add it to associations
++ if (--out[i] == 0)
++ {
++ associations.emplace_back(std::make_tuple(i, nd, graph[i][nd]));
++ root.push(i);
++ }
++ }
++ root.pop();
++ }
++
++ using Association = std::tuple<std::string, std::string, std::string>;
++ for (const auto& asso : associations)
++ {
++ Node outNode = indexNodeMap[std::get<0>(asso)];
++ Node inNode = indexNodeMap[std::get<1>(asso)];
++ Edge edge = indexEdgeMap[std::get<2>(asso)];
++
++ auto assoIntf =
++ createInterface(objServer, std::get<0>(outNode),
++ "xyz.openbmc_project.Association.Definitions",
++ std::get<1>(outNode));
++ std::string objectPath = std::get<0>(inNode);
++ std::vector<Association> assoProperty;
++
++ std::string upstreamEntityType = std::get<2>(inNode);
++ std::string downstreamEntityType = std::get<2>(outNode);
++
++ if (upstreamEntityType == "Board" && downstreamEntityType == "Board")
++ {
++ assoProperty.emplace_back("containedby", "contains", objectPath);
++ }
++
++ if (upstreamEntityType == "Board" &&
++ downstreamEntityType == "PowerSupply")
++ {
++ assoProperty.emplace_back("chassis", "poweredby", objectPath);
++ }
++
++ assoIntf->register_property("Associations", assoProperty);
++ assoIntf->initialize();
++
++ // We use the connector printted label as the enitiy location
++ auto locIntf = createInterface(
++ objServer, std::get<0>(outNode),
++ "xyz.openbmc_project.Inventory.Decorator.LocationCode",
++ std::get<1>(outNode));
++ locIntf->register_property("LocationCode", std::get<3>(edge));
++ locIntf->initialize();
++ }
+ }
+
+ // reads json files out of the filesystem
+diff --git a/src/fru_device.cpp b/src/fru_device.cpp
+index c679741..ca28a48 100644
+--- a/src/fru_device.cpp
++++ b/src/fru_device.cpp
+@@ -145,33 +145,6 @@ static int busStrToInt(const std::string_view busName)
+ return val;
+ }
+
+-static int getRootBus(size_t bus)
+-{
+- auto ec = std::error_code();
+- auto path = std::filesystem::read_symlink(
+- std::filesystem::path("/sys/bus/i2c/devices/i2c-" +
+- std::to_string(bus) + "/mux_device"),
+- ec);
+- if (ec)
+- {
+- return -1;
+- }
+-
+- std::string filename = path.filename();
+- auto findBus = filename.find('-');
+- if (findBus == std::string::npos)
+- {
+- return -1;
+- }
+- return std::stoi(filename.substr(0, findBus));
+-}
+-
+-static bool isMuxBus(size_t bus)
+-{
+- return is_symlink(std::filesystem::path(
+- "/sys/bus/i2c/devices/i2c-" + std::to_string(bus) + "/mux_device"));
+-}
+-
+ static void makeProbeInterface(size_t bus, size_t address,
+ sdbusplus::asio::object_server& objServer)
+ {
+diff --git a/src/perform_scan.cpp b/src/perform_scan.cpp
+index 44f284c..77efc13 100644
+--- a/src/perform_scan.cpp
++++ b/src/perform_scan.cpp
+@@ -615,6 +615,12 @@ void PerformScan::run()
+ continue;
+ }
+
++ // Use the I2C bus to mark entities if an I2C bus is probed.
++ if (it->find("Bus") == it->end())
++ {
++ it->update(R"( {"Bus": "$bus"} )"_json);
++ }
++
+ nlohmann::json& recordRef = *it;
+ nlohmann::json probeCommand;
+ if ((*findProbe).type() != nlohmann::json::value_t::array)
diff --git a/recipes-phosphor/configuration/entity-manager_%.bbappend b/recipes-phosphor/configuration/entity-manager_%.bbappend
new file mode 100644
index 0000000..3b05e3e
--- /dev/null
+++ b/recipes-phosphor/configuration/entity-manager_%.bbappend
@@ -0,0 +1,7 @@
+FILESEXTRAPATHS:prepend:gbmc := "${THISDIR}/${PN}:"
+
+SRC_URI:append:gbmc = " \
+ file://0002-entity-manager-Build-entity-inventory-association.patch \
+ file://0002-Publish-the-root-entity-object-path.patch \
+ file://0001-fru_device-limit-the-fru-scan-range-to-0x50-0x57.patch \
+"