blob: 6a003f5c34e35a0fa6d8df3d4607dbbe69a46781 [file] [log] [blame]
James Feist3cb5fec2018-01-23 14:41:51 -08001/*
2// Copyright (c) 2018 Intel Corporation
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8// http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15*/
Brad Bishope45d8c72022-05-25 15:12:53 -040016/// \file entity_manager.cpp
James Feist3cb5fec2018-01-23 14:41:51 -080017
Brad Bishope45d8c72022-05-25 15:12:53 -040018#include "entity_manager.hpp"
James Feist1df06a42019-04-11 14:23:04 -070019
Christopher Meisfc9e7fd2025-04-03 13:13:35 +020020#include "../utils.hpp"
21#include "../variant_visitors.hpp"
Christopher Meisbdaa6b22025-04-02 10:49:02 +020022#include "configuration.hpp"
Christopher Meis12bea9b2025-04-03 10:14:42 +020023#include "dbus_interface.hpp"
Brad Bishope45d8c72022-05-25 15:12:53 -040024#include "overlay.hpp"
Christopher Meis26fbbd52025-03-26 14:55:06 +010025#include "perform_scan.hpp"
Benjamin Fairca2eb042022-09-13 06:40:42 +000026#include "topology.hpp"
Christopher Meis59ef1e72025-04-16 08:53:25 +020027#include "utils.hpp"
James Feist481c5d52019-08-13 14:40:40 -070028
James Feist11be6672018-04-06 14:05:32 -070029#include <boost/algorithm/string/case_conv.hpp>
James Feistf5125b02019-06-06 11:27:43 -070030#include <boost/algorithm/string/classification.hpp>
James Feist3cb5fec2018-01-23 14:41:51 -080031#include <boost/algorithm/string/predicate.hpp>
32#include <boost/algorithm/string/replace.hpp>
James Feistf5125b02019-06-06 11:27:43 -070033#include <boost/algorithm/string/split.hpp>
James Feist02d2b932020-02-06 16:28:48 -080034#include <boost/asio/io_context.hpp>
Ed Tanous49a888c2023-03-06 13:44:51 -080035#include <boost/asio/post.hpp>
James Feistb1728ca2020-04-30 15:40:55 -070036#include <boost/asio/steady_timer.hpp>
James Feist3cb5fec2018-01-23 14:41:51 -080037#include <boost/container/flat_map.hpp>
38#include <boost/container/flat_set.hpp>
James Feistf5125b02019-06-06 11:27:43 -070039#include <boost/range/iterator_range.hpp>
James Feist8c505da2020-05-28 10:06:33 -070040#include <nlohmann/json.hpp>
41#include <sdbusplus/asio/connection.hpp>
42#include <sdbusplus/asio/object_server.hpp>
43
James Feist637b3ef2019-04-15 16:35:30 -070044#include <filesystem>
James Feista465ccc2019-02-08 12:51:01 -080045#include <fstream>
Andrew Jefferye35d0ac2022-03-24 15:53:13 +103046#include <functional>
James Feista465ccc2019-02-08 12:51:01 -080047#include <iostream>
Andrew Jeffery666583b2021-12-01 15:50:12 +103048#include <map>
James Feista465ccc2019-02-08 12:51:01 -080049#include <regex>
James Feist1df06a42019-04-11 14:23:04 -070050constexpr const char* tempConfigDir = "/tmp/configuration/";
51constexpr const char* lastConfiguration = "/tmp/configuration/last.json";
James Feistf1b14142019-04-10 15:22:09 -070052
Adrian Ambrożewiczc789fca2020-05-14 15:50:05 +020053static constexpr std::array<const char*, 6> settableInterfaces = {
54 "FanProfile", "Pid", "Pid.Zone", "Stepwise", "Thresholds", "Polling"};
James Feist3cb5fec2018-01-23 14:41:51 -080055
Ed Tanousfc171422024-04-04 17:18:16 -070056// NOLINTBEGIN(cppcoreguidelines-avoid-non-const-global-variables)
James Feist02d2b932020-02-06 16:28:48 -080057boost::asio::io_context io;
Ed Tanousfc171422024-04-04 17:18:16 -070058// NOLINTEND(cppcoreguidelines-avoid-non-const-global-variables)
James Feist02d2b932020-02-06 16:28:48 -080059
Ed Tanous07d467b2021-02-23 14:48:37 -080060const std::regex illegalDbusPathRegex("[^A-Za-z0-9_.]");
61const std::regex illegalDbusMemberRegex("[^A-Za-z0-9_]");
James Feist1b2e2242018-01-30 13:45:19 -080062
James Feista465ccc2019-02-08 12:51:01 -080063sdbusplus::asio::PropertyPermission getPermission(const std::string& interface)
James Feistc6248a52018-08-14 10:09:45 -070064{
65 return std::find(settableInterfaces.begin(), settableInterfaces.end(),
66 interface) != settableInterfaces.end()
67 ? sdbusplus::asio::PropertyPermission::readWrite
68 : sdbusplus::asio::PropertyPermission::readOnly;
69}
70
Christopher Meiscf6a75b2025-06-03 07:53:50 +020071EntityManager::EntityManager(
72 std::shared_ptr<sdbusplus::asio::connection>& systemBus) :
73 systemBus(systemBus),
74 objServer(sdbusplus::asio::object_server(systemBus, /*skipManager=*/true)),
75 lastJson(nlohmann::json::object()),
76 systemConfiguration(nlohmann::json::object())
77{
78 // All other objects that EntityManager currently support are under the
79 // inventory subtree.
80 // See the discussion at
81 // https://discord.com/channels/775381525260664832/1018929092009144380
82 objServer.add_manager("/xyz/openbmc_project/inventory");
James Feist75fdeeb2018-02-20 14:26:16 -080083
Christopher Meiscf6a75b2025-06-03 07:53:50 +020084 entityIface = objServer.add_interface("/xyz/openbmc_project/EntityManager",
85 "xyz.openbmc_project.EntityManager");
86 entityIface->register_method("ReScan", [this]() {
87 propertiesChangedCallback();
88 });
89 dbus_interface::tryIfaceInitialize(entityIface);
90}
91
92void EntityManager::postToDbus(const nlohmann::json& newConfiguration)
James Feist1b2e2242018-01-30 13:45:19 -080093{
Matt Spinler6eb60972023-08-14 16:36:20 -050094 std::map<std::string, std::string> newBoards; // path -> name
Benjamin Fairca2eb042022-09-13 06:40:42 +000095
James Feist97a63f12018-05-17 13:50:57 -070096 // iterate through boards
Patrick Williams2594d362022-09-28 06:46:24 -050097 for (const auto& [boardId, boardConfig] : newConfiguration.items())
James Feist1b2e2242018-01-30 13:45:19 -080098 {
Matt Spinler3d1909a2023-08-10 16:39:44 -050099 std::string boardName = boardConfig["Name"];
100 std::string boardNameOrig = boardConfig["Name"];
Andrew Jeffery13132df2022-03-25 13:29:41 +1030101 std::string jsonPointerPath = "/" + boardId;
James Feist97a63f12018-05-17 13:50:57 -0700102 // loop through newConfiguration, but use values from system
103 // configuration to be able to modify via dbus later
Andrew Jeffery13132df2022-03-25 13:29:41 +1030104 auto boardValues = systemConfiguration[boardId];
James Feistd63d18a2018-07-19 15:23:45 -0700105 auto findBoardType = boardValues.find("Type");
James Feist1b2e2242018-01-30 13:45:19 -0800106 std::string boardType;
107 if (findBoardType != boardValues.end() &&
108 findBoardType->type() == nlohmann::json::value_t::string)
109 {
110 boardType = findBoardType->get<std::string>();
111 std::regex_replace(boardType.begin(), boardType.begin(),
Ed Tanous07d467b2021-02-23 14:48:37 -0800112 boardType.end(), illegalDbusMemberRegex, "_");
James Feist1b2e2242018-01-30 13:45:19 -0800113 }
114 else
115 {
Matt Spinler3d1909a2023-08-10 16:39:44 -0500116 std::cerr << "Unable to find type for " << boardName
James Feist1b2e2242018-01-30 13:45:19 -0800117 << " reverting to Chassis.\n";
118 boardType = "Chassis";
119 }
James Feist11be6672018-04-06 14:05:32 -0700120 std::string boardtypeLower = boost::algorithm::to_lower_copy(boardType);
James Feist1b2e2242018-01-30 13:45:19 -0800121
Matt Spinler3d1909a2023-08-10 16:39:44 -0500122 std::regex_replace(boardName.begin(), boardName.begin(),
123 boardName.end(), illegalDbusMemberRegex, "_");
124 std::string boardPath = "/xyz/openbmc_project/inventory/system/";
125 boardPath += boardtypeLower;
126 boardPath += "/";
127 boardPath += boardName;
James Feist1b2e2242018-01-30 13:45:19 -0800128
James Feistd58879a2019-09-11 11:26:07 -0700129 std::shared_ptr<sdbusplus::asio::dbus_interface> inventoryIface =
Christopher Meis12bea9b2025-04-03 10:14:42 +0200130 dbus_interface::createInterface(
131 objServer, boardPath, "xyz.openbmc_project.Inventory.Item",
132 boardName);
James Feist68500ff2018-08-08 15:40:42 -0700133
James Feistd58879a2019-09-11 11:26:07 -0700134 std::shared_ptr<sdbusplus::asio::dbus_interface> boardIface =
Christopher Meis12bea9b2025-04-03 10:14:42 +0200135 dbus_interface::createInterface(
136 objServer, boardPath,
137 "xyz.openbmc_project.Inventory.Item." + boardType,
138 boardNameOrig);
James Feist11be6672018-04-06 14:05:32 -0700139
Christopher Meis12bea9b2025-04-03 10:14:42 +0200140 dbus_interface::createAddObjectMethod(
141 jsonPointerPath, boardPath, systemConfiguration, objServer,
142 boardNameOrig);
James Feist68500ff2018-08-08 15:40:42 -0700143
Christopher Meis12bea9b2025-04-03 10:14:42 +0200144 dbus_interface::populateInterfaceFromJson(
145 systemConfiguration, jsonPointerPath, boardIface, boardValues,
146 objServer);
James Feist97a63f12018-05-17 13:50:57 -0700147 jsonPointerPath += "/";
148 // iterate through board properties
Patrick Williams2594d362022-09-28 06:46:24 -0500149 for (const auto& [propName, propValue] : boardValues.items())
James Feist11be6672018-04-06 14:05:32 -0700150 {
Andrew Jefferya96950d2022-03-25 13:32:46 +1030151 if (propValue.type() == nlohmann::json::value_t::object)
James Feist11be6672018-04-06 14:05:32 -0700152 {
James Feistd58879a2019-09-11 11:26:07 -0700153 std::shared_ptr<sdbusplus::asio::dbus_interface> iface =
Christopher Meis12bea9b2025-04-03 10:14:42 +0200154 dbus_interface::createInterface(objServer, boardPath,
155 propName, boardNameOrig);
James Feistd58879a2019-09-11 11:26:07 -0700156
Christopher Meis12bea9b2025-04-03 10:14:42 +0200157 dbus_interface::populateInterfaceFromJson(
158 systemConfiguration, jsonPointerPath + propName, iface,
159 propValue, objServer);
James Feist11be6672018-04-06 14:05:32 -0700160 }
161 }
James Feist97a63f12018-05-17 13:50:57 -0700162
James Feist1e3e6982018-08-03 16:09:28 -0700163 auto exposes = boardValues.find("Exposes");
James Feist1b2e2242018-01-30 13:45:19 -0800164 if (exposes == boardValues.end())
165 {
166 continue;
167 }
James Feist97a63f12018-05-17 13:50:57 -0700168 // iterate through exposes
James Feist1e3e6982018-08-03 16:09:28 -0700169 jsonPointerPath += "Exposes/";
James Feist97a63f12018-05-17 13:50:57 -0700170
171 // store the board level pointer so we can modify it on the way down
172 std::string jsonPointerPathBoard = jsonPointerPath;
173 size_t exposesIndex = -1;
James Feista465ccc2019-02-08 12:51:01 -0800174 for (auto& item : *exposes)
James Feist1b2e2242018-01-30 13:45:19 -0800175 {
James Feist97a63f12018-05-17 13:50:57 -0700176 exposesIndex++;
177 jsonPointerPath = jsonPointerPathBoard;
178 jsonPointerPath += std::to_string(exposesIndex);
179
James Feistd63d18a2018-07-19 15:23:45 -0700180 auto findName = item.find("Name");
James Feist1b2e2242018-01-30 13:45:19 -0800181 if (findName == item.end())
182 {
183 std::cerr << "cannot find name in field " << item << "\n";
184 continue;
185 }
James Feist1e3e6982018-08-03 16:09:28 -0700186 auto findStatus = item.find("Status");
James Feist1b2e2242018-01-30 13:45:19 -0800187 // if status is not found it is assumed to be status = 'okay'
188 if (findStatus != item.end())
189 {
190 if (*findStatus == "disabled")
191 {
192 continue;
193 }
194 }
James Feistd63d18a2018-07-19 15:23:45 -0700195 auto findType = item.find("Type");
James Feist1b2e2242018-01-30 13:45:19 -0800196 std::string itemType;
197 if (findType != item.end())
198 {
199 itemType = findType->get<std::string>();
200 std::regex_replace(itemType.begin(), itemType.begin(),
Ed Tanous07d467b2021-02-23 14:48:37 -0800201 itemType.end(), illegalDbusPathRegex, "_");
James Feist1b2e2242018-01-30 13:45:19 -0800202 }
203 else
204 {
205 itemType = "unknown";
206 }
207 std::string itemName = findName->get<std::string>();
208 std::regex_replace(itemName.begin(), itemName.begin(),
Ed Tanous07d467b2021-02-23 14:48:37 -0800209 itemName.end(), illegalDbusMemberRegex, "_");
Matt Spinler3d1909a2023-08-10 16:39:44 -0500210 std::string ifacePath = boardPath;
Ed Tanous07d467b2021-02-23 14:48:37 -0800211 ifacePath += "/";
212 ifacePath += itemName;
James Feistc6248a52018-08-14 10:09:45 -0700213
Sui Chen74ebe592022-09-13 10:22:03 -0700214 if (itemType == "BMC")
215 {
216 std::shared_ptr<sdbusplus::asio::dbus_interface> bmcIface =
Christopher Meis12bea9b2025-04-03 10:14:42 +0200217 dbus_interface::createInterface(
218 objServer, ifacePath,
219 "xyz.openbmc_project.Inventory.Item.Bmc",
220 boardNameOrig);
221 dbus_interface::populateInterfaceFromJson(
222 systemConfiguration, jsonPointerPath, bmcIface, item,
223 objServer, getPermission(itemType));
Sui Chen74ebe592022-09-13 10:22:03 -0700224 }
Edward Leeeb587b42023-03-08 18:59:04 +0000225 else if (itemType == "System")
226 {
227 std::shared_ptr<sdbusplus::asio::dbus_interface> systemIface =
Christopher Meis12bea9b2025-04-03 10:14:42 +0200228 dbus_interface::createInterface(
229 objServer, ifacePath,
230 "xyz.openbmc_project.Inventory.Item.System",
231 boardNameOrig);
232 dbus_interface::populateInterfaceFromJson(
233 systemConfiguration, jsonPointerPath, systemIface, item,
234 objServer, getPermission(itemType));
Edward Leeeb587b42023-03-08 18:59:04 +0000235 }
Sui Chen74ebe592022-09-13 10:22:03 -0700236
Patrick Williams2594d362022-09-28 06:46:24 -0500237 for (const auto& [name, config] : item.items())
James Feist1b2e2242018-01-30 13:45:19 -0800238 {
Andrew Jeffery5a6379c2022-03-25 13:25:31 +1030239 jsonPointerPath = jsonPointerPathBoard;
240 jsonPointerPath.append(std::to_string(exposesIndex))
241 .append("/")
242 .append(name);
243 if (config.type() == nlohmann::json::value_t::object)
James Feist1b2e2242018-01-30 13:45:19 -0800244 {
Andrew Jeffery5a6379c2022-03-25 13:25:31 +1030245 std::string ifaceName =
246 "xyz.openbmc_project.Configuration.";
247 ifaceName.append(itemType).append(".").append(name);
James Feist97a63f12018-05-17 13:50:57 -0700248
Andrew Jeffery5a6379c2022-03-25 13:25:31 +1030249 std::shared_ptr<sdbusplus::asio::dbus_interface>
Christopher Meis12bea9b2025-04-03 10:14:42 +0200250 objectIface = dbus_interface::createInterface(
251 objServer, ifacePath, ifaceName, boardNameOrig);
Andrew Jeffery5a6379c2022-03-25 13:25:31 +1030252
Christopher Meis12bea9b2025-04-03 10:14:42 +0200253 dbus_interface::populateInterfaceFromJson(
Andrew Jeffery5a6379c2022-03-25 13:25:31 +1030254 systemConfiguration, jsonPointerPath, objectIface,
255 config, objServer, getPermission(name));
James Feist1b2e2242018-01-30 13:45:19 -0800256 }
Andrew Jeffery5a6379c2022-03-25 13:25:31 +1030257 else if (config.type() == nlohmann::json::value_t::array)
James Feist1b2e2242018-01-30 13:45:19 -0800258 {
259 size_t index = 0;
Ed Tanous3013fb42022-07-09 08:27:06 -0700260 if (config.empty())
James Feist1b2e2242018-01-30 13:45:19 -0800261 {
James Feist8f2710a2018-05-09 17:18:55 -0700262 continue;
263 }
264 bool isLegal = true;
Andrew Jeffery5a6379c2022-03-25 13:25:31 +1030265 auto type = config[0].type();
James Feist8f2710a2018-05-09 17:18:55 -0700266 if (type != nlohmann::json::value_t::object)
267 {
268 continue;
269 }
270
271 // verify legal json
Andrew Jeffery5a6379c2022-03-25 13:25:31 +1030272 for (const auto& arrayItem : config)
James Feist8f2710a2018-05-09 17:18:55 -0700273 {
274 if (arrayItem.type() != type)
James Feist1b2e2242018-01-30 13:45:19 -0800275 {
James Feist8f2710a2018-05-09 17:18:55 -0700276 isLegal = false;
James Feist1b2e2242018-01-30 13:45:19 -0800277 break;
278 }
James Feist8f2710a2018-05-09 17:18:55 -0700279 }
280 if (!isLegal)
281 {
Andrew Jeffery5a6379c2022-03-25 13:25:31 +1030282 std::cerr << "dbus format error" << config << "\n";
James Feist8f2710a2018-05-09 17:18:55 -0700283 break;
284 }
285
Andrew Jeffery5a6379c2022-03-25 13:25:31 +1030286 for (auto& arrayItem : config)
James Feist8f2710a2018-05-09 17:18:55 -0700287 {
Andrew Jeffery5a6379c2022-03-25 13:25:31 +1030288 std::string ifaceName =
289 "xyz.openbmc_project.Configuration.";
290 ifaceName.append(itemType).append(".").append(name);
291 ifaceName.append(std::to_string(index));
James Feist97a63f12018-05-17 13:50:57 -0700292
James Feistd58879a2019-09-11 11:26:07 -0700293 std::shared_ptr<sdbusplus::asio::dbus_interface>
Christopher Meis12bea9b2025-04-03 10:14:42 +0200294 objectIface = dbus_interface::createInterface(
Matt Spinler3d1909a2023-08-10 16:39:44 -0500295 objServer, ifacePath, ifaceName, boardNameOrig);
James Feistd58879a2019-09-11 11:26:07 -0700296
Christopher Meis12bea9b2025-04-03 10:14:42 +0200297 dbus_interface::populateInterfaceFromJson(
James Feistc6248a52018-08-14 10:09:45 -0700298 systemConfiguration,
299 jsonPointerPath + "/" + std::to_string(index),
300 objectIface, arrayItem, objServer,
Andrew Jeffery5a6379c2022-03-25 13:25:31 +1030301 getPermission(name));
James Feistbb43d022018-06-12 15:44:33 -0700302 index++;
James Feist1b2e2242018-01-30 13:45:19 -0800303 }
304 }
305 }
Benjamin Fairca2eb042022-09-13 06:40:42 +0000306
George Liu5c1a61a2024-10-24 18:02:29 +0800307 std::shared_ptr<sdbusplus::asio::dbus_interface> itemIface =
Christopher Meis12bea9b2025-04-03 10:14:42 +0200308 dbus_interface::createInterface(
309 objServer, ifacePath,
310 "xyz.openbmc_project.Configuration." + itemType,
311 boardNameOrig);
George Liu5c1a61a2024-10-24 18:02:29 +0800312
Christopher Meis12bea9b2025-04-03 10:14:42 +0200313 dbus_interface::populateInterfaceFromJson(
314 systemConfiguration, jsonPointerPath, itemIface, item,
315 objServer, getPermission(itemType));
George Liu5c1a61a2024-10-24 18:02:29 +0800316
Matt Spinler6eb60972023-08-14 16:36:20 -0500317 topology.addBoard(boardPath, boardType, boardNameOrig, item);
James Feist1b2e2242018-01-30 13:45:19 -0800318 }
Matt Spinler6eb60972023-08-14 16:36:20 -0500319
320 newBoards.emplace(boardPath, boardNameOrig);
James Feist1b2e2242018-01-30 13:45:19 -0800321 }
Benjamin Fairca2eb042022-09-13 06:40:42 +0000322
Matt Spinler6eb60972023-08-14 16:36:20 -0500323 for (const auto& [assocPath, assocPropValue] :
324 topology.getAssocs(newBoards))
Benjamin Fairca2eb042022-09-13 06:40:42 +0000325 {
Matt Spinler6eb60972023-08-14 16:36:20 -0500326 auto findBoard = newBoards.find(assocPath);
327 if (findBoard == newBoards.end())
328 {
329 continue;
330 }
Benjamin Fairca2eb042022-09-13 06:40:42 +0000331
Christopher Meis12bea9b2025-04-03 10:14:42 +0200332 auto ifacePtr = dbus_interface::createInterface(
Matt Spinler6eb60972023-08-14 16:36:20 -0500333 objServer, assocPath, "xyz.openbmc_project.Association.Definitions",
334 findBoard->second);
335
336 ifacePtr->register_property("Associations", assocPropValue);
Christopher Meis12bea9b2025-04-03 10:14:42 +0200337 dbus_interface::tryIfaceInitialize(ifacePtr);
Benjamin Fairca2eb042022-09-13 06:40:42 +0000338 }
James Feist1b2e2242018-01-30 13:45:19 -0800339}
340
Andrew Jeffery55192932022-03-24 12:29:27 +1030341static bool deviceRequiresPowerOn(const nlohmann::json& entity)
342{
343 auto powerState = entity.find("PowerState");
Andrew Jefferyb6209442022-03-24 12:36:20 +1030344 if (powerState == entity.end())
Andrew Jeffery55192932022-03-24 12:29:27 +1030345 {
Andrew Jefferyb6209442022-03-24 12:36:20 +1030346 return false;
Andrew Jeffery55192932022-03-24 12:29:27 +1030347 }
348
Ed Tanous3013fb42022-07-09 08:27:06 -0700349 const auto* ptr = powerState->get_ptr<const std::string*>();
350 if (ptr == nullptr)
Andrew Jefferyb6209442022-03-24 12:36:20 +1030351 {
352 return false;
353 }
354
355 return *ptr == "On" || *ptr == "BiosPost";
Andrew Jeffery55192932022-03-24 12:29:27 +1030356}
357
Andrew Jeffery89ec3522022-03-24 13:30:41 +1030358static void pruneDevice(const nlohmann::json& systemConfiguration,
359 const bool powerOff, const bool scannedPowerOff,
360 const std::string& name, const nlohmann::json& device)
361{
362 if (systemConfiguration.contains(name))
363 {
364 return;
365 }
366
Andrew Jeffery4db38bc2022-03-24 13:42:41 +1030367 if (deviceRequiresPowerOn(device) && (powerOff || scannedPowerOff))
Andrew Jeffery89ec3522022-03-24 13:30:41 +1030368 {
Andrew Jeffery89ec3522022-03-24 13:30:41 +1030369 return;
370 }
371
372 logDeviceRemoved(device);
373}
374
James Feistb1728ca2020-04-30 15:40:55 -0700375void startRemovedTimer(boost::asio::steady_timer& timer,
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200376 nlohmann::json& systemConfiguration,
377 nlohmann::json& lastJson)
James Feist1df06a42019-04-11 14:23:04 -0700378{
379 static bool scannedPowerOff = false;
380 static bool scannedPowerOn = false;
381
James Feistfb00f392019-06-25 14:16:48 -0700382 if (systemConfiguration.empty() || lastJson.empty())
383 {
384 return; // not ready yet
385 }
James Feist1df06a42019-04-11 14:23:04 -0700386 if (scannedPowerOn)
387 {
388 return;
389 }
390
Christopher Meis59ef1e72025-04-16 08:53:25 +0200391 if (!em_utils::isPowerOn() && scannedPowerOff)
James Feist1df06a42019-04-11 14:23:04 -0700392 {
393 return;
394 }
395
James Feistb1728ca2020-04-30 15:40:55 -0700396 timer.expires_after(std::chrono::seconds(10));
Andrew Jeffery27a1cfb2022-03-24 12:31:53 +1030397 timer.async_wait(
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200398 [&systemConfiguration, &lastJson](const boost::system::error_code& ec) {
Patrick Williamsb7077432024-08-16 15:22:21 -0400399 if (ec == boost::asio::error::operation_aborted)
400 {
401 return;
402 }
Andrew Jeffery27a1cfb2022-03-24 12:31:53 +1030403
Christopher Meis59ef1e72025-04-16 08:53:25 +0200404 bool powerOff = !em_utils::isPowerOn();
Patrick Williamsb7077432024-08-16 15:22:21 -0400405 for (const auto& [name, device] : lastJson.items())
406 {
407 pruneDevice(systemConfiguration, powerOff, scannedPowerOff,
408 name, device);
409 }
Andrew Jeffery89ec3522022-03-24 13:30:41 +1030410
Patrick Williamsb7077432024-08-16 15:22:21 -0400411 scannedPowerOff = true;
412 if (!powerOff)
413 {
414 scannedPowerOn = true;
415 }
416 });
James Feist1df06a42019-04-11 14:23:04 -0700417}
418
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200419void EntityManager::pruneConfiguration(bool powerOff, const std::string& name,
420 const nlohmann::json& device)
Andrew Jeffery0d0c1462022-03-24 15:26:39 +1030421{
422 if (powerOff && deviceRequiresPowerOn(device))
423 {
424 // power not on yet, don't know if it's there or not
425 return;
426 }
427
Christopher Meis12bea9b2025-04-03 10:14:42 +0200428 auto& ifaces = dbus_interface::getDeviceInterfaces(device);
Andrew Jeffery0d0c1462022-03-24 15:26:39 +1030429 for (auto& iface : ifaces)
430 {
431 auto sharedPtr = iface.lock();
432 if (!!sharedPtr)
433 {
434 objServer.remove_interface(sharedPtr);
435 }
436 }
437
438 ifaces.clear();
439 systemConfiguration.erase(name);
Matt Spinler6eb60972023-08-14 16:36:20 -0500440 topology.remove(device["Name"].get<std::string>());
Andrew Jeffery0d0c1462022-03-24 15:26:39 +1030441 logDeviceRemoved(device);
442}
443
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200444void EntityManager::publishNewConfiguration(
Andrew Jefferye35d0ac2022-03-24 15:53:13 +1030445 const size_t& instance, const size_t count,
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200446 boost::asio::steady_timer& timer, // Gerrit discussion:
Andrew Jefferye35d0ac2022-03-24 15:53:13 +1030447 // https://gerrit.openbmc-project.xyz/c/openbmc/entity-manager/+/52316/6
448 //
449 // Discord discussion:
450 // https://discord.com/channels/775381525260664832/867820390406422538/958048437729910854
451 //
452 // NOLINTNEXTLINE(performance-unnecessary-value-param)
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200453 const nlohmann::json newConfiguration)
Andrew Jefferye35d0ac2022-03-24 15:53:13 +1030454{
455 loadOverlays(newConfiguration);
456
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200457 boost::asio::post(io, [this]() {
Christopher Meisbdaa6b22025-04-02 10:49:02 +0200458 if (!configuration::writeJsonFiles(systemConfiguration))
Andrew Jefferye35d0ac2022-03-24 15:53:13 +1030459 {
460 std::cerr << "Error writing json files\n";
461 }
462 });
463
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200464 boost::asio::post(io, [this, &instance, count, &timer, newConfiguration]() {
465 postToDbus(newConfiguration);
Andrew Jefferye35d0ac2022-03-24 15:53:13 +1030466 if (count == instance)
467 {
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200468 startRemovedTimer(timer, systemConfiguration, lastJson);
Andrew Jefferye35d0ac2022-03-24 15:53:13 +1030469 }
470 });
471}
472
James Feist8f2710a2018-05-09 17:18:55 -0700473// main properties changed entry
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200474void EntityManager::propertiesChangedCallback()
James Feist8f2710a2018-05-09 17:18:55 -0700475{
James Feist2539ccd2020-05-01 16:15:08 -0700476 static bool inProgress = false;
James Feistb1728ca2020-04-30 15:40:55 -0700477 static boost::asio::steady_timer timer(io);
James Feist899e17f2019-09-13 11:46:29 -0700478 static size_t instance = 0;
479 instance++;
480 size_t count = instance;
James Feist1df06a42019-04-11 14:23:04 -0700481
Anupama B Rbf263982024-01-25 04:42:39 -0600482 timer.expires_after(std::chrono::milliseconds(500));
James Feist8f2710a2018-05-09 17:18:55 -0700483
484 // setup an async wait as we normally get flooded with new requests
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200485 timer.async_wait([this, count](const boost::system::error_code& ec) {
James Feist8f2710a2018-05-09 17:18:55 -0700486 if (ec == boost::asio::error::operation_aborted)
487 {
488 // we were cancelled
489 return;
490 }
Ed Tanous07d467b2021-02-23 14:48:37 -0800491 if (ec)
James Feist8f2710a2018-05-09 17:18:55 -0700492 {
493 std::cerr << "async wait error " << ec << "\n";
494 return;
495 }
496
James Feist2539ccd2020-05-01 16:15:08 -0700497 if (inProgress)
498 {
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200499 propertiesChangedCallback();
James Feist2539ccd2020-05-01 16:15:08 -0700500 return;
501 }
502 inProgress = true;
503
James Feist8f2710a2018-05-09 17:18:55 -0700504 nlohmann::json oldConfiguration = systemConfiguration;
James Feist899e17f2019-09-13 11:46:29 -0700505 auto missingConfigurations = std::make_shared<nlohmann::json>();
506 *missingConfigurations = systemConfiguration;
507
James Feist8f2710a2018-05-09 17:18:55 -0700508 std::list<nlohmann::json> configurations;
Christopher Meisbdaa6b22025-04-02 10:49:02 +0200509 if (!configuration::loadConfigurations(configurations))
James Feist8f2710a2018-05-09 17:18:55 -0700510 {
Andrew Jefferyf3311792022-03-29 22:09:00 +1030511 std::cerr << "Could not load configurations\n";
James Feist2539ccd2020-05-01 16:15:08 -0700512 inProgress = false;
James Feist8f2710a2018-05-09 17:18:55 -0700513 return;
514 }
515
Christopher Meis26fbbd52025-03-26 14:55:06 +0100516 auto perfScan = std::make_shared<scan::PerformScan>(
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200517 *this, *missingConfigurations, configurations,
518 [this, count, oldConfiguration, missingConfigurations]() {
Patrick Williamsb7077432024-08-16 15:22:21 -0400519 // this is something that since ac has been applied to the bmc
520 // we saw, and we no longer see it
Christopher Meis59ef1e72025-04-16 08:53:25 +0200521 bool powerOff = !em_utils::isPowerOn();
Patrick Williamsb7077432024-08-16 15:22:21 -0400522 for (const auto& [name, device] :
523 missingConfigurations->items())
524 {
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200525 pruneConfiguration(powerOff, name, device);
Patrick Williamsb7077432024-08-16 15:22:21 -0400526 }
James Feist899e17f2019-09-13 11:46:29 -0700527
Patrick Williamsb7077432024-08-16 15:22:21 -0400528 nlohmann::json newConfiguration = systemConfiguration;
Andrew Jeffery7c4cbbe2022-03-24 15:27:10 +1030529
Christopher Meisbdaa6b22025-04-02 10:49:02 +0200530 configuration::deriveNewConfiguration(oldConfiguration,
531 newConfiguration);
Andrew Jeffery7c4cbbe2022-03-24 15:27:10 +1030532
Patrick Williamsb7077432024-08-16 15:22:21 -0400533 for (const auto& [_, device] : newConfiguration.items())
534 {
535 logDeviceAdded(device);
536 }
James Feist899e17f2019-09-13 11:46:29 -0700537
Patrick Williamsb7077432024-08-16 15:22:21 -0400538 inProgress = false;
James Feist2539ccd2020-05-01 16:15:08 -0700539
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200540 boost::asio::post(io, [this, newConfiguration, count] {
541 publishNewConfiguration(std::ref(instance), count,
542 std::ref(timer), newConfiguration);
543 });
Patrick Williamsb7077432024-08-16 15:22:21 -0400544 });
James Feist8f2710a2018-05-09 17:18:55 -0700545 perfScan->run();
546 });
James Feist75fdeeb2018-02-20 14:26:16 -0800547}
548
Matt Spinler8d1ac3a2023-02-02 09:48:04 -0600549// Check if InterfacesAdded payload contains an iface that needs probing.
Patrick Williamsb7077432024-08-16 15:22:21 -0400550static bool iaContainsProbeInterface(
551 sdbusplus::message_t& msg, const std::set<std::string>& probeInterfaces)
Matt Spinler8d1ac3a2023-02-02 09:48:04 -0600552{
553 sdbusplus::message::object_path path;
554 DBusObject interfaces;
555 std::set<std::string> interfaceSet;
556 std::set<std::string> intersect;
557
558 msg.read(path, interfaces);
559
560 std::for_each(interfaces.begin(), interfaces.end(),
561 [&interfaceSet](const auto& iface) {
Patrick Williamsb7077432024-08-16 15:22:21 -0400562 interfaceSet.insert(iface.first);
563 });
Matt Spinler8d1ac3a2023-02-02 09:48:04 -0600564
565 std::set_intersection(interfaceSet.begin(), interfaceSet.end(),
566 probeInterfaces.begin(), probeInterfaces.end(),
567 std::inserter(intersect, intersect.end()));
568 return !intersect.empty();
569}
570
571// Check if InterfacesRemoved payload contains an iface that needs probing.
Patrick Williamsb7077432024-08-16 15:22:21 -0400572static bool irContainsProbeInterface(
573 sdbusplus::message_t& msg, const std::set<std::string>& probeInterfaces)
Matt Spinler8d1ac3a2023-02-02 09:48:04 -0600574{
575 sdbusplus::message::object_path path;
576 std::set<std::string> interfaces;
577 std::set<std::string> intersect;
578
579 msg.read(path, interfaces);
580
581 std::set_intersection(interfaces.begin(), interfaces.end(),
582 probeInterfaces.begin(), probeInterfaces.end(),
583 std::inserter(intersect, intersect.end()));
584 return !intersect.empty();
585}
586
Alexander Hansenad28e402025-06-25 12:38:20 +0200587void EntityManager::handleCurrentConfigurationJson()
588{
589 if (em_utils::fwVersionIsSame())
590 {
591 if (std::filesystem::is_regular_file(
592 configuration::currentConfiguration))
593 {
594 // this file could just be deleted, but it's nice for debug
595 std::filesystem::create_directory(tempConfigDir);
596 std::filesystem::remove(lastConfiguration);
597 std::filesystem::copy(configuration::currentConfiguration,
598 lastConfiguration);
599 std::filesystem::remove(configuration::currentConfiguration);
600
601 std::ifstream jsonStream(lastConfiguration);
602 if (jsonStream.good())
603 {
604 auto data = nlohmann::json::parse(jsonStream, nullptr, false);
605 if (data.is_discarded())
606 {
607 std::cerr
608 << "syntax error in " << lastConfiguration << "\n";
609 }
610 else
611 {
612 lastJson = std::move(data);
613 }
614 }
615 else
616 {
617 std::cerr << "unable to open " << lastConfiguration << "\n";
618 }
619 }
620 }
621 else
622 {
623 // not an error, just logging at this level to make it in the journal
624 std::cerr << "Clearing previous configuration\n";
625 std::filesystem::remove(configuration::currentConfiguration);
626 }
627}
628
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200629void EntityManager::registerCallback(const std::string& path)
James Feist75fdeeb2018-02-20 14:26:16 -0800630{
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200631 static boost::container::flat_map<std::string, sdbusplus::bus::match_t>
632 dbusMatches;
James Feist4131aea2018-03-09 09:47:30 -0800633
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200634 if (dbusMatches.contains(path))
635 {
636 return;
637 }
Nan Zhoua3315672022-09-20 19:48:14 +0000638
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200639 std::function<void(sdbusplus::message_t & message)> eventHandler =
640 [&](sdbusplus::message_t&) { propertiesChangedCallback(); };
James Feistfd1264a2018-05-03 12:10:00 -0700641
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200642 sdbusplus::bus::match_t match(
643 static_cast<sdbusplus::bus_t&>(*systemBus),
644 "type='signal',member='PropertiesChanged',path='" + path + "'",
645 eventHandler);
646 dbusMatches.emplace(path, std::move(match));
647}
James Feistfd1264a2018-05-03 12:10:00 -0700648
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200649// We need a poke from DBus for static providers that create all their
650// objects prior to claiming a well-known name, and thus don't emit any
651// org.freedesktop.DBus.Properties signals. Similarly if a process exits
652// for any reason, expected or otherwise, we'll need a poke to remove
653// entities from DBus.
654void EntityManager::initFilters(const std::set<std::string>& probeInterfaces)
655{
656 static sdbusplus::bus::match_t nameOwnerChangedMatch(
Patrick Williams2af39222022-07-22 19:26:56 -0500657 static_cast<sdbusplus::bus_t&>(*systemBus),
Brad Bishopc76af0f2020-12-04 13:50:23 -0500658 sdbusplus::bus::match::rules::nameOwnerChanged(),
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200659 [this](sdbusplus::message_t& m) {
Patrick Williamsb7077432024-08-16 15:22:21 -0400660 auto [name, oldOwner,
661 newOwner] = m.unpack<std::string, std::string, std::string>();
Patrick Williams7b8786f2022-10-10 10:23:37 -0500662
Patrick Williamsb7077432024-08-16 15:22:21 -0400663 if (name.starts_with(':'))
664 {
665 // We should do nothing with unique-name connections.
666 return;
667 }
Patrick Williams7b8786f2022-10-10 10:23:37 -0500668
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200669 propertiesChangedCallback();
Patrick Williamsb7077432024-08-16 15:22:21 -0400670 });
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200671
Brad Bishop10a8c5f2020-12-07 21:40:07 -0500672 // We also need a poke from DBus when new interfaces are created or
673 // destroyed.
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200674 static sdbusplus::bus::match_t interfacesAddedMatch(
Patrick Williams2af39222022-07-22 19:26:56 -0500675 static_cast<sdbusplus::bus_t&>(*systemBus),
Brad Bishop10a8c5f2020-12-07 21:40:07 -0500676 sdbusplus::bus::match::rules::interfacesAdded(),
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200677 [this, probeInterfaces](sdbusplus::message_t& msg) {
Patrick Williamsb7077432024-08-16 15:22:21 -0400678 if (iaContainsProbeInterface(msg, probeInterfaces))
679 {
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200680 propertiesChangedCallback();
Patrick Williamsb7077432024-08-16 15:22:21 -0400681 }
682 });
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200683
684 static sdbusplus::bus::match_t interfacesRemovedMatch(
Patrick Williams2af39222022-07-22 19:26:56 -0500685 static_cast<sdbusplus::bus_t&>(*systemBus),
Brad Bishop10a8c5f2020-12-07 21:40:07 -0500686 sdbusplus::bus::match::rules::interfacesRemoved(),
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200687 [this, probeInterfaces](sdbusplus::message_t& msg) {
Patrick Williamsb7077432024-08-16 15:22:21 -0400688 if (irContainsProbeInterface(msg, probeInterfaces))
689 {
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200690 propertiesChangedCallback();
Patrick Williamsb7077432024-08-16 15:22:21 -0400691 }
692 });
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200693}
Brad Bishopc76af0f2020-12-04 13:50:23 -0500694
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200695int main()
696{
697 auto systemBus = std::make_shared<sdbusplus::asio::connection>(io);
698 systemBus->request_name("xyz.openbmc_project.EntityManager");
699 EntityManager em(systemBus);
James Feist4131aea2018-03-09 09:47:30 -0800700
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200701 nlohmann::json systemConfiguration = nlohmann::json::object();
702
703 std::set<std::string> probeInterfaces = configuration::getProbeInterfaces();
704
705 em.initFilters(probeInterfaces);
706
707 boost::asio::post(io, [&]() { em.propertiesChangedCallback(); });
James Feist8f2710a2018-05-09 17:18:55 -0700708
Alexander Hansenad28e402025-06-25 12:38:20 +0200709 em.handleCurrentConfigurationJson();
James Feist1df06a42019-04-11 14:23:04 -0700710
711 // some boards only show up after power is on, we want to not say they are
712 // removed until the same state happens
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200713 em_utils::setupPowerMatch(em.systemBus);
James Feist1df06a42019-04-11 14:23:04 -0700714
James Feist1b2e2242018-01-30 13:45:19 -0800715 io.run();
James Feist3cb5fec2018-01-23 14:41:51 -0800716
717 return 0;
James Feist75fdeeb2018-02-20 14:26:16 -0800718}