blob: d11484158c4c3fcb187186be5f01185c45598174 [file] [log] [blame]
Alexander Hansen4e1142d2025-07-25 17:07:27 +02001// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright 2018 Intel Corporation
James Feist3cb5fec2018-01-23 14:41:51 -08003
Brad Bishope45d8c72022-05-25 15:12:53 -04004#include "entity_manager.hpp"
James Feist1df06a42019-04-11 14:23:04 -07005
Christopher Meisfc9e7fd2025-04-03 13:13:35 +02006#include "../utils.hpp"
7#include "../variant_visitors.hpp"
Christopher Meisbdaa6b22025-04-02 10:49:02 +02008#include "configuration.hpp"
Christopher Meis12bea9b2025-04-03 10:14:42 +02009#include "dbus_interface.hpp"
Alexander Hansenf57a2592025-06-27 15:07:07 +020010#include "log_device_inventory.hpp"
Brad Bishope45d8c72022-05-25 15:12:53 -040011#include "overlay.hpp"
Christopher Meis26fbbd52025-03-26 14:55:06 +010012#include "perform_scan.hpp"
Benjamin Fairca2eb042022-09-13 06:40:42 +000013#include "topology.hpp"
Christopher Meis59ef1e72025-04-16 08:53:25 +020014#include "utils.hpp"
James Feist481c5d52019-08-13 14:40:40 -070015
James Feist02d2b932020-02-06 16:28:48 -080016#include <boost/asio/io_context.hpp>
Ed Tanous49a888c2023-03-06 13:44:51 -080017#include <boost/asio/post.hpp>
James Feistb1728ca2020-04-30 15:40:55 -070018#include <boost/asio/steady_timer.hpp>
James Feist3cb5fec2018-01-23 14:41:51 -080019#include <boost/container/flat_set.hpp>
James Feistf5125b02019-06-06 11:27:43 -070020#include <boost/range/iterator_range.hpp>
James Feist8c505da2020-05-28 10:06:33 -070021#include <nlohmann/json.hpp>
Alexander Hansen8feb0452025-09-15 14:29:20 +020022#include <phosphor-logging/lg2.hpp>
James Feist8c505da2020-05-28 10:06:33 -070023#include <sdbusplus/asio/connection.hpp>
24#include <sdbusplus/asio/object_server.hpp>
Alexander Hansenc58af0b2025-09-12 15:47:19 +020025#include <xyz/openbmc_project/Association/Definitions/common.hpp>
26#include <xyz/openbmc_project/Inventory/Item/Bmc/common.hpp>
27#include <xyz/openbmc_project/Inventory/Item/System/common.hpp>
28#include <xyz/openbmc_project/Inventory/Item/common.hpp>
James Feist8c505da2020-05-28 10:06:33 -070029
James Feist637b3ef2019-04-15 16:35:30 -070030#include <filesystem>
Ed Tanousdbf95b22025-10-13 11:38:35 -070031#include <flat_map>
James Feista465ccc2019-02-08 12:51:01 -080032#include <fstream>
Andrew Jefferye35d0ac2022-03-24 15:53:13 +103033#include <functional>
Andrew Jeffery666583b2021-12-01 15:50:12 +103034#include <map>
James Feista465ccc2019-02-08 12:51:01 -080035#include <regex>
James Feist1df06a42019-04-11 14:23:04 -070036constexpr const char* tempConfigDir = "/tmp/configuration/";
37constexpr const char* lastConfiguration = "/tmp/configuration/last.json";
James Feistf1b14142019-04-10 15:22:09 -070038
Adrian Ambrożewiczc789fca2020-05-14 15:50:05 +020039static constexpr std::array<const char*, 6> settableInterfaces = {
40 "FanProfile", "Pid", "Pid.Zone", "Stepwise", "Thresholds", "Polling"};
James Feist3cb5fec2018-01-23 14:41:51 -080041
Ed Tanous07d467b2021-02-23 14:48:37 -080042const std::regex illegalDbusPathRegex("[^A-Za-z0-9_.]");
43const std::regex illegalDbusMemberRegex("[^A-Za-z0-9_]");
James Feist1b2e2242018-01-30 13:45:19 -080044
James Feista465ccc2019-02-08 12:51:01 -080045sdbusplus::asio::PropertyPermission getPermission(const std::string& interface)
James Feistc6248a52018-08-14 10:09:45 -070046{
47 return std::find(settableInterfaces.begin(), settableInterfaces.end(),
48 interface) != settableInterfaces.end()
49 ? sdbusplus::asio::PropertyPermission::readWrite
50 : sdbusplus::asio::PropertyPermission::readOnly;
51}
52
Christopher Meiscf6a75b2025-06-03 07:53:50 +020053EntityManager::EntityManager(
Alexander Hansena555acf2025-06-27 11:59:10 +020054 std::shared_ptr<sdbusplus::asio::connection>& systemBus,
Alexander Hansen8290ca42025-08-04 15:27:22 +020055 boost::asio::io_context& io,
56 const std::vector<std::filesystem::path>& configurationDirectories) :
Christopher Meiscf6a75b2025-06-03 07:53:50 +020057 systemBus(systemBus),
58 objServer(sdbusplus::asio::object_server(systemBus, /*skipManager=*/true)),
Alexander Hansen8290ca42025-08-04 15:27:22 +020059 configuration(configurationDirectories), lastJson(nlohmann::json::object()),
Alexander Hansenb1340da2025-06-27 14:29:13 +020060 systemConfiguration(nlohmann::json::object()), io(io),
Alexander Hansen89737252025-08-04 15:15:13 +020061 dbus_interface(io, objServer), powerStatus(*systemBus),
62 propertiesChangedTimer(io)
Christopher Meiscf6a75b2025-06-03 07:53:50 +020063{
64 // All other objects that EntityManager currently support are under the
65 // inventory subtree.
66 // See the discussion at
67 // https://discord.com/channels/775381525260664832/1018929092009144380
68 objServer.add_manager("/xyz/openbmc_project/inventory");
James Feist75fdeeb2018-02-20 14:26:16 -080069
Christopher Meiscf6a75b2025-06-03 07:53:50 +020070 entityIface = objServer.add_interface("/xyz/openbmc_project/EntityManager",
71 "xyz.openbmc_project.EntityManager");
72 entityIface->register_method("ReScan", [this]() {
73 propertiesChangedCallback();
74 });
75 dbus_interface::tryIfaceInitialize(entityIface);
Christopher Meisf7252572025-06-11 13:22:05 +020076
77 initFilters(configuration.probeInterfaces);
Christopher Meiscf6a75b2025-06-03 07:53:50 +020078}
79
80void EntityManager::postToDbus(const nlohmann::json& newConfiguration)
James Feist1b2e2242018-01-30 13:45:19 -080081{
Matt Spinler6eb60972023-08-14 16:36:20 -050082 std::map<std::string, std::string> newBoards; // path -> name
Benjamin Fairca2eb042022-09-13 06:40:42 +000083
James Feist97a63f12018-05-17 13:50:57 -070084 // iterate through boards
Patrick Williams2594d362022-09-28 06:46:24 -050085 for (const auto& [boardId, boardConfig] : newConfiguration.items())
James Feist1b2e2242018-01-30 13:45:19 -080086 {
Ed Tanous77192692025-06-24 11:32:12 -070087 const nlohmann::json::object_t* boardConfigPtr =
88 boardConfig.get_ptr<const nlohmann::json::object_t*>();
89 if (boardConfigPtr == nullptr)
90 {
91 lg2::error("boardConfig for {BOARD} was not an object", "BOARD",
92 boardId);
93 continue;
94 }
95 postBoardToDBus(boardId, *boardConfigPtr, newBoards);
James Feist1b2e2242018-01-30 13:45:19 -080096 }
Benjamin Fairca2eb042022-09-13 06:40:42 +000097
Matt Spinler6eb60972023-08-14 16:36:20 -050098 for (const auto& [assocPath, assocPropValue] :
Alexander Hansen32e74182025-08-20 16:16:00 +020099 topology.getAssocs(std::views::keys(newBoards)))
Benjamin Fairca2eb042022-09-13 06:40:42 +0000100 {
Matt Spinler6eb60972023-08-14 16:36:20 -0500101 auto findBoard = newBoards.find(assocPath);
102 if (findBoard == newBoards.end())
103 {
104 continue;
105 }
Benjamin Fairca2eb042022-09-13 06:40:42 +0000106
Alexander Hansen57604ed2025-06-27 13:22:28 +0200107 auto ifacePtr = dbus_interface.createInterface(
Alexander Hansenc58af0b2025-09-12 15:47:19 +0200108 assocPath,
109 sdbusplus::common::xyz::openbmc_project::association::Definitions::
110 interface,
Matt Spinler6eb60972023-08-14 16:36:20 -0500111 findBoard->second);
112
113 ifacePtr->register_property("Associations", assocPropValue);
Christopher Meis12bea9b2025-04-03 10:14:42 +0200114 dbus_interface::tryIfaceInitialize(ifacePtr);
Benjamin Fairca2eb042022-09-13 06:40:42 +0000115 }
James Feist1b2e2242018-01-30 13:45:19 -0800116}
117
Alexander Hansen4fcd9462025-07-30 17:44:44 +0200118void EntityManager::postBoardToDBus(
Ed Tanous77192692025-06-24 11:32:12 -0700119 const std::string& boardId, const nlohmann::json::object_t& boardConfig,
Alexander Hansen4fcd9462025-07-30 17:44:44 +0200120 std::map<std::string, std::string>& newBoards)
121{
Ed Tanous77192692025-06-24 11:32:12 -0700122 auto boardNameIt = boardConfig.find("Name");
123 if (boardNameIt == boardConfig.end())
124 {
125 lg2::error("Unable to find name for {BOARD}", "BOARD", boardId);
126 return;
127 }
128 const std::string* boardNamePtr =
129 boardNameIt->second.get_ptr<const std::string*>();
130 if (boardNamePtr == nullptr)
131 {
132 lg2::error("Name for {BOARD} was not a string", "BOARD", boardId);
133 return;
134 }
135 std::string boardName = *boardNamePtr;
136 std::string boardNameOrig = *boardNamePtr;
Alexander Hansen4fcd9462025-07-30 17:44:44 +0200137 std::string jsonPointerPath = "/" + boardId;
138 // loop through newConfiguration, but use values from system
139 // configuration to be able to modify via dbus later
140 auto boardValues = systemConfiguration[boardId];
141 auto findBoardType = boardValues.find("Type");
142 std::string boardType;
143 if (findBoardType != boardValues.end() &&
144 findBoardType->type() == nlohmann::json::value_t::string)
145 {
146 boardType = findBoardType->get<std::string>();
147 std::regex_replace(boardType.begin(), boardType.begin(),
148 boardType.end(), illegalDbusMemberRegex, "_");
149 }
150 else
151 {
Alexander Hansen8feb0452025-09-15 14:29:20 +0200152 lg2::error("Unable to find type for {BOARD} reverting to Chassis.",
153 "BOARD", boardName);
Alexander Hansen4fcd9462025-07-30 17:44:44 +0200154 boardType = "Chassis";
155 }
Alexander Hansen4fcd9462025-07-30 17:44:44 +0200156
Christopher Meis811160e2025-08-08 08:48:37 +0200157 const std::string boardPath =
158 em_utils::buildInventorySystemPath(boardName, boardType);
Alexander Hansen4fcd9462025-07-30 17:44:44 +0200159
160 std::shared_ptr<sdbusplus::asio::dbus_interface> inventoryIface =
Alexander Hansen89737252025-08-04 15:15:13 +0200161 dbus_interface.createInterface(
Alexander Hansenc58af0b2025-09-12 15:47:19 +0200162 boardPath,
163 sdbusplus::common::xyz::openbmc_project::inventory::Item::interface,
164 boardName);
165
166 const std::string invItemIntf = std::format(
167 "{}.{}",
168 sdbusplus::common::xyz::openbmc_project::inventory::Item::interface,
169 boardType);
Alexander Hansen4fcd9462025-07-30 17:44:44 +0200170
171 std::shared_ptr<sdbusplus::asio::dbus_interface> boardIface =
Alexander Hansenc58af0b2025-09-12 15:47:19 +0200172 dbus_interface.createInterface(boardPath, invItemIntf, boardNameOrig);
Alexander Hansen4fcd9462025-07-30 17:44:44 +0200173
Alexander Hansen89737252025-08-04 15:15:13 +0200174 dbus_interface.createAddObjectMethod(jsonPointerPath, boardPath,
175 systemConfiguration, boardNameOrig);
Alexander Hansen4fcd9462025-07-30 17:44:44 +0200176
Alexander Hansen89737252025-08-04 15:15:13 +0200177 dbus_interface.populateInterfaceFromJson(
178 systemConfiguration, jsonPointerPath, boardIface, boardValues);
Alexander Hansen4fcd9462025-07-30 17:44:44 +0200179 jsonPointerPath += "/";
180 // iterate through board properties
181 for (const auto& [propName, propValue] : boardValues.items())
182 {
183 if (propValue.type() == nlohmann::json::value_t::object)
184 {
185 std::shared_ptr<sdbusplus::asio::dbus_interface> iface =
Alexander Hansen89737252025-08-04 15:15:13 +0200186 dbus_interface.createInterface(boardPath, propName,
Alexander Hansen4fcd9462025-07-30 17:44:44 +0200187 boardNameOrig);
188
Alexander Hansen89737252025-08-04 15:15:13 +0200189 dbus_interface.populateInterfaceFromJson(
190 systemConfiguration, jsonPointerPath + propName, iface,
191 propValue);
Alexander Hansen4fcd9462025-07-30 17:44:44 +0200192 }
193 }
194
Alexander Hansen4fa40122025-07-30 18:26:35 +0200195 nlohmann::json::iterator exposes = boardValues.find("Exposes");
Alexander Hansen4fcd9462025-07-30 17:44:44 +0200196 if (exposes == boardValues.end())
197 {
198 return;
199 }
200 // iterate through exposes
201 jsonPointerPath += "Exposes/";
202
203 // store the board level pointer so we can modify it on the way down
204 std::string jsonPointerPathBoard = jsonPointerPath;
205 size_t exposesIndex = -1;
Alexander Hansen4fa40122025-07-30 18:26:35 +0200206 for (nlohmann::json& item : *exposes)
Alexander Hansen4fcd9462025-07-30 17:44:44 +0200207 {
Alexander Hansen4fa40122025-07-30 18:26:35 +0200208 postExposesRecordsToDBus(item, exposesIndex, boardNameOrig,
209 jsonPointerPath, jsonPointerPathBoard,
210 boardPath, boardType);
211 }
Alexander Hansen4fcd9462025-07-30 17:44:44 +0200212
Alexander Hansen4fa40122025-07-30 18:26:35 +0200213 newBoards.emplace(boardPath, boardNameOrig);
214}
215
216void EntityManager::postExposesRecordsToDBus(
217 nlohmann::json& item, size_t& exposesIndex,
218 const std::string& boardNameOrig, std::string jsonPointerPath,
219 const std::string& jsonPointerPathBoard, const std::string& boardPath,
220 const std::string& boardType)
221{
222 exposesIndex++;
223 jsonPointerPath = jsonPointerPathBoard;
224 jsonPointerPath += std::to_string(exposesIndex);
225
226 auto findName = item.find("Name");
227 if (findName == item.end())
228 {
Alexander Hansen8feb0452025-09-15 14:29:20 +0200229 lg2::error("cannot find name in field {ITEM}", "ITEM", item);
Alexander Hansen4fa40122025-07-30 18:26:35 +0200230 return;
231 }
232 auto findStatus = item.find("Status");
233 // if status is not found it is assumed to be status = 'okay'
234 if (findStatus != item.end())
235 {
236 if (*findStatus == "disabled")
Alexander Hansen4fcd9462025-07-30 17:44:44 +0200237 {
Alexander Hansen4fa40122025-07-30 18:26:35 +0200238 return;
Alexander Hansen4fcd9462025-07-30 17:44:44 +0200239 }
Alexander Hansen4fa40122025-07-30 18:26:35 +0200240 }
241 auto findType = item.find("Type");
242 std::string itemType;
243 if (findType != item.end())
244 {
245 itemType = findType->get<std::string>();
246 std::regex_replace(itemType.begin(), itemType.begin(), itemType.end(),
247 illegalDbusPathRegex, "_");
248 }
249 else
250 {
251 itemType = "unknown";
252 }
253 std::string itemName = findName->get<std::string>();
254 std::regex_replace(itemName.begin(), itemName.begin(), itemName.end(),
255 illegalDbusMemberRegex, "_");
256 std::string ifacePath = boardPath;
257 ifacePath += "/";
258 ifacePath += itemName;
259
260 if (itemType == "BMC")
261 {
262 std::shared_ptr<sdbusplus::asio::dbus_interface> bmcIface =
263 dbus_interface.createInterface(
Alexander Hansenc58af0b2025-09-12 15:47:19 +0200264 ifacePath,
265 sdbusplus::common::xyz::openbmc_project::inventory::item::Bmc::
266 interface,
Alexander Hansen4fa40122025-07-30 18:26:35 +0200267 boardNameOrig);
Alexander Hansen89737252025-08-04 15:15:13 +0200268 dbus_interface.populateInterfaceFromJson(
269 systemConfiguration, jsonPointerPath, bmcIface, item,
Alexander Hansen4fa40122025-07-30 18:26:35 +0200270 getPermission(itemType));
271 }
272 else if (itemType == "System")
273 {
274 std::shared_ptr<sdbusplus::asio::dbus_interface> systemIface =
275 dbus_interface.createInterface(
Alexander Hansenc58af0b2025-09-12 15:47:19 +0200276 ifacePath,
277 sdbusplus::common::xyz::openbmc_project::inventory::item::
278 System::interface,
Alexander Hansen89737252025-08-04 15:15:13 +0200279 boardNameOrig);
280 dbus_interface.populateInterfaceFromJson(
281 systemConfiguration, jsonPointerPath, systemIface, item,
282 getPermission(itemType));
Alexander Hansen4fa40122025-07-30 18:26:35 +0200283 }
284
285 for (const auto& [name, config] : item.items())
286 {
287 jsonPointerPath = jsonPointerPathBoard;
288 jsonPointerPath.append(std::to_string(exposesIndex))
289 .append("/")
290 .append(name);
Alexander Hansen5aa65d82025-07-31 14:08:42 +0200291
292 if (!postConfigurationRecord(name, config, boardNameOrig, itemType,
293 jsonPointerPath, ifacePath))
Alexander Hansen4fcd9462025-07-30 17:44:44 +0200294 {
Alexander Hansen5aa65d82025-07-31 14:08:42 +0200295 break;
Alexander Hansen4fcd9462025-07-30 17:44:44 +0200296 }
Alexander Hansen4fcd9462025-07-30 17:44:44 +0200297 }
298
Alexander Hansen4fa40122025-07-30 18:26:35 +0200299 std::shared_ptr<sdbusplus::asio::dbus_interface> itemIface =
300 dbus_interface.createInterface(
Alexander Hansen89737252025-08-04 15:15:13 +0200301 ifacePath, "xyz.openbmc_project.Configuration." + itemType,
302 boardNameOrig);
Alexander Hansen4fa40122025-07-30 18:26:35 +0200303
Alexander Hansen89737252025-08-04 15:15:13 +0200304 dbus_interface.populateInterfaceFromJson(
305 systemConfiguration, jsonPointerPath, itemIface, item,
Alexander Hansen4fa40122025-07-30 18:26:35 +0200306 getPermission(itemType));
307
308 topology.addBoard(boardPath, boardType, boardNameOrig, item);
Alexander Hansen4fcd9462025-07-30 17:44:44 +0200309}
310
Alexander Hansen5aa65d82025-07-31 14:08:42 +0200311bool EntityManager::postConfigurationRecord(
312 const std::string& name, nlohmann::json& config,
313 const std::string& boardNameOrig, const std::string& itemType,
314 const std::string& jsonPointerPath, const std::string& ifacePath)
315{
316 if (config.type() == nlohmann::json::value_t::object)
317 {
318 std::string ifaceName = "xyz.openbmc_project.Configuration.";
319 ifaceName.append(itemType).append(".").append(name);
320
321 std::shared_ptr<sdbusplus::asio::dbus_interface> objectIface =
Alexander Hansen89737252025-08-04 15:15:13 +0200322 dbus_interface.createInterface(ifacePath, ifaceName, boardNameOrig);
Alexander Hansen5aa65d82025-07-31 14:08:42 +0200323
Alexander Hansen89737252025-08-04 15:15:13 +0200324 dbus_interface.populateInterfaceFromJson(
325 systemConfiguration, jsonPointerPath, objectIface, config,
326 getPermission(name));
Alexander Hansen5aa65d82025-07-31 14:08:42 +0200327 }
328 else if (config.type() == nlohmann::json::value_t::array)
329 {
330 size_t index = 0;
331 if (config.empty())
332 {
333 return true;
334 }
335 bool isLegal = true;
336 auto type = config[0].type();
337 if (type != nlohmann::json::value_t::object)
338 {
339 return true;
340 }
341
342 // verify legal json
343 for (const auto& arrayItem : config)
344 {
345 if (arrayItem.type() != type)
346 {
347 isLegal = false;
348 break;
349 }
350 }
351 if (!isLegal)
352 {
Alexander Hansen8feb0452025-09-15 14:29:20 +0200353 lg2::error("dbus format error {JSON}", "JSON", config);
Alexander Hansen5aa65d82025-07-31 14:08:42 +0200354 return false;
355 }
356
357 for (auto& arrayItem : config)
358 {
359 std::string ifaceName = "xyz.openbmc_project.Configuration.";
360 ifaceName.append(itemType).append(".").append(name);
361 ifaceName.append(std::to_string(index));
362
363 std::shared_ptr<sdbusplus::asio::dbus_interface> objectIface =
Alexander Hansen89737252025-08-04 15:15:13 +0200364 dbus_interface.createInterface(ifacePath, ifaceName,
Alexander Hansen5aa65d82025-07-31 14:08:42 +0200365 boardNameOrig);
366
Alexander Hansen89737252025-08-04 15:15:13 +0200367 dbus_interface.populateInterfaceFromJson(
368 systemConfiguration,
Alexander Hansen5aa65d82025-07-31 14:08:42 +0200369 jsonPointerPath + "/" + std::to_string(index), objectIface,
Alexander Hansen89737252025-08-04 15:15:13 +0200370 arrayItem, getPermission(name));
Alexander Hansen5aa65d82025-07-31 14:08:42 +0200371 index++;
372 }
373 }
374
375 return true;
376}
377
Andrew Jeffery55192932022-03-24 12:29:27 +1030378static bool deviceRequiresPowerOn(const nlohmann::json& entity)
379{
380 auto powerState = entity.find("PowerState");
Andrew Jefferyb6209442022-03-24 12:36:20 +1030381 if (powerState == entity.end())
Andrew Jeffery55192932022-03-24 12:29:27 +1030382 {
Andrew Jefferyb6209442022-03-24 12:36:20 +1030383 return false;
Andrew Jeffery55192932022-03-24 12:29:27 +1030384 }
385
Ed Tanous3013fb42022-07-09 08:27:06 -0700386 const auto* ptr = powerState->get_ptr<const std::string*>();
387 if (ptr == nullptr)
Andrew Jefferyb6209442022-03-24 12:36:20 +1030388 {
389 return false;
390 }
391
392 return *ptr == "On" || *ptr == "BiosPost";
Andrew Jeffery55192932022-03-24 12:29:27 +1030393}
394
Andrew Jeffery89ec3522022-03-24 13:30:41 +1030395static void pruneDevice(const nlohmann::json& systemConfiguration,
396 const bool powerOff, const bool scannedPowerOff,
397 const std::string& name, const nlohmann::json& device)
398{
399 if (systemConfiguration.contains(name))
400 {
401 return;
402 }
403
Andrew Jeffery4db38bc2022-03-24 13:42:41 +1030404 if (deviceRequiresPowerOn(device) && (powerOff || scannedPowerOff))
Andrew Jeffery89ec3522022-03-24 13:30:41 +1030405 {
Andrew Jeffery89ec3522022-03-24 13:30:41 +1030406 return;
407 }
408
409 logDeviceRemoved(device);
410}
411
Alexander Hansen95ab18f2025-06-27 13:58:10 +0200412void EntityManager::startRemovedTimer(boost::asio::steady_timer& timer,
413 nlohmann::json& systemConfiguration)
James Feist1df06a42019-04-11 14:23:04 -0700414{
James Feistfb00f392019-06-25 14:16:48 -0700415 if (systemConfiguration.empty() || lastJson.empty())
416 {
417 return; // not ready yet
418 }
James Feist1df06a42019-04-11 14:23:04 -0700419 if (scannedPowerOn)
420 {
421 return;
422 }
423
Alexander Hansen0ab32b32025-06-27 14:50:33 +0200424 if (!powerStatus.isPowerOn() && scannedPowerOff)
James Feist1df06a42019-04-11 14:23:04 -0700425 {
426 return;
427 }
428
James Feistb1728ca2020-04-30 15:40:55 -0700429 timer.expires_after(std::chrono::seconds(10));
Andrew Jeffery27a1cfb2022-03-24 12:31:53 +1030430 timer.async_wait(
Alexander Hansen95ab18f2025-06-27 13:58:10 +0200431 [&systemConfiguration, this](const boost::system::error_code& ec) {
Patrick Williamsb7077432024-08-16 15:22:21 -0400432 if (ec == boost::asio::error::operation_aborted)
433 {
434 return;
435 }
Andrew Jeffery27a1cfb2022-03-24 12:31:53 +1030436
Alexander Hansen0ab32b32025-06-27 14:50:33 +0200437 bool powerOff = !powerStatus.isPowerOn();
Patrick Williamsb7077432024-08-16 15:22:21 -0400438 for (const auto& [name, device] : lastJson.items())
439 {
440 pruneDevice(systemConfiguration, powerOff, scannedPowerOff,
441 name, device);
442 }
Andrew Jeffery89ec3522022-03-24 13:30:41 +1030443
Patrick Williamsb7077432024-08-16 15:22:21 -0400444 scannedPowerOff = true;
445 if (!powerOff)
446 {
447 scannedPowerOn = true;
448 }
449 });
James Feist1df06a42019-04-11 14:23:04 -0700450}
451
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200452void EntityManager::pruneConfiguration(bool powerOff, const std::string& name,
453 const nlohmann::json& device)
Andrew Jeffery0d0c1462022-03-24 15:26:39 +1030454{
455 if (powerOff && deviceRequiresPowerOn(device))
456 {
457 // power not on yet, don't know if it's there or not
458 return;
459 }
460
Alexander Hansen57604ed2025-06-27 13:22:28 +0200461 auto& ifaces = dbus_interface.getDeviceInterfaces(device);
Andrew Jeffery0d0c1462022-03-24 15:26:39 +1030462 for (auto& iface : ifaces)
463 {
464 auto sharedPtr = iface.lock();
465 if (!!sharedPtr)
466 {
467 objServer.remove_interface(sharedPtr);
468 }
469 }
470
471 ifaces.clear();
472 systemConfiguration.erase(name);
Matt Spinler6eb60972023-08-14 16:36:20 -0500473 topology.remove(device["Name"].get<std::string>());
Andrew Jeffery0d0c1462022-03-24 15:26:39 +1030474 logDeviceRemoved(device);
475}
476
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200477void EntityManager::publishNewConfiguration(
Andrew Jefferye35d0ac2022-03-24 15:53:13 +1030478 const size_t& instance, const size_t count,
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200479 boost::asio::steady_timer& timer, // Gerrit discussion:
Andrew Jefferye35d0ac2022-03-24 15:53:13 +1030480 // https://gerrit.openbmc-project.xyz/c/openbmc/entity-manager/+/52316/6
481 //
482 // Discord discussion:
483 // https://discord.com/channels/775381525260664832/867820390406422538/958048437729910854
484 //
485 // NOLINTNEXTLINE(performance-unnecessary-value-param)
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200486 const nlohmann::json newConfiguration)
Andrew Jefferye35d0ac2022-03-24 15:53:13 +1030487{
Alexander Hansena555acf2025-06-27 11:59:10 +0200488 loadOverlays(newConfiguration, io);
Andrew Jefferye35d0ac2022-03-24 15:53:13 +1030489
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200490 boost::asio::post(io, [this]() {
Christopher Meisf7252572025-06-11 13:22:05 +0200491 if (!writeJsonFiles(systemConfiguration))
Andrew Jefferye35d0ac2022-03-24 15:53:13 +1030492 {
Alexander Hansen8feb0452025-09-15 14:29:20 +0200493 lg2::error("Error writing json files");
Andrew Jefferye35d0ac2022-03-24 15:53:13 +1030494 }
495 });
496
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200497 boost::asio::post(io, [this, &instance, count, &timer, newConfiguration]() {
498 postToDbus(newConfiguration);
Andrew Jefferye35d0ac2022-03-24 15:53:13 +1030499 if (count == instance)
500 {
Alexander Hansen95ab18f2025-06-27 13:58:10 +0200501 startRemovedTimer(timer, systemConfiguration);
Andrew Jefferye35d0ac2022-03-24 15:53:13 +1030502 }
503 });
504}
505
James Feist8f2710a2018-05-09 17:18:55 -0700506// main properties changed entry
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200507void EntityManager::propertiesChangedCallback()
James Feist8f2710a2018-05-09 17:18:55 -0700508{
Alexander Hansenfc1b1e22025-06-27 14:34:11 +0200509 propertiesChangedInstance++;
510 size_t count = propertiesChangedInstance;
James Feist1df06a42019-04-11 14:23:04 -0700511
Alexander Hansenb1340da2025-06-27 14:29:13 +0200512 propertiesChangedTimer.expires_after(std::chrono::milliseconds(500));
James Feist8f2710a2018-05-09 17:18:55 -0700513
514 // setup an async wait as we normally get flooded with new requests
Alexander Hansenb1340da2025-06-27 14:29:13 +0200515 propertiesChangedTimer.async_wait(
516 [this, count](const boost::system::error_code& ec) {
517 if (ec == boost::asio::error::operation_aborted)
518 {
519 // we were cancelled
520 return;
521 }
522 if (ec)
523 {
Alexander Hansen8feb0452025-09-15 14:29:20 +0200524 lg2::error("async wait error {ERR}", "ERR", ec.message());
Alexander Hansenb1340da2025-06-27 14:29:13 +0200525 return;
526 }
James Feist8f2710a2018-05-09 17:18:55 -0700527
Alexander Hansenb1340da2025-06-27 14:29:13 +0200528 if (propertiesChangedInProgress)
529 {
530 propertiesChangedCallback();
531 return;
532 }
533 propertiesChangedInProgress = true;
James Feist2539ccd2020-05-01 16:15:08 -0700534
Alexander Hansenb1340da2025-06-27 14:29:13 +0200535 nlohmann::json oldConfiguration = systemConfiguration;
536 auto missingConfigurations = std::make_shared<nlohmann::json>();
537 *missingConfigurations = systemConfiguration;
James Feist899e17f2019-09-13 11:46:29 -0700538
Alexander Hansenb1340da2025-06-27 14:29:13 +0200539 auto perfScan = std::make_shared<scan::PerformScan>(
Christopher Meisf7252572025-06-11 13:22:05 +0200540 *this, *missingConfigurations, configuration.configurations, io,
Alexander Hansenb1340da2025-06-27 14:29:13 +0200541 [this, count, oldConfiguration, missingConfigurations]() {
542 // this is something that since ac has been applied to the
543 // bmc we saw, and we no longer see it
Alexander Hansen0ab32b32025-06-27 14:50:33 +0200544 bool powerOff = !powerStatus.isPowerOn();
Alexander Hansenb1340da2025-06-27 14:29:13 +0200545 for (const auto& [name, device] :
546 missingConfigurations->items())
547 {
548 pruneConfiguration(powerOff, name, device);
549 }
Alexander Hansenb1340da2025-06-27 14:29:13 +0200550 nlohmann::json newConfiguration = systemConfiguration;
551
Christopher Meisf7252572025-06-11 13:22:05 +0200552 deriveNewConfiguration(oldConfiguration, newConfiguration);
Alexander Hansenb1340da2025-06-27 14:29:13 +0200553
554 for (const auto& [_, device] : newConfiguration.items())
555 {
556 logDeviceAdded(device);
557 }
558
559 propertiesChangedInProgress = false;
560
561 boost::asio::post(io, [this, newConfiguration, count] {
562 publishNewConfiguration(
Alexander Hansenfc1b1e22025-06-27 14:34:11 +0200563 std::ref(propertiesChangedInstance), count,
Alexander Hansenb1340da2025-06-27 14:29:13 +0200564 std::ref(propertiesChangedTimer), newConfiguration);
565 });
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200566 });
Alexander Hansenb1340da2025-06-27 14:29:13 +0200567 perfScan->run();
568 });
James Feist75fdeeb2018-02-20 14:26:16 -0800569}
570
Matt Spinler8d1ac3a2023-02-02 09:48:04 -0600571// Check if InterfacesAdded payload contains an iface that needs probing.
Patrick Williamsb7077432024-08-16 15:22:21 -0400572static bool iaContainsProbeInterface(
Christopher Meisf7252572025-06-11 13:22:05 +0200573 sdbusplus::message_t& msg,
574 const std::unordered_set<std::string>& probeInterfaces)
Matt Spinler8d1ac3a2023-02-02 09:48:04 -0600575{
576 sdbusplus::message::object_path path;
577 DBusObject interfaces;
Matt Spinler8d1ac3a2023-02-02 09:48:04 -0600578 msg.read(path, interfaces);
Eldin Lee802cbf22025-08-22 14:44:04 +0800579 return std::ranges::any_of(interfaces | std::views::keys,
580 [&probeInterfaces](const auto& ifaceName) {
581 return probeInterfaces.contains(ifaceName);
582 });
Matt Spinler8d1ac3a2023-02-02 09:48:04 -0600583}
584
585// Check if InterfacesRemoved payload contains an iface that needs probing.
Patrick Williamsb7077432024-08-16 15:22:21 -0400586static bool irContainsProbeInterface(
Christopher Meisf7252572025-06-11 13:22:05 +0200587 sdbusplus::message_t& msg,
588 const std::unordered_set<std::string>& probeInterfaces)
Matt Spinler8d1ac3a2023-02-02 09:48:04 -0600589{
590 sdbusplus::message::object_path path;
Eldin Lee802cbf22025-08-22 14:44:04 +0800591 std::vector<std::string> interfaces;
Matt Spinler8d1ac3a2023-02-02 09:48:04 -0600592 msg.read(path, interfaces);
Eldin Lee802cbf22025-08-22 14:44:04 +0800593 return std::ranges::any_of(interfaces,
594 [&probeInterfaces](const auto& ifaceName) {
595 return probeInterfaces.contains(ifaceName);
596 });
Matt Spinler8d1ac3a2023-02-02 09:48:04 -0600597}
598
Alexander Hansenad28e402025-06-25 12:38:20 +0200599void EntityManager::handleCurrentConfigurationJson()
600{
Alexander Hansene6651852025-01-21 16:22:05 +0100601 if (EM_CACHE_CONFIGURATION && em_utils::fwVersionIsSame())
Alexander Hansenad28e402025-06-25 12:38:20 +0200602 {
Christopher Meisf7252572025-06-11 13:22:05 +0200603 if (std::filesystem::is_regular_file(currentConfiguration))
Alexander Hansenad28e402025-06-25 12:38:20 +0200604 {
605 // this file could just be deleted, but it's nice for debug
606 std::filesystem::create_directory(tempConfigDir);
607 std::filesystem::remove(lastConfiguration);
Christopher Meisf7252572025-06-11 13:22:05 +0200608 std::filesystem::copy(currentConfiguration, lastConfiguration);
609 std::filesystem::remove(currentConfiguration);
Alexander Hansenad28e402025-06-25 12:38:20 +0200610
611 std::ifstream jsonStream(lastConfiguration);
612 if (jsonStream.good())
613 {
614 auto data = nlohmann::json::parse(jsonStream, nullptr, false);
615 if (data.is_discarded())
616 {
Alexander Hansen8feb0452025-09-15 14:29:20 +0200617 lg2::error("syntax error in {PATH}", "PATH",
618 lastConfiguration);
Alexander Hansenad28e402025-06-25 12:38:20 +0200619 }
620 else
621 {
622 lastJson = std::move(data);
623 }
624 }
625 else
626 {
Alexander Hansen8feb0452025-09-15 14:29:20 +0200627 lg2::error("unable to open {PATH}", "PATH", lastConfiguration);
Alexander Hansenad28e402025-06-25 12:38:20 +0200628 }
629 }
630 }
631 else
632 {
633 // not an error, just logging at this level to make it in the journal
Marc Olberdingb2f82822025-09-25 13:56:41 -0700634 std::error_code ec;
Alexander Hansen8feb0452025-09-15 14:29:20 +0200635 lg2::error("Clearing previous configuration");
Marc Olberdingb2f82822025-09-25 13:56:41 -0700636 std::filesystem::remove(currentConfiguration, ec);
Alexander Hansenad28e402025-06-25 12:38:20 +0200637 }
638}
639
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200640void EntityManager::registerCallback(const std::string& path)
James Feist75fdeeb2018-02-20 14:26:16 -0800641{
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200642 if (dbusMatches.contains(path))
643 {
644 return;
645 }
Nan Zhoua3315672022-09-20 19:48:14 +0000646
Alexander Hansen60803182025-06-27 14:41:28 +0200647 lg2::debug("creating PropertiesChanged match on {PATH}", "PATH", path);
648
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200649 std::function<void(sdbusplus::message_t & message)> eventHandler =
650 [&](sdbusplus::message_t&) { propertiesChangedCallback(); };
James Feistfd1264a2018-05-03 12:10:00 -0700651
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200652 sdbusplus::bus::match_t match(
653 static_cast<sdbusplus::bus_t&>(*systemBus),
654 "type='signal',member='PropertiesChanged',path='" + path + "'",
655 eventHandler);
656 dbusMatches.emplace(path, std::move(match));
657}
James Feistfd1264a2018-05-03 12:10:00 -0700658
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200659// We need a poke from DBus for static providers that create all their
660// objects prior to claiming a well-known name, and thus don't emit any
661// org.freedesktop.DBus.Properties signals. Similarly if a process exits
662// for any reason, expected or otherwise, we'll need a poke to remove
663// entities from DBus.
Christopher Meisf7252572025-06-11 13:22:05 +0200664void EntityManager::initFilters(
665 const std::unordered_set<std::string>& probeInterfaces)
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200666{
Alexander Hansen0f7bd782025-06-27 13:39:53 +0200667 nameOwnerChangedMatch = std::make_unique<sdbusplus::bus::match_t>(
Patrick Williams2af39222022-07-22 19:26:56 -0500668 static_cast<sdbusplus::bus_t&>(*systemBus),
Brad Bishopc76af0f2020-12-04 13:50:23 -0500669 sdbusplus::bus::match::rules::nameOwnerChanged(),
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200670 [this](sdbusplus::message_t& m) {
Patrick Williamsb7077432024-08-16 15:22:21 -0400671 auto [name, oldOwner,
672 newOwner] = m.unpack<std::string, std::string, std::string>();
Patrick Williams7b8786f2022-10-10 10:23:37 -0500673
Patrick Williamsb7077432024-08-16 15:22:21 -0400674 if (name.starts_with(':'))
675 {
676 // We should do nothing with unique-name connections.
677 return;
678 }
Patrick Williams7b8786f2022-10-10 10:23:37 -0500679
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200680 propertiesChangedCallback();
Patrick Williamsb7077432024-08-16 15:22:21 -0400681 });
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200682
Brad Bishop10a8c5f2020-12-07 21:40:07 -0500683 // We also need a poke from DBus when new interfaces are created or
684 // destroyed.
Alexander Hansen0f7bd782025-06-27 13:39:53 +0200685 interfacesAddedMatch = std::make_unique<sdbusplus::bus::match_t>(
Patrick Williams2af39222022-07-22 19:26:56 -0500686 static_cast<sdbusplus::bus_t&>(*systemBus),
Brad Bishop10a8c5f2020-12-07 21:40:07 -0500687 sdbusplus::bus::match::rules::interfacesAdded(),
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200688 [this, probeInterfaces](sdbusplus::message_t& msg) {
Patrick Williamsb7077432024-08-16 15:22:21 -0400689 if (iaContainsProbeInterface(msg, probeInterfaces))
690 {
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200691 propertiesChangedCallback();
Patrick Williamsb7077432024-08-16 15:22:21 -0400692 }
693 });
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200694
Alexander Hansen0f7bd782025-06-27 13:39:53 +0200695 interfacesRemovedMatch = std::make_unique<sdbusplus::bus::match_t>(
Patrick Williams2af39222022-07-22 19:26:56 -0500696 static_cast<sdbusplus::bus_t&>(*systemBus),
Brad Bishop10a8c5f2020-12-07 21:40:07 -0500697 sdbusplus::bus::match::rules::interfacesRemoved(),
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200698 [this, probeInterfaces](sdbusplus::message_t& msg) {
Patrick Williamsb7077432024-08-16 15:22:21 -0400699 if (irContainsProbeInterface(msg, probeInterfaces))
700 {
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200701 propertiesChangedCallback();
Patrick Williamsb7077432024-08-16 15:22:21 -0400702 }
703 });
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200704}