xref: /openbmc/entity-manager/src/entity_manager/entity_manager.cpp (revision b2f82829bb6bab09d0d5d671a37224b564ec5dae)
1 // SPDX-License-Identifier: Apache-2.0
2 // SPDX-FileCopyrightText: Copyright 2018 Intel Corporation
3 
4 #include "entity_manager.hpp"
5 
6 #include "../utils.hpp"
7 #include "../variant_visitors.hpp"
8 #include "configuration.hpp"
9 #include "dbus_interface.hpp"
10 #include "log_device_inventory.hpp"
11 #include "overlay.hpp"
12 #include "perform_scan.hpp"
13 #include "phosphor-logging/lg2.hpp"
14 #include "topology.hpp"
15 #include "utils.hpp"
16 
17 #include <boost/algorithm/string/case_conv.hpp>
18 #include <boost/algorithm/string/classification.hpp>
19 #include <boost/algorithm/string/replace.hpp>
20 #include <boost/algorithm/string/split.hpp>
21 #include <boost/asio/io_context.hpp>
22 #include <boost/asio/post.hpp>
23 #include <boost/asio/steady_timer.hpp>
24 #include <boost/container/flat_map.hpp>
25 #include <boost/container/flat_set.hpp>
26 #include <boost/range/iterator_range.hpp>
27 #include <nlohmann/json.hpp>
28 #include <sdbusplus/asio/connection.hpp>
29 #include <sdbusplus/asio/object_server.hpp>
30 
31 #include <filesystem>
32 #include <fstream>
33 #include <functional>
34 #include <iostream>
35 #include <map>
36 #include <regex>
37 constexpr const char* tempConfigDir = "/tmp/configuration/";
38 constexpr const char* lastConfiguration = "/tmp/configuration/last.json";
39 
40 static constexpr std::array<const char*, 6> settableInterfaces = {
41     "FanProfile", "Pid", "Pid.Zone", "Stepwise", "Thresholds", "Polling"};
42 
43 const std::regex illegalDbusPathRegex("[^A-Za-z0-9_.]");
44 const std::regex illegalDbusMemberRegex("[^A-Za-z0-9_]");
45 
getPermission(const std::string & interface)46 sdbusplus::asio::PropertyPermission getPermission(const std::string& interface)
47 {
48     return std::find(settableInterfaces.begin(), settableInterfaces.end(),
49                      interface) != settableInterfaces.end()
50                ? sdbusplus::asio::PropertyPermission::readWrite
51                : sdbusplus::asio::PropertyPermission::readOnly;
52 }
53 
EntityManager(std::shared_ptr<sdbusplus::asio::connection> & systemBus,boost::asio::io_context & io)54 EntityManager::EntityManager(
55     std::shared_ptr<sdbusplus::asio::connection>& systemBus,
56     boost::asio::io_context& io) :
57     systemBus(systemBus),
58     objServer(sdbusplus::asio::object_server(systemBus, /*skipManager=*/true)),
59     lastJson(nlohmann::json::object()),
60     systemConfiguration(nlohmann::json::object()), io(io),
61     dbus_interface(io, objServer), powerStatus(*systemBus),
62     propertiesChangedTimer(io)
63 {
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");
69 
70     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);
76 
77     initFilters(configuration.probeInterfaces);
78 }
79 
postToDbus(const nlohmann::json & newConfiguration)80 void EntityManager::postToDbus(const nlohmann::json& newConfiguration)
81 {
82     std::map<std::string, std::string> newBoards; // path -> name
83 
84     // iterate through boards
85     for (const auto& [boardId, boardConfig] : newConfiguration.items())
86     {
87         postBoardToDBus(boardId, boardConfig, newBoards);
88     }
89 
90     for (const auto& [assocPath, assocPropValue] :
91          topology.getAssocs(std::views::keys(newBoards)))
92     {
93         auto findBoard = newBoards.find(assocPath);
94         if (findBoard == newBoards.end())
95         {
96             continue;
97         }
98 
99         auto ifacePtr = dbus_interface.createInterface(
100             assocPath, "xyz.openbmc_project.Association.Definitions",
101             findBoard->second);
102 
103         ifacePtr->register_property("Associations", assocPropValue);
104         dbus_interface::tryIfaceInitialize(ifacePtr);
105     }
106 }
107 
postBoardToDBus(const std::string & boardId,const nlohmann::json & boardConfig,std::map<std::string,std::string> & newBoards)108 void EntityManager::postBoardToDBus(
109     const std::string& boardId, const nlohmann::json& boardConfig,
110     std::map<std::string, std::string>& newBoards)
111 {
112     std::string boardName = boardConfig["Name"];
113     std::string boardNameOrig = boardConfig["Name"];
114     std::string jsonPointerPath = "/" + boardId;
115     // loop through newConfiguration, but use values from system
116     // configuration to be able to modify via dbus later
117     auto boardValues = systemConfiguration[boardId];
118     auto findBoardType = boardValues.find("Type");
119     std::string boardType;
120     if (findBoardType != boardValues.end() &&
121         findBoardType->type() == nlohmann::json::value_t::string)
122     {
123         boardType = findBoardType->get<std::string>();
124         std::regex_replace(boardType.begin(), boardType.begin(),
125                            boardType.end(), illegalDbusMemberRegex, "_");
126     }
127     else
128     {
129         std::cerr << "Unable to find type for " << boardName
130                   << " reverting to Chassis.\n";
131         boardType = "Chassis";
132     }
133 
134     const std::string boardPath =
135         em_utils::buildInventorySystemPath(boardName, boardType);
136 
137     std::shared_ptr<sdbusplus::asio::dbus_interface> inventoryIface =
138         dbus_interface.createInterface(
139             boardPath, "xyz.openbmc_project.Inventory.Item", boardName);
140 
141     std::shared_ptr<sdbusplus::asio::dbus_interface> boardIface =
142         dbus_interface.createInterface(
143             boardPath, "xyz.openbmc_project.Inventory.Item." + boardType,
144             boardNameOrig);
145 
146     dbus_interface.createAddObjectMethod(jsonPointerPath, boardPath,
147                                          systemConfiguration, boardNameOrig);
148 
149     dbus_interface.populateInterfaceFromJson(
150         systemConfiguration, jsonPointerPath, boardIface, boardValues);
151     jsonPointerPath += "/";
152     // iterate through board properties
153     for (const auto& [propName, propValue] : boardValues.items())
154     {
155         if (propValue.type() == nlohmann::json::value_t::object)
156         {
157             std::shared_ptr<sdbusplus::asio::dbus_interface> iface =
158                 dbus_interface.createInterface(boardPath, propName,
159                                                boardNameOrig);
160 
161             dbus_interface.populateInterfaceFromJson(
162                 systemConfiguration, jsonPointerPath + propName, iface,
163                 propValue);
164         }
165     }
166 
167     nlohmann::json::iterator exposes = boardValues.find("Exposes");
168     if (exposes == boardValues.end())
169     {
170         return;
171     }
172     // iterate through exposes
173     jsonPointerPath += "Exposes/";
174 
175     // store the board level pointer so we can modify it on the way down
176     std::string jsonPointerPathBoard = jsonPointerPath;
177     size_t exposesIndex = -1;
178     for (nlohmann::json& item : *exposes)
179     {
180         postExposesRecordsToDBus(item, exposesIndex, boardNameOrig,
181                                  jsonPointerPath, jsonPointerPathBoard,
182                                  boardPath, boardType);
183     }
184 
185     newBoards.emplace(boardPath, boardNameOrig);
186 }
187 
postExposesRecordsToDBus(nlohmann::json & item,size_t & exposesIndex,const std::string & boardNameOrig,std::string jsonPointerPath,const std::string & jsonPointerPathBoard,const std::string & boardPath,const std::string & boardType)188 void EntityManager::postExposesRecordsToDBus(
189     nlohmann::json& item, size_t& exposesIndex,
190     const std::string& boardNameOrig, std::string jsonPointerPath,
191     const std::string& jsonPointerPathBoard, const std::string& boardPath,
192     const std::string& boardType)
193 {
194     exposesIndex++;
195     jsonPointerPath = jsonPointerPathBoard;
196     jsonPointerPath += std::to_string(exposesIndex);
197 
198     auto findName = item.find("Name");
199     if (findName == item.end())
200     {
201         std::cerr << "cannot find name in field " << item << "\n";
202         return;
203     }
204     auto findStatus = item.find("Status");
205     // if status is not found it is assumed to be status = 'okay'
206     if (findStatus != item.end())
207     {
208         if (*findStatus == "disabled")
209         {
210             return;
211         }
212     }
213     auto findType = item.find("Type");
214     std::string itemType;
215     if (findType != item.end())
216     {
217         itemType = findType->get<std::string>();
218         std::regex_replace(itemType.begin(), itemType.begin(), itemType.end(),
219                            illegalDbusPathRegex, "_");
220     }
221     else
222     {
223         itemType = "unknown";
224     }
225     std::string itemName = findName->get<std::string>();
226     std::regex_replace(itemName.begin(), itemName.begin(), itemName.end(),
227                        illegalDbusMemberRegex, "_");
228     std::string ifacePath = boardPath;
229     ifacePath += "/";
230     ifacePath += itemName;
231 
232     if (itemType == "BMC")
233     {
234         std::shared_ptr<sdbusplus::asio::dbus_interface> bmcIface =
235             dbus_interface.createInterface(
236                 ifacePath, "xyz.openbmc_project.Inventory.Item.Bmc",
237                 boardNameOrig);
238         dbus_interface.populateInterfaceFromJson(
239             systemConfiguration, jsonPointerPath, bmcIface, item,
240             getPermission(itemType));
241     }
242     else if (itemType == "System")
243     {
244         std::shared_ptr<sdbusplus::asio::dbus_interface> systemIface =
245             dbus_interface.createInterface(
246                 ifacePath, "xyz.openbmc_project.Inventory.Item.System",
247                 boardNameOrig);
248         dbus_interface.populateInterfaceFromJson(
249             systemConfiguration, jsonPointerPath, systemIface, item,
250             getPermission(itemType));
251     }
252 
253     for (const auto& [name, config] : item.items())
254     {
255         jsonPointerPath = jsonPointerPathBoard;
256         jsonPointerPath.append(std::to_string(exposesIndex))
257             .append("/")
258             .append(name);
259 
260         if (!postConfigurationRecord(name, config, boardNameOrig, itemType,
261                                      jsonPointerPath, ifacePath))
262         {
263             break;
264         }
265     }
266 
267     std::shared_ptr<sdbusplus::asio::dbus_interface> itemIface =
268         dbus_interface.createInterface(
269             ifacePath, "xyz.openbmc_project.Configuration." + itemType,
270             boardNameOrig);
271 
272     dbus_interface.populateInterfaceFromJson(
273         systemConfiguration, jsonPointerPath, itemIface, item,
274         getPermission(itemType));
275 
276     topology.addBoard(boardPath, boardType, boardNameOrig, item);
277 }
278 
postConfigurationRecord(const std::string & name,nlohmann::json & config,const std::string & boardNameOrig,const std::string & itemType,const std::string & jsonPointerPath,const std::string & ifacePath)279 bool EntityManager::postConfigurationRecord(
280     const std::string& name, nlohmann::json& config,
281     const std::string& boardNameOrig, const std::string& itemType,
282     const std::string& jsonPointerPath, const std::string& ifacePath)
283 {
284     if (config.type() == nlohmann::json::value_t::object)
285     {
286         std::string ifaceName = "xyz.openbmc_project.Configuration.";
287         ifaceName.append(itemType).append(".").append(name);
288 
289         std::shared_ptr<sdbusplus::asio::dbus_interface> objectIface =
290             dbus_interface.createInterface(ifacePath, ifaceName, boardNameOrig);
291 
292         dbus_interface.populateInterfaceFromJson(
293             systemConfiguration, jsonPointerPath, objectIface, config,
294             getPermission(name));
295     }
296     else if (config.type() == nlohmann::json::value_t::array)
297     {
298         size_t index = 0;
299         if (config.empty())
300         {
301             return true;
302         }
303         bool isLegal = true;
304         auto type = config[0].type();
305         if (type != nlohmann::json::value_t::object)
306         {
307             return true;
308         }
309 
310         // verify legal json
311         for (const auto& arrayItem : config)
312         {
313             if (arrayItem.type() != type)
314             {
315                 isLegal = false;
316                 break;
317             }
318         }
319         if (!isLegal)
320         {
321             std::cerr << "dbus format error" << config << "\n";
322             return false;
323         }
324 
325         for (auto& arrayItem : config)
326         {
327             std::string ifaceName = "xyz.openbmc_project.Configuration.";
328             ifaceName.append(itemType).append(".").append(name);
329             ifaceName.append(std::to_string(index));
330 
331             std::shared_ptr<sdbusplus::asio::dbus_interface> objectIface =
332                 dbus_interface.createInterface(ifacePath, ifaceName,
333                                                boardNameOrig);
334 
335             dbus_interface.populateInterfaceFromJson(
336                 systemConfiguration,
337                 jsonPointerPath + "/" + std::to_string(index), objectIface,
338                 arrayItem, getPermission(name));
339             index++;
340         }
341     }
342 
343     return true;
344 }
345 
deviceRequiresPowerOn(const nlohmann::json & entity)346 static bool deviceRequiresPowerOn(const nlohmann::json& entity)
347 {
348     auto powerState = entity.find("PowerState");
349     if (powerState == entity.end())
350     {
351         return false;
352     }
353 
354     const auto* ptr = powerState->get_ptr<const std::string*>();
355     if (ptr == nullptr)
356     {
357         return false;
358     }
359 
360     return *ptr == "On" || *ptr == "BiosPost";
361 }
362 
pruneDevice(const nlohmann::json & systemConfiguration,const bool powerOff,const bool scannedPowerOff,const std::string & name,const nlohmann::json & device)363 static void pruneDevice(const nlohmann::json& systemConfiguration,
364                         const bool powerOff, const bool scannedPowerOff,
365                         const std::string& name, const nlohmann::json& device)
366 {
367     if (systemConfiguration.contains(name))
368     {
369         return;
370     }
371 
372     if (deviceRequiresPowerOn(device) && (powerOff || scannedPowerOff))
373     {
374         return;
375     }
376 
377     logDeviceRemoved(device);
378 }
379 
startRemovedTimer(boost::asio::steady_timer & timer,nlohmann::json & systemConfiguration)380 void EntityManager::startRemovedTimer(boost::asio::steady_timer& timer,
381                                       nlohmann::json& systemConfiguration)
382 {
383     if (systemConfiguration.empty() || lastJson.empty())
384     {
385         return; // not ready yet
386     }
387     if (scannedPowerOn)
388     {
389         return;
390     }
391 
392     if (!powerStatus.isPowerOn() && scannedPowerOff)
393     {
394         return;
395     }
396 
397     timer.expires_after(std::chrono::seconds(10));
398     timer.async_wait(
399         [&systemConfiguration, this](const boost::system::error_code& ec) {
400             if (ec == boost::asio::error::operation_aborted)
401             {
402                 return;
403             }
404 
405             bool powerOff = !powerStatus.isPowerOn();
406             for (const auto& [name, device] : lastJson.items())
407             {
408                 pruneDevice(systemConfiguration, powerOff, scannedPowerOff,
409                             name, device);
410             }
411 
412             scannedPowerOff = true;
413             if (!powerOff)
414             {
415                 scannedPowerOn = true;
416             }
417         });
418 }
419 
pruneConfiguration(bool powerOff,const std::string & name,const nlohmann::json & device)420 void EntityManager::pruneConfiguration(bool powerOff, const std::string& name,
421                                        const nlohmann::json& device)
422 {
423     if (powerOff && deviceRequiresPowerOn(device))
424     {
425         // power not on yet, don't know if it's there or not
426         return;
427     }
428 
429     auto& ifaces = dbus_interface.getDeviceInterfaces(device);
430     for (auto& iface : ifaces)
431     {
432         auto sharedPtr = iface.lock();
433         if (!!sharedPtr)
434         {
435             objServer.remove_interface(sharedPtr);
436         }
437     }
438 
439     ifaces.clear();
440     systemConfiguration.erase(name);
441     topology.remove(device["Name"].get<std::string>());
442     logDeviceRemoved(device);
443 }
444 
publishNewConfiguration(const size_t & instance,const size_t count,boost::asio::steady_timer & timer,const nlohmann::json newConfiguration)445 void EntityManager::publishNewConfiguration(
446     const size_t& instance, const size_t count,
447     boost::asio::steady_timer& timer, // Gerrit discussion:
448     // https://gerrit.openbmc-project.xyz/c/openbmc/entity-manager/+/52316/6
449     //
450     // Discord discussion:
451     // https://discord.com/channels/775381525260664832/867820390406422538/958048437729910854
452     //
453     // NOLINTNEXTLINE(performance-unnecessary-value-param)
454     const nlohmann::json newConfiguration)
455 {
456     loadOverlays(newConfiguration, io);
457 
458     boost::asio::post(io, [this]() {
459         if (!writeJsonFiles(systemConfiguration))
460         {
461             std::cerr << "Error writing json files\n";
462         }
463     });
464 
465     boost::asio::post(io, [this, &instance, count, &timer, newConfiguration]() {
466         postToDbus(newConfiguration);
467         if (count == instance)
468         {
469             startRemovedTimer(timer, systemConfiguration);
470         }
471     });
472 }
473 
474 // main properties changed entry
propertiesChangedCallback()475 void EntityManager::propertiesChangedCallback()
476 {
477     propertiesChangedInstance++;
478     size_t count = propertiesChangedInstance;
479 
480     propertiesChangedTimer.expires_after(std::chrono::milliseconds(500));
481 
482     // setup an async wait as we normally get flooded with new requests
483     propertiesChangedTimer.async_wait(
484         [this, count](const boost::system::error_code& ec) {
485             if (ec == boost::asio::error::operation_aborted)
486             {
487                 // we were cancelled
488                 return;
489             }
490             if (ec)
491             {
492                 std::cerr << "async wait error " << ec << "\n";
493                 return;
494             }
495 
496             if (propertiesChangedInProgress)
497             {
498                 propertiesChangedCallback();
499                 return;
500             }
501             propertiesChangedInProgress = true;
502 
503             nlohmann::json oldConfiguration = systemConfiguration;
504             auto missingConfigurations = std::make_shared<nlohmann::json>();
505             *missingConfigurations = systemConfiguration;
506 
507             auto perfScan = std::make_shared<scan::PerformScan>(
508                 *this, *missingConfigurations, configuration.configurations, io,
509                 [this, count, oldConfiguration, missingConfigurations]() {
510                     // this is something that since ac has been applied to the
511                     // bmc we saw, and we no longer see it
512                     bool powerOff = !powerStatus.isPowerOn();
513                     for (const auto& [name, device] :
514                          missingConfigurations->items())
515                     {
516                         pruneConfiguration(powerOff, name, device);
517                     }
518                     nlohmann::json newConfiguration = systemConfiguration;
519 
520                     deriveNewConfiguration(oldConfiguration, newConfiguration);
521 
522                     for (const auto& [_, device] : newConfiguration.items())
523                     {
524                         logDeviceAdded(device);
525                     }
526 
527                     propertiesChangedInProgress = false;
528 
529                     boost::asio::post(io, [this, newConfiguration, count] {
530                         publishNewConfiguration(
531                             std::ref(propertiesChangedInstance), count,
532                             std::ref(propertiesChangedTimer), newConfiguration);
533                     });
534                 });
535             perfScan->run();
536         });
537 }
538 
539 // Check if InterfacesAdded payload contains an iface that needs probing.
iaContainsProbeInterface(sdbusplus::message_t & msg,const std::unordered_set<std::string> & probeInterfaces)540 static bool iaContainsProbeInterface(
541     sdbusplus::message_t& msg,
542     const std::unordered_set<std::string>& probeInterfaces)
543 {
544     sdbusplus::message::object_path path;
545     DBusObject interfaces;
546     msg.read(path, interfaces);
547     return std::ranges::any_of(interfaces | std::views::keys,
548                                [&probeInterfaces](const auto& ifaceName) {
549                                    return probeInterfaces.contains(ifaceName);
550                                });
551 }
552 
553 // Check if InterfacesRemoved payload contains an iface that needs probing.
irContainsProbeInterface(sdbusplus::message_t & msg,const std::unordered_set<std::string> & probeInterfaces)554 static bool irContainsProbeInterface(
555     sdbusplus::message_t& msg,
556     const std::unordered_set<std::string>& probeInterfaces)
557 {
558     sdbusplus::message::object_path path;
559     std::vector<std::string> interfaces;
560     msg.read(path, interfaces);
561     return std::ranges::any_of(interfaces,
562                                [&probeInterfaces](const auto& ifaceName) {
563                                    return probeInterfaces.contains(ifaceName);
564                                });
565 }
566 
handleCurrentConfigurationJson()567 void EntityManager::handleCurrentConfigurationJson()
568 {
569     if (EM_CACHE_CONFIGURATION && em_utils::fwVersionIsSame())
570     {
571         if (std::filesystem::is_regular_file(currentConfiguration))
572         {
573             // this file could just be deleted, but it's nice for debug
574             std::filesystem::create_directory(tempConfigDir);
575             std::filesystem::remove(lastConfiguration);
576             std::filesystem::copy(currentConfiguration, lastConfiguration);
577             std::filesystem::remove(currentConfiguration);
578 
579             std::ifstream jsonStream(lastConfiguration);
580             if (jsonStream.good())
581             {
582                 auto data = nlohmann::json::parse(jsonStream, nullptr, false);
583                 if (data.is_discarded())
584                 {
585                     std::cerr
586                         << "syntax error in " << lastConfiguration << "\n";
587                 }
588                 else
589                 {
590                     lastJson = std::move(data);
591                 }
592             }
593             else
594             {
595                 std::cerr << "unable to open " << lastConfiguration << "\n";
596             }
597         }
598     }
599     else
600     {
601         // not an error, just logging at this level to make it in the journal
602         std::error_code ec;
603         std::cerr << "Clearing previous configuration\n";
604         std::filesystem::remove(currentConfiguration, ec);
605     }
606 }
607 
registerCallback(const std::string & path)608 void EntityManager::registerCallback(const std::string& path)
609 {
610     if (dbusMatches.contains(path))
611     {
612         return;
613     }
614 
615     lg2::debug("creating PropertiesChanged match on {PATH}", "PATH", path);
616 
617     std::function<void(sdbusplus::message_t & message)> eventHandler =
618         [&](sdbusplus::message_t&) { propertiesChangedCallback(); };
619 
620     sdbusplus::bus::match_t match(
621         static_cast<sdbusplus::bus_t&>(*systemBus),
622         "type='signal',member='PropertiesChanged',path='" + path + "'",
623         eventHandler);
624     dbusMatches.emplace(path, std::move(match));
625 }
626 
627 // We need a poke from DBus for static providers that create all their
628 // objects prior to claiming a well-known name, and thus don't emit any
629 // org.freedesktop.DBus.Properties signals.  Similarly if a process exits
630 // for any reason, expected or otherwise, we'll need a poke to remove
631 // entities from DBus.
initFilters(const std::unordered_set<std::string> & probeInterfaces)632 void EntityManager::initFilters(
633     const std::unordered_set<std::string>& probeInterfaces)
634 {
635     nameOwnerChangedMatch = std::make_unique<sdbusplus::bus::match_t>(
636         static_cast<sdbusplus::bus_t&>(*systemBus),
637         sdbusplus::bus::match::rules::nameOwnerChanged(),
638         [this](sdbusplus::message_t& m) {
639             auto [name, oldOwner,
640                   newOwner] = m.unpack<std::string, std::string, std::string>();
641 
642             if (name.starts_with(':'))
643             {
644                 // We should do nothing with unique-name connections.
645                 return;
646             }
647 
648             propertiesChangedCallback();
649         });
650 
651     // We also need a poke from DBus when new interfaces are created or
652     // destroyed.
653     interfacesAddedMatch = std::make_unique<sdbusplus::bus::match_t>(
654         static_cast<sdbusplus::bus_t&>(*systemBus),
655         sdbusplus::bus::match::rules::interfacesAdded(),
656         [this, probeInterfaces](sdbusplus::message_t& msg) {
657             if (iaContainsProbeInterface(msg, probeInterfaces))
658             {
659                 propertiesChangedCallback();
660             }
661         });
662 
663     interfacesRemovedMatch = std::make_unique<sdbusplus::bus::match_t>(
664         static_cast<sdbusplus::bus_t&>(*systemBus),
665         sdbusplus::bus::match::rules::interfacesRemoved(),
666         [this, probeInterfaces](sdbusplus::message_t& msg) {
667             if (irContainsProbeInterface(msg, probeInterfaces))
668             {
669                 propertiesChangedCallback();
670             }
671         });
672 }
673