blob: 8de134a4463d3f0be69ba03766a9543f606e3ad0 [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 Tanous07d467b2021-02-23 14:48:37 -080056const std::regex illegalDbusPathRegex("[^A-Za-z0-9_.]");
57const std::regex illegalDbusMemberRegex("[^A-Za-z0-9_]");
James Feist1b2e2242018-01-30 13:45:19 -080058
James Feista465ccc2019-02-08 12:51:01 -080059sdbusplus::asio::PropertyPermission getPermission(const std::string& interface)
James Feistc6248a52018-08-14 10:09:45 -070060{
61 return std::find(settableInterfaces.begin(), settableInterfaces.end(),
62 interface) != settableInterfaces.end()
63 ? sdbusplus::asio::PropertyPermission::readWrite
64 : sdbusplus::asio::PropertyPermission::readOnly;
65}
66
Christopher Meiscf6a75b2025-06-03 07:53:50 +020067EntityManager::EntityManager(
Alexander Hansena555acf2025-06-27 11:59:10 +020068 std::shared_ptr<sdbusplus::asio::connection>& systemBus,
69 boost::asio::io_context& io) :
Christopher Meiscf6a75b2025-06-03 07:53:50 +020070 systemBus(systemBus),
71 objServer(sdbusplus::asio::object_server(systemBus, /*skipManager=*/true)),
72 lastJson(nlohmann::json::object()),
Alexander Hansenb1340da2025-06-27 14:29:13 +020073 systemConfiguration(nlohmann::json::object()), io(io),
74 propertiesChangedTimer(io)
Christopher Meiscf6a75b2025-06-03 07:53:50 +020075{
76 // All other objects that EntityManager currently support are under the
77 // inventory subtree.
78 // See the discussion at
79 // https://discord.com/channels/775381525260664832/1018929092009144380
80 objServer.add_manager("/xyz/openbmc_project/inventory");
James Feist75fdeeb2018-02-20 14:26:16 -080081
Christopher Meiscf6a75b2025-06-03 07:53:50 +020082 entityIface = objServer.add_interface("/xyz/openbmc_project/EntityManager",
83 "xyz.openbmc_project.EntityManager");
84 entityIface->register_method("ReScan", [this]() {
85 propertiesChangedCallback();
86 });
87 dbus_interface::tryIfaceInitialize(entityIface);
88}
89
90void EntityManager::postToDbus(const nlohmann::json& newConfiguration)
James Feist1b2e2242018-01-30 13:45:19 -080091{
Matt Spinler6eb60972023-08-14 16:36:20 -050092 std::map<std::string, std::string> newBoards; // path -> name
Benjamin Fairca2eb042022-09-13 06:40:42 +000093
James Feist97a63f12018-05-17 13:50:57 -070094 // iterate through boards
Patrick Williams2594d362022-09-28 06:46:24 -050095 for (const auto& [boardId, boardConfig] : newConfiguration.items())
James Feist1b2e2242018-01-30 13:45:19 -080096 {
Matt Spinler3d1909a2023-08-10 16:39:44 -050097 std::string boardName = boardConfig["Name"];
98 std::string boardNameOrig = boardConfig["Name"];
Andrew Jeffery13132df2022-03-25 13:29:41 +103099 std::string jsonPointerPath = "/" + boardId;
James Feist97a63f12018-05-17 13:50:57 -0700100 // loop through newConfiguration, but use values from system
101 // configuration to be able to modify via dbus later
Andrew Jeffery13132df2022-03-25 13:29:41 +1030102 auto boardValues = systemConfiguration[boardId];
James Feistd63d18a2018-07-19 15:23:45 -0700103 auto findBoardType = boardValues.find("Type");
James Feist1b2e2242018-01-30 13:45:19 -0800104 std::string boardType;
105 if (findBoardType != boardValues.end() &&
106 findBoardType->type() == nlohmann::json::value_t::string)
107 {
108 boardType = findBoardType->get<std::string>();
109 std::regex_replace(boardType.begin(), boardType.begin(),
Ed Tanous07d467b2021-02-23 14:48:37 -0800110 boardType.end(), illegalDbusMemberRegex, "_");
James Feist1b2e2242018-01-30 13:45:19 -0800111 }
112 else
113 {
Matt Spinler3d1909a2023-08-10 16:39:44 -0500114 std::cerr << "Unable to find type for " << boardName
James Feist1b2e2242018-01-30 13:45:19 -0800115 << " reverting to Chassis.\n";
116 boardType = "Chassis";
117 }
James Feist11be6672018-04-06 14:05:32 -0700118 std::string boardtypeLower = boost::algorithm::to_lower_copy(boardType);
James Feist1b2e2242018-01-30 13:45:19 -0800119
Matt Spinler3d1909a2023-08-10 16:39:44 -0500120 std::regex_replace(boardName.begin(), boardName.begin(),
121 boardName.end(), illegalDbusMemberRegex, "_");
122 std::string boardPath = "/xyz/openbmc_project/inventory/system/";
123 boardPath += boardtypeLower;
124 boardPath += "/";
125 boardPath += boardName;
James Feist1b2e2242018-01-30 13:45:19 -0800126
James Feistd58879a82019-09-11 11:26:07 -0700127 std::shared_ptr<sdbusplus::asio::dbus_interface> inventoryIface =
Alexander Hansen57604ed2025-06-27 13:22:28 +0200128 dbus_interface.createInterface(objServer, boardPath,
129 "xyz.openbmc_project.Inventory.Item",
130 boardName);
James Feist68500ff2018-08-08 15:40:42 -0700131
James Feistd58879a82019-09-11 11:26:07 -0700132 std::shared_ptr<sdbusplus::asio::dbus_interface> boardIface =
Alexander Hansen57604ed2025-06-27 13:22:28 +0200133 dbus_interface.createInterface(
Christopher Meis12bea9b2025-04-03 10:14:42 +0200134 objServer, boardPath,
135 "xyz.openbmc_project.Inventory.Item." + boardType,
136 boardNameOrig);
James Feist11be6672018-04-06 14:05:32 -0700137
Alexander Hansen57604ed2025-06-27 13:22:28 +0200138 dbus_interface.createAddObjectMethod(
Alexander Hansena555acf2025-06-27 11:59:10 +0200139 io, jsonPointerPath, boardPath, systemConfiguration, objServer,
Christopher Meis12bea9b2025-04-03 10:14:42 +0200140 boardNameOrig);
James Feist68500ff2018-08-08 15:40:42 -0700141
Christopher Meis12bea9b2025-04-03 10:14:42 +0200142 dbus_interface::populateInterfaceFromJson(
Alexander Hansena555acf2025-06-27 11:59:10 +0200143 io, systemConfiguration, jsonPointerPath, boardIface, boardValues,
Christopher Meis12bea9b2025-04-03 10:14:42 +0200144 objServer);
James Feist97a63f12018-05-17 13:50:57 -0700145 jsonPointerPath += "/";
146 // iterate through board properties
Patrick Williams2594d362022-09-28 06:46:24 -0500147 for (const auto& [propName, propValue] : boardValues.items())
James Feist11be6672018-04-06 14:05:32 -0700148 {
Andrew Jefferya96950d2022-03-25 13:32:46 +1030149 if (propValue.type() == nlohmann::json::value_t::object)
James Feist11be6672018-04-06 14:05:32 -0700150 {
James Feistd58879a82019-09-11 11:26:07 -0700151 std::shared_ptr<sdbusplus::asio::dbus_interface> iface =
Alexander Hansen57604ed2025-06-27 13:22:28 +0200152 dbus_interface.createInterface(objServer, boardPath,
153 propName, boardNameOrig);
James Feistd58879a82019-09-11 11:26:07 -0700154
Christopher Meis12bea9b2025-04-03 10:14:42 +0200155 dbus_interface::populateInterfaceFromJson(
Alexander Hansena555acf2025-06-27 11:59:10 +0200156 io, systemConfiguration, jsonPointerPath + propName, iface,
Christopher Meis12bea9b2025-04-03 10:14:42 +0200157 propValue, objServer);
James Feist11be6672018-04-06 14:05:32 -0700158 }
159 }
James Feist97a63f12018-05-17 13:50:57 -0700160
James Feist1e3e6982018-08-03 16:09:28 -0700161 auto exposes = boardValues.find("Exposes");
James Feist1b2e2242018-01-30 13:45:19 -0800162 if (exposes == boardValues.end())
163 {
164 continue;
165 }
James Feist97a63f12018-05-17 13:50:57 -0700166 // iterate through exposes
James Feist1e3e6982018-08-03 16:09:28 -0700167 jsonPointerPath += "Exposes/";
James Feist97a63f12018-05-17 13:50:57 -0700168
169 // store the board level pointer so we can modify it on the way down
170 std::string jsonPointerPathBoard = jsonPointerPath;
171 size_t exposesIndex = -1;
James Feista465ccc2019-02-08 12:51:01 -0800172 for (auto& item : *exposes)
James Feist1b2e2242018-01-30 13:45:19 -0800173 {
James Feist97a63f12018-05-17 13:50:57 -0700174 exposesIndex++;
175 jsonPointerPath = jsonPointerPathBoard;
176 jsonPointerPath += std::to_string(exposesIndex);
177
James Feistd63d18a2018-07-19 15:23:45 -0700178 auto findName = item.find("Name");
James Feist1b2e2242018-01-30 13:45:19 -0800179 if (findName == item.end())
180 {
181 std::cerr << "cannot find name in field " << item << "\n";
182 continue;
183 }
James Feist1e3e6982018-08-03 16:09:28 -0700184 auto findStatus = item.find("Status");
James Feist1b2e2242018-01-30 13:45:19 -0800185 // if status is not found it is assumed to be status = 'okay'
186 if (findStatus != item.end())
187 {
188 if (*findStatus == "disabled")
189 {
190 continue;
191 }
192 }
James Feistd63d18a2018-07-19 15:23:45 -0700193 auto findType = item.find("Type");
James Feist1b2e2242018-01-30 13:45:19 -0800194 std::string itemType;
195 if (findType != item.end())
196 {
197 itemType = findType->get<std::string>();
198 std::regex_replace(itemType.begin(), itemType.begin(),
Ed Tanous07d467b2021-02-23 14:48:37 -0800199 itemType.end(), illegalDbusPathRegex, "_");
James Feist1b2e2242018-01-30 13:45:19 -0800200 }
201 else
202 {
203 itemType = "unknown";
204 }
205 std::string itemName = findName->get<std::string>();
206 std::regex_replace(itemName.begin(), itemName.begin(),
Ed Tanous07d467b2021-02-23 14:48:37 -0800207 itemName.end(), illegalDbusMemberRegex, "_");
Matt Spinler3d1909a2023-08-10 16:39:44 -0500208 std::string ifacePath = boardPath;
Ed Tanous07d467b2021-02-23 14:48:37 -0800209 ifacePath += "/";
210 ifacePath += itemName;
James Feistc6248a52018-08-14 10:09:45 -0700211
Sui Chen74ebe592022-09-13 10:22:03 -0700212 if (itemType == "BMC")
213 {
214 std::shared_ptr<sdbusplus::asio::dbus_interface> bmcIface =
Alexander Hansen57604ed2025-06-27 13:22:28 +0200215 dbus_interface.createInterface(
Christopher Meis12bea9b2025-04-03 10:14:42 +0200216 objServer, ifacePath,
217 "xyz.openbmc_project.Inventory.Item.Bmc",
218 boardNameOrig);
219 dbus_interface::populateInterfaceFromJson(
Alexander Hansena555acf2025-06-27 11:59:10 +0200220 io, systemConfiguration, jsonPointerPath, bmcIface, item,
Christopher Meis12bea9b2025-04-03 10:14:42 +0200221 objServer, getPermission(itemType));
Sui Chen74ebe592022-09-13 10:22:03 -0700222 }
Edward Leeeb587b42023-03-08 18:59:04 +0000223 else if (itemType == "System")
224 {
225 std::shared_ptr<sdbusplus::asio::dbus_interface> systemIface =
Alexander Hansen57604ed2025-06-27 13:22:28 +0200226 dbus_interface.createInterface(
Christopher Meis12bea9b2025-04-03 10:14:42 +0200227 objServer, ifacePath,
228 "xyz.openbmc_project.Inventory.Item.System",
229 boardNameOrig);
230 dbus_interface::populateInterfaceFromJson(
Alexander Hansena555acf2025-06-27 11:59:10 +0200231 io, systemConfiguration, jsonPointerPath, systemIface, item,
Christopher Meis12bea9b2025-04-03 10:14:42 +0200232 objServer, getPermission(itemType));
Edward Leeeb587b42023-03-08 18:59:04 +0000233 }
Sui Chen74ebe592022-09-13 10:22:03 -0700234
Patrick Williams2594d362022-09-28 06:46:24 -0500235 for (const auto& [name, config] : item.items())
James Feist1b2e2242018-01-30 13:45:19 -0800236 {
Andrew Jeffery5a6379c2022-03-25 13:25:31 +1030237 jsonPointerPath = jsonPointerPathBoard;
238 jsonPointerPath.append(std::to_string(exposesIndex))
239 .append("/")
240 .append(name);
241 if (config.type() == nlohmann::json::value_t::object)
James Feist1b2e2242018-01-30 13:45:19 -0800242 {
Andrew Jeffery5a6379c2022-03-25 13:25:31 +1030243 std::string ifaceName =
244 "xyz.openbmc_project.Configuration.";
245 ifaceName.append(itemType).append(".").append(name);
James Feist97a63f12018-05-17 13:50:57 -0700246
Andrew Jeffery5a6379c2022-03-25 13:25:31 +1030247 std::shared_ptr<sdbusplus::asio::dbus_interface>
Alexander Hansen57604ed2025-06-27 13:22:28 +0200248 objectIface = dbus_interface.createInterface(
Christopher Meis12bea9b2025-04-03 10:14:42 +0200249 objServer, ifacePath, ifaceName, boardNameOrig);
Andrew Jeffery5a6379c2022-03-25 13:25:31 +1030250
Christopher Meis12bea9b2025-04-03 10:14:42 +0200251 dbus_interface::populateInterfaceFromJson(
Alexander Hansena555acf2025-06-27 11:59:10 +0200252 io, systemConfiguration, jsonPointerPath, objectIface,
Andrew Jeffery5a6379c2022-03-25 13:25:31 +1030253 config, objServer, getPermission(name));
James Feist1b2e2242018-01-30 13:45:19 -0800254 }
Andrew Jeffery5a6379c2022-03-25 13:25:31 +1030255 else if (config.type() == nlohmann::json::value_t::array)
James Feist1b2e2242018-01-30 13:45:19 -0800256 {
257 size_t index = 0;
Ed Tanous3013fb42022-07-09 08:27:06 -0700258 if (config.empty())
James Feist1b2e2242018-01-30 13:45:19 -0800259 {
James Feist8f2710a2018-05-09 17:18:55 -0700260 continue;
261 }
262 bool isLegal = true;
Andrew Jeffery5a6379c2022-03-25 13:25:31 +1030263 auto type = config[0].type();
James Feist8f2710a2018-05-09 17:18:55 -0700264 if (type != nlohmann::json::value_t::object)
265 {
266 continue;
267 }
268
269 // verify legal json
Andrew Jeffery5a6379c2022-03-25 13:25:31 +1030270 for (const auto& arrayItem : config)
James Feist8f2710a2018-05-09 17:18:55 -0700271 {
272 if (arrayItem.type() != type)
James Feist1b2e2242018-01-30 13:45:19 -0800273 {
James Feist8f2710a2018-05-09 17:18:55 -0700274 isLegal = false;
James Feist1b2e2242018-01-30 13:45:19 -0800275 break;
276 }
James Feist8f2710a2018-05-09 17:18:55 -0700277 }
278 if (!isLegal)
279 {
Andrew Jeffery5a6379c2022-03-25 13:25:31 +1030280 std::cerr << "dbus format error" << config << "\n";
James Feist8f2710a2018-05-09 17:18:55 -0700281 break;
282 }
283
Andrew Jeffery5a6379c2022-03-25 13:25:31 +1030284 for (auto& arrayItem : config)
James Feist8f2710a2018-05-09 17:18:55 -0700285 {
Andrew Jeffery5a6379c2022-03-25 13:25:31 +1030286 std::string ifaceName =
287 "xyz.openbmc_project.Configuration.";
288 ifaceName.append(itemType).append(".").append(name);
289 ifaceName.append(std::to_string(index));
James Feist97a63f12018-05-17 13:50:57 -0700290
James Feistd58879a82019-09-11 11:26:07 -0700291 std::shared_ptr<sdbusplus::asio::dbus_interface>
Alexander Hansen57604ed2025-06-27 13:22:28 +0200292 objectIface = dbus_interface.createInterface(
Matt Spinler3d1909a2023-08-10 16:39:44 -0500293 objServer, ifacePath, ifaceName, boardNameOrig);
James Feistd58879a82019-09-11 11:26:07 -0700294
Christopher Meis12bea9b2025-04-03 10:14:42 +0200295 dbus_interface::populateInterfaceFromJson(
Alexander Hansena555acf2025-06-27 11:59:10 +0200296 io, systemConfiguration,
James Feistc6248a52018-08-14 10:09:45 -0700297 jsonPointerPath + "/" + std::to_string(index),
298 objectIface, arrayItem, objServer,
Andrew Jeffery5a6379c2022-03-25 13:25:31 +1030299 getPermission(name));
James Feistbb43d022018-06-12 15:44:33 -0700300 index++;
James Feist1b2e2242018-01-30 13:45:19 -0800301 }
302 }
303 }
Benjamin Fairca2eb042022-09-13 06:40:42 +0000304
George Liu5c1a61a2024-10-24 18:02:29 +0800305 std::shared_ptr<sdbusplus::asio::dbus_interface> itemIface =
Alexander Hansen57604ed2025-06-27 13:22:28 +0200306 dbus_interface.createInterface(
Christopher Meis12bea9b2025-04-03 10:14:42 +0200307 objServer, ifacePath,
308 "xyz.openbmc_project.Configuration." + itemType,
309 boardNameOrig);
George Liu5c1a61a2024-10-24 18:02:29 +0800310
Christopher Meis12bea9b2025-04-03 10:14:42 +0200311 dbus_interface::populateInterfaceFromJson(
Alexander Hansena555acf2025-06-27 11:59:10 +0200312 io, systemConfiguration, jsonPointerPath, itemIface, item,
Christopher Meis12bea9b2025-04-03 10:14:42 +0200313 objServer, getPermission(itemType));
George Liu5c1a61a2024-10-24 18:02:29 +0800314
Matt Spinler6eb60972023-08-14 16:36:20 -0500315 topology.addBoard(boardPath, boardType, boardNameOrig, item);
James Feist1b2e2242018-01-30 13:45:19 -0800316 }
Matt Spinler6eb60972023-08-14 16:36:20 -0500317
318 newBoards.emplace(boardPath, boardNameOrig);
James Feist1b2e2242018-01-30 13:45:19 -0800319 }
Benjamin Fairca2eb042022-09-13 06:40:42 +0000320
Matt Spinler6eb60972023-08-14 16:36:20 -0500321 for (const auto& [assocPath, assocPropValue] :
322 topology.getAssocs(newBoards))
Benjamin Fairca2eb042022-09-13 06:40:42 +0000323 {
Matt Spinler6eb60972023-08-14 16:36:20 -0500324 auto findBoard = newBoards.find(assocPath);
325 if (findBoard == newBoards.end())
326 {
327 continue;
328 }
Benjamin Fairca2eb042022-09-13 06:40:42 +0000329
Alexander Hansen57604ed2025-06-27 13:22:28 +0200330 auto ifacePtr = dbus_interface.createInterface(
Matt Spinler6eb60972023-08-14 16:36:20 -0500331 objServer, assocPath, "xyz.openbmc_project.Association.Definitions",
332 findBoard->second);
333
334 ifacePtr->register_property("Associations", assocPropValue);
Christopher Meis12bea9b2025-04-03 10:14:42 +0200335 dbus_interface::tryIfaceInitialize(ifacePtr);
Benjamin Fairca2eb042022-09-13 06:40:42 +0000336 }
James Feist1b2e2242018-01-30 13:45:19 -0800337}
338
Andrew Jeffery55192932022-03-24 12:29:27 +1030339static bool deviceRequiresPowerOn(const nlohmann::json& entity)
340{
341 auto powerState = entity.find("PowerState");
Andrew Jefferyb6209442022-03-24 12:36:20 +1030342 if (powerState == entity.end())
Andrew Jeffery55192932022-03-24 12:29:27 +1030343 {
Andrew Jefferyb6209442022-03-24 12:36:20 +1030344 return false;
Andrew Jeffery55192932022-03-24 12:29:27 +1030345 }
346
Ed Tanous3013fb42022-07-09 08:27:06 -0700347 const auto* ptr = powerState->get_ptr<const std::string*>();
348 if (ptr == nullptr)
Andrew Jefferyb6209442022-03-24 12:36:20 +1030349 {
350 return false;
351 }
352
353 return *ptr == "On" || *ptr == "BiosPost";
Andrew Jeffery55192932022-03-24 12:29:27 +1030354}
355
Andrew Jeffery89ec3522022-03-24 13:30:41 +1030356static void pruneDevice(const nlohmann::json& systemConfiguration,
357 const bool powerOff, const bool scannedPowerOff,
358 const std::string& name, const nlohmann::json& device)
359{
360 if (systemConfiguration.contains(name))
361 {
362 return;
363 }
364
Andrew Jeffery4db38bc2022-03-24 13:42:41 +1030365 if (deviceRequiresPowerOn(device) && (powerOff || scannedPowerOff))
Andrew Jeffery89ec3522022-03-24 13:30:41 +1030366 {
Andrew Jeffery89ec3522022-03-24 13:30:41 +1030367 return;
368 }
369
370 logDeviceRemoved(device);
371}
372
Alexander Hansen95ab18f2025-06-27 13:58:10 +0200373void EntityManager::startRemovedTimer(boost::asio::steady_timer& timer,
374 nlohmann::json& systemConfiguration)
James Feist1df06a42019-04-11 14:23:04 -0700375{
James Feistfb00f392019-06-25 14:16:48 -0700376 if (systemConfiguration.empty() || lastJson.empty())
377 {
378 return; // not ready yet
379 }
James Feist1df06a42019-04-11 14:23:04 -0700380 if (scannedPowerOn)
381 {
382 return;
383 }
384
Christopher Meis59ef1e72025-04-16 08:53:25 +0200385 if (!em_utils::isPowerOn() && scannedPowerOff)
James Feist1df06a42019-04-11 14:23:04 -0700386 {
387 return;
388 }
389
James Feistb1728ca2020-04-30 15:40:55 -0700390 timer.expires_after(std::chrono::seconds(10));
Andrew Jeffery27a1cfb2022-03-24 12:31:53 +1030391 timer.async_wait(
Alexander Hansen95ab18f2025-06-27 13:58:10 +0200392 [&systemConfiguration, this](const boost::system::error_code& ec) {
Patrick Williamsb7077432024-08-16 15:22:21 -0400393 if (ec == boost::asio::error::operation_aborted)
394 {
395 return;
396 }
Andrew Jeffery27a1cfb2022-03-24 12:31:53 +1030397
Christopher Meis59ef1e72025-04-16 08:53:25 +0200398 bool powerOff = !em_utils::isPowerOn();
Patrick Williamsb7077432024-08-16 15:22:21 -0400399 for (const auto& [name, device] : lastJson.items())
400 {
401 pruneDevice(systemConfiguration, powerOff, scannedPowerOff,
402 name, device);
403 }
Andrew Jeffery89ec3522022-03-24 13:30:41 +1030404
Patrick Williamsb7077432024-08-16 15:22:21 -0400405 scannedPowerOff = true;
406 if (!powerOff)
407 {
408 scannedPowerOn = true;
409 }
410 });
James Feist1df06a42019-04-11 14:23:04 -0700411}
412
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200413void EntityManager::pruneConfiguration(bool powerOff, const std::string& name,
414 const nlohmann::json& device)
Andrew Jeffery0d0c1462022-03-24 15:26:39 +1030415{
416 if (powerOff && deviceRequiresPowerOn(device))
417 {
418 // power not on yet, don't know if it's there or not
419 return;
420 }
421
Alexander Hansen57604ed2025-06-27 13:22:28 +0200422 auto& ifaces = dbus_interface.getDeviceInterfaces(device);
Andrew Jeffery0d0c1462022-03-24 15:26:39 +1030423 for (auto& iface : ifaces)
424 {
425 auto sharedPtr = iface.lock();
426 if (!!sharedPtr)
427 {
428 objServer.remove_interface(sharedPtr);
429 }
430 }
431
432 ifaces.clear();
433 systemConfiguration.erase(name);
Matt Spinler6eb60972023-08-14 16:36:20 -0500434 topology.remove(device["Name"].get<std::string>());
Andrew Jeffery0d0c1462022-03-24 15:26:39 +1030435 logDeviceRemoved(device);
436}
437
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200438void EntityManager::publishNewConfiguration(
Andrew Jefferye35d0ac2022-03-24 15:53:13 +1030439 const size_t& instance, const size_t count,
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200440 boost::asio::steady_timer& timer, // Gerrit discussion:
Andrew Jefferye35d0ac2022-03-24 15:53:13 +1030441 // https://gerrit.openbmc-project.xyz/c/openbmc/entity-manager/+/52316/6
442 //
443 // Discord discussion:
444 // https://discord.com/channels/775381525260664832/867820390406422538/958048437729910854
445 //
446 // NOLINTNEXTLINE(performance-unnecessary-value-param)
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200447 const nlohmann::json newConfiguration)
Andrew Jefferye35d0ac2022-03-24 15:53:13 +1030448{
Alexander Hansena555acf2025-06-27 11:59:10 +0200449 loadOverlays(newConfiguration, io);
Andrew Jefferye35d0ac2022-03-24 15:53:13 +1030450
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200451 boost::asio::post(io, [this]() {
Christopher Meisbdaa6b22025-04-02 10:49:02 +0200452 if (!configuration::writeJsonFiles(systemConfiguration))
Andrew Jefferye35d0ac2022-03-24 15:53:13 +1030453 {
454 std::cerr << "Error writing json files\n";
455 }
456 });
457
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200458 boost::asio::post(io, [this, &instance, count, &timer, newConfiguration]() {
459 postToDbus(newConfiguration);
Andrew Jefferye35d0ac2022-03-24 15:53:13 +1030460 if (count == instance)
461 {
Alexander Hansen95ab18f2025-06-27 13:58:10 +0200462 startRemovedTimer(timer, systemConfiguration);
Andrew Jefferye35d0ac2022-03-24 15:53:13 +1030463 }
464 });
465}
466
James Feist8f2710a2018-05-09 17:18:55 -0700467// main properties changed entry
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200468void EntityManager::propertiesChangedCallback()
James Feist8f2710a2018-05-09 17:18:55 -0700469{
Alexander Hansenfc1b1e22025-06-27 14:34:11 +0200470 propertiesChangedInstance++;
471 size_t count = propertiesChangedInstance;
James Feist1df06a42019-04-11 14:23:04 -0700472
Alexander Hansenb1340da2025-06-27 14:29:13 +0200473 propertiesChangedTimer.expires_after(std::chrono::milliseconds(500));
James Feist8f2710a2018-05-09 17:18:55 -0700474
475 // setup an async wait as we normally get flooded with new requests
Alexander Hansenb1340da2025-06-27 14:29:13 +0200476 propertiesChangedTimer.async_wait(
477 [this, count](const boost::system::error_code& ec) {
478 if (ec == boost::asio::error::operation_aborted)
479 {
480 // we were cancelled
481 return;
482 }
483 if (ec)
484 {
485 std::cerr << "async wait error " << ec << "\n";
486 return;
487 }
James Feist8f2710a2018-05-09 17:18:55 -0700488
Alexander Hansenb1340da2025-06-27 14:29:13 +0200489 if (propertiesChangedInProgress)
490 {
491 propertiesChangedCallback();
492 return;
493 }
494 propertiesChangedInProgress = true;
James Feist2539ccd2020-05-01 16:15:08 -0700495
Alexander Hansenb1340da2025-06-27 14:29:13 +0200496 nlohmann::json oldConfiguration = systemConfiguration;
497 auto missingConfigurations = std::make_shared<nlohmann::json>();
498 *missingConfigurations = systemConfiguration;
James Feist899e17f2019-09-13 11:46:29 -0700499
Alexander Hansenb1340da2025-06-27 14:29:13 +0200500 std::list<nlohmann::json> configurations;
501 if (!configuration::loadConfigurations(configurations))
502 {
503 std::cerr << "Could not load configurations\n";
Alexander Hansen16d40022025-06-27 14:22:56 +0200504 propertiesChangedInProgress = false;
Alexander Hansenb1340da2025-06-27 14:29:13 +0200505 return;
506 }
James Feist2539ccd2020-05-01 16:15:08 -0700507
Alexander Hansenb1340da2025-06-27 14:29:13 +0200508 auto perfScan = std::make_shared<scan::PerformScan>(
509 *this, *missingConfigurations, configurations, io,
510 [this, count, oldConfiguration, missingConfigurations]() {
511 // this is something that since ac has been applied to the
512 // bmc we saw, and we no longer see it
513 bool powerOff = !em_utils::isPowerOn();
514 for (const auto& [name, device] :
515 missingConfigurations->items())
516 {
517 pruneConfiguration(powerOff, name, device);
518 }
519
520 nlohmann::json newConfiguration = systemConfiguration;
521
522 configuration::deriveNewConfiguration(oldConfiguration,
523 newConfiguration);
524
525 for (const auto& [_, device] : newConfiguration.items())
526 {
527 logDeviceAdded(device);
528 }
529
530 propertiesChangedInProgress = false;
531
532 boost::asio::post(io, [this, newConfiguration, count] {
533 publishNewConfiguration(
Alexander Hansenfc1b1e22025-06-27 14:34:11 +0200534 std::ref(propertiesChangedInstance), count,
Alexander Hansenb1340da2025-06-27 14:29:13 +0200535 std::ref(propertiesChangedTimer), newConfiguration);
536 });
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200537 });
Alexander Hansenb1340da2025-06-27 14:29:13 +0200538 perfScan->run();
539 });
James Feist75fdeeb2018-02-20 14:26:16 -0800540}
541
Matt Spinler8d1ac3a2023-02-02 09:48:04 -0600542// Check if InterfacesAdded payload contains an iface that needs probing.
Patrick Williamsb7077432024-08-16 15:22:21 -0400543static bool iaContainsProbeInterface(
544 sdbusplus::message_t& msg, const std::set<std::string>& probeInterfaces)
Matt Spinler8d1ac3a2023-02-02 09:48:04 -0600545{
546 sdbusplus::message::object_path path;
547 DBusObject interfaces;
548 std::set<std::string> interfaceSet;
549 std::set<std::string> intersect;
550
551 msg.read(path, interfaces);
552
553 std::for_each(interfaces.begin(), interfaces.end(),
554 [&interfaceSet](const auto& iface) {
Patrick Williamsb7077432024-08-16 15:22:21 -0400555 interfaceSet.insert(iface.first);
556 });
Matt Spinler8d1ac3a2023-02-02 09:48:04 -0600557
558 std::set_intersection(interfaceSet.begin(), interfaceSet.end(),
559 probeInterfaces.begin(), probeInterfaces.end(),
560 std::inserter(intersect, intersect.end()));
561 return !intersect.empty();
562}
563
564// Check if InterfacesRemoved payload contains an iface that needs probing.
Patrick Williamsb7077432024-08-16 15:22:21 -0400565static bool irContainsProbeInterface(
566 sdbusplus::message_t& msg, const std::set<std::string>& probeInterfaces)
Matt Spinler8d1ac3a2023-02-02 09:48:04 -0600567{
568 sdbusplus::message::object_path path;
569 std::set<std::string> interfaces;
570 std::set<std::string> intersect;
571
572 msg.read(path, interfaces);
573
574 std::set_intersection(interfaces.begin(), interfaces.end(),
575 probeInterfaces.begin(), probeInterfaces.end(),
576 std::inserter(intersect, intersect.end()));
577 return !intersect.empty();
578}
579
Alexander Hansenad28e402025-06-25 12:38:20 +0200580void EntityManager::handleCurrentConfigurationJson()
581{
582 if (em_utils::fwVersionIsSame())
583 {
584 if (std::filesystem::is_regular_file(
585 configuration::currentConfiguration))
586 {
587 // this file could just be deleted, but it's nice for debug
588 std::filesystem::create_directory(tempConfigDir);
589 std::filesystem::remove(lastConfiguration);
590 std::filesystem::copy(configuration::currentConfiguration,
591 lastConfiguration);
592 std::filesystem::remove(configuration::currentConfiguration);
593
594 std::ifstream jsonStream(lastConfiguration);
595 if (jsonStream.good())
596 {
597 auto data = nlohmann::json::parse(jsonStream, nullptr, false);
598 if (data.is_discarded())
599 {
600 std::cerr
601 << "syntax error in " << lastConfiguration << "\n";
602 }
603 else
604 {
605 lastJson = std::move(data);
606 }
607 }
608 else
609 {
610 std::cerr << "unable to open " << lastConfiguration << "\n";
611 }
612 }
613 }
614 else
615 {
616 // not an error, just logging at this level to make it in the journal
617 std::cerr << "Clearing previous configuration\n";
618 std::filesystem::remove(configuration::currentConfiguration);
619 }
620}
621
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200622void EntityManager::registerCallback(const std::string& path)
James Feist75fdeeb2018-02-20 14:26:16 -0800623{
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200624 static boost::container::flat_map<std::string, sdbusplus::bus::match_t>
625 dbusMatches;
James Feist4131aea2018-03-09 09:47:30 -0800626
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200627 if (dbusMatches.contains(path))
628 {
629 return;
630 }
Nan Zhoua3315672022-09-20 19:48:14 +0000631
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200632 std::function<void(sdbusplus::message_t & message)> eventHandler =
633 [&](sdbusplus::message_t&) { propertiesChangedCallback(); };
James Feistfd1264a2018-05-03 12:10:00 -0700634
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200635 sdbusplus::bus::match_t match(
636 static_cast<sdbusplus::bus_t&>(*systemBus),
637 "type='signal',member='PropertiesChanged',path='" + path + "'",
638 eventHandler);
639 dbusMatches.emplace(path, std::move(match));
640}
James Feistfd1264a2018-05-03 12:10:00 -0700641
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200642// We need a poke from DBus for static providers that create all their
643// objects prior to claiming a well-known name, and thus don't emit any
644// org.freedesktop.DBus.Properties signals. Similarly if a process exits
645// for any reason, expected or otherwise, we'll need a poke to remove
646// entities from DBus.
647void EntityManager::initFilters(const std::set<std::string>& probeInterfaces)
648{
Alexander Hansen0f7bd782025-06-27 13:39:53 +0200649 nameOwnerChangedMatch = std::make_unique<sdbusplus::bus::match_t>(
Patrick Williams2af39222022-07-22 19:26:56 -0500650 static_cast<sdbusplus::bus_t&>(*systemBus),
Brad Bishopc76af0f2020-12-04 13:50:23 -0500651 sdbusplus::bus::match::rules::nameOwnerChanged(),
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200652 [this](sdbusplus::message_t& m) {
Patrick Williamsb7077432024-08-16 15:22:21 -0400653 auto [name, oldOwner,
654 newOwner] = m.unpack<std::string, std::string, std::string>();
Patrick Williams7b8786f2022-10-10 10:23:37 -0500655
Patrick Williamsb7077432024-08-16 15:22:21 -0400656 if (name.starts_with(':'))
657 {
658 // We should do nothing with unique-name connections.
659 return;
660 }
Patrick Williams7b8786f2022-10-10 10:23:37 -0500661
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200662 propertiesChangedCallback();
Patrick Williamsb7077432024-08-16 15:22:21 -0400663 });
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200664
Brad Bishop10a8c5f2020-12-07 21:40:07 -0500665 // We also need a poke from DBus when new interfaces are created or
666 // destroyed.
Alexander Hansen0f7bd782025-06-27 13:39:53 +0200667 interfacesAddedMatch = std::make_unique<sdbusplus::bus::match_t>(
Patrick Williams2af39222022-07-22 19:26:56 -0500668 static_cast<sdbusplus::bus_t&>(*systemBus),
Brad Bishop10a8c5f2020-12-07 21:40:07 -0500669 sdbusplus::bus::match::rules::interfacesAdded(),
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200670 [this, probeInterfaces](sdbusplus::message_t& msg) {
Patrick Williamsb7077432024-08-16 15:22:21 -0400671 if (iaContainsProbeInterface(msg, probeInterfaces))
672 {
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200673 propertiesChangedCallback();
Patrick Williamsb7077432024-08-16 15:22:21 -0400674 }
675 });
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200676
Alexander Hansen0f7bd782025-06-27 13:39:53 +0200677 interfacesRemovedMatch = std::make_unique<sdbusplus::bus::match_t>(
Patrick Williams2af39222022-07-22 19:26:56 -0500678 static_cast<sdbusplus::bus_t&>(*systemBus),
Brad Bishop10a8c5f2020-12-07 21:40:07 -0500679 sdbusplus::bus::match::rules::interfacesRemoved(),
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200680 [this, probeInterfaces](sdbusplus::message_t& msg) {
Patrick Williamsb7077432024-08-16 15:22:21 -0400681 if (irContainsProbeInterface(msg, probeInterfaces))
682 {
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200683 propertiesChangedCallback();
Patrick Williamsb7077432024-08-16 15:22:21 -0400684 }
685 });
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200686}
Brad Bishopc76af0f2020-12-04 13:50:23 -0500687
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200688int main()
689{
Alexander Hansena555acf2025-06-27 11:59:10 +0200690 boost::asio::io_context io;
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200691 auto systemBus = std::make_shared<sdbusplus::asio::connection>(io);
692 systemBus->request_name("xyz.openbmc_project.EntityManager");
Alexander Hansena555acf2025-06-27 11:59:10 +0200693 EntityManager em(systemBus, io);
James Feist4131aea2018-03-09 09:47:30 -0800694
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200695 nlohmann::json systemConfiguration = nlohmann::json::object();
696
697 std::set<std::string> probeInterfaces = configuration::getProbeInterfaces();
698
699 em.initFilters(probeInterfaces);
700
701 boost::asio::post(io, [&]() { em.propertiesChangedCallback(); });
James Feist8f2710a2018-05-09 17:18:55 -0700702
Alexander Hansenad28e402025-06-25 12:38:20 +0200703 em.handleCurrentConfigurationJson();
James Feist1df06a42019-04-11 14:23:04 -0700704
705 // some boards only show up after power is on, we want to not say they are
706 // removed until the same state happens
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200707 em_utils::setupPowerMatch(em.systemBus);
James Feist1df06a42019-04-11 14:23:04 -0700708
James Feist1b2e2242018-01-30 13:45:19 -0800709 io.run();
James Feist3cb5fec2018-01-23 14:41:51 -0800710
711 return 0;
James Feist75fdeeb2018-02-20 14:26:16 -0800712}