blob: 399b59a5c79084d4651612fc6296c9dbd94d6501 [file] [log] [blame]
Willy Tude54f482021-01-26 15:59:09 -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*/
16
17#include "dbus-sdr/sdrutils.hpp"
18
Willy Tu4eca2512022-06-20 21:14:51 -070019#include <optional>
20#include <unordered_set>
21
Scron Chang2703b022021-07-06 15:47:45 +080022#ifdef FEATURE_HYBRID_SENSORS
23
24#include <ipmid/utils.hpp>
25namespace ipmi
26{
27namespace sensor
28{
29extern const IdInfoMap sensors;
30} // namespace sensor
31} // namespace ipmi
32
33#endif
34
Willy Tude54f482021-01-26 15:59:09 -080035namespace details
36{
Kuiying Wanga8b5b262021-02-06 23:38:22 +080037uint16_t getSensorSubtree(std::shared_ptr<SensorSubTree>& subtree)
Willy Tude54f482021-01-26 15:59:09 -080038{
39 static std::shared_ptr<SensorSubTree> sensorTreePtr;
Kuiying Wanga8b5b262021-02-06 23:38:22 +080040 static uint16_t sensorUpdatedIndex = 0;
Willy Tude54f482021-01-26 15:59:09 -080041 std::shared_ptr<sdbusplus::asio::connection> dbus = getSdBus();
Patrick Williams5d82f472022-07-22 19:26:53 -050042 static sdbusplus::bus::match_t sensorAdded(
Willy Tude54f482021-01-26 15:59:09 -080043 *dbus,
44 "type='signal',member='InterfacesAdded',arg0path='/xyz/openbmc_project/"
45 "sensors/'",
Patrick Williams5d82f472022-07-22 19:26:53 -050046 [](sdbusplus::message_t&) { sensorTreePtr.reset(); });
Willy Tude54f482021-01-26 15:59:09 -080047
Patrick Williams5d82f472022-07-22 19:26:53 -050048 static sdbusplus::bus::match_t sensorRemoved(
Willy Tude54f482021-01-26 15:59:09 -080049 *dbus,
50 "type='signal',member='InterfacesRemoved',arg0path='/xyz/"
51 "openbmc_project/sensors/'",
Patrick Williams5d82f472022-07-22 19:26:53 -050052 [](sdbusplus::message_t&) { sensorTreePtr.reset(); });
Willy Tude54f482021-01-26 15:59:09 -080053
Willy Tude54f482021-01-26 15:59:09 -080054 if (sensorTreePtr)
55 {
56 subtree = sensorTreePtr;
Kuiying Wanga8b5b262021-02-06 23:38:22 +080057 return sensorUpdatedIndex;
Willy Tude54f482021-01-26 15:59:09 -080058 }
59
60 sensorTreePtr = std::make_shared<SensorSubTree>();
61
Willy Tude54f482021-01-26 15:59:09 -080062 static constexpr const int32_t depth = 2;
Hao Jiang9a5b51e2021-01-06 10:32:22 -080063
Patrick Williamsfbc6c9d2023-05-10 07:50:16 -050064 auto lbdUpdateSensorTree =
65 [&dbus](const char* path, const auto& interfaces) {
Hao Jiang9a5b51e2021-01-06 10:32:22 -080066 auto mapperCall = dbus->new_method_call(
67 "xyz.openbmc_project.ObjectMapper",
68 "/xyz/openbmc_project/object_mapper",
69 "xyz.openbmc_project.ObjectMapper", "GetSubTree");
70 SensorSubTree sensorTreePartial;
71
72 mapperCall.append(path, depth, interfaces);
73
74 try
75 {
76 auto mapperReply = dbus->call(mapperCall);
77 mapperReply.read(sensorTreePartial);
78 }
Patrick Williamsa2ad2da2021-10-06 12:21:46 -050079 catch (const sdbusplus::exception_t& e)
Hao Jiang9a5b51e2021-01-06 10:32:22 -080080 {
81 phosphor::logging::log<phosphor::logging::level::ERR>(
82 "fail to update subtree",
83 phosphor::logging::entry("PATH=%s", path),
84 phosphor::logging::entry("WHAT=%s", e.what()));
85 return false;
86 }
87 if constexpr (debug)
88 {
89 std::fprintf(stderr, "IPMI updated: %zu sensors under %s\n",
90 sensorTreePartial.size(), path);
91 }
92 sensorTreePtr->merge(std::move(sensorTreePartial));
93 return true;
94 };
95
96 // Add sensors to SensorTree
97 static constexpr const std::array sensorInterfaces = {
Willy Tude54f482021-01-26 15:59:09 -080098 "xyz.openbmc_project.Sensor.Value",
Jie Yangf0a89942021-07-29 15:30:25 -070099 "xyz.openbmc_project.Sensor.ValueMutability",
Willy Tude54f482021-01-26 15:59:09 -0800100 "xyz.openbmc_project.Sensor.Threshold.Warning",
101 "xyz.openbmc_project.Sensor.Threshold.Critical"};
Hao Jiang9a5b51e2021-01-06 10:32:22 -0800102 static constexpr const std::array vrInterfaces = {
103 "xyz.openbmc_project.Control.VoltageRegulatorMode"};
Willy Tude54f482021-01-26 15:59:09 -0800104
Patrick Williamsfbc6c9d2023-05-10 07:50:16 -0500105 bool sensorRez = lbdUpdateSensorTree("/xyz/openbmc_project/sensors",
106 sensorInterfaces);
Hao Jiang9a5b51e2021-01-06 10:32:22 -0800107
Scron Chang2703b022021-07-06 15:47:45 +0800108#ifdef FEATURE_HYBRID_SENSORS
109
110 if (!ipmi::sensor::sensors.empty())
111 {
112 for (const auto& sensor : ipmi::sensor::sensors)
113 {
114 // Threshold sensors should not be emplaced in here.
115 if (boost::starts_with(sensor.second.sensorPath,
116 "/xyz/openbmc_project/sensors/"))
117 {
118 continue;
119 }
120
121 // The bus service name is not listed in ipmi::sensor::Info. Give it
122 // an empty string. For those function using non-threshold sensors,
123 // the bus service name will be retrieved in an alternative way.
124 boost::container::flat_map<std::string, std::vector<std::string>>
125 connectionMap{
126 {"", {sensor.second.propertyInterfaces.begin()->first}}};
127 sensorTreePtr->emplace(sensor.second.sensorPath, connectionMap);
128 }
129 }
130
131#endif
132
Hao Jiang9a5b51e2021-01-06 10:32:22 -0800133 // Error if searching for sensors failed.
134 if (!sensorRez)
Willy Tude54f482021-01-26 15:59:09 -0800135 {
Kuiying Wanga8b5b262021-02-06 23:38:22 +0800136 return sensorUpdatedIndex;
Willy Tude54f482021-01-26 15:59:09 -0800137 }
Hao Jiang9a5b51e2021-01-06 10:32:22 -0800138
139 // Add VR control as optional search path.
140 (void)lbdUpdateSensorTree("/xyz/openbmc_project/vr", vrInterfaces);
141
Willy Tude54f482021-01-26 15:59:09 -0800142 subtree = sensorTreePtr;
Kuiying Wanga8b5b262021-02-06 23:38:22 +0800143 sensorUpdatedIndex++;
Josh Lehana55c9532020-10-28 21:59:06 -0700144 // The SDR is being regenerated, wipe the old stats
145 sdrStatsTable.wipeTable();
Jie Yangf0a89942021-07-29 15:30:25 -0700146 sdrWriteTable.wipeTable();
Kuiying Wanga8b5b262021-02-06 23:38:22 +0800147 return sensorUpdatedIndex;
Willy Tude54f482021-01-26 15:59:09 -0800148}
149
150bool getSensorNumMap(std::shared_ptr<SensorNumMap>& sensorNumMap)
151{
152 static std::shared_ptr<SensorNumMap> sensorNumMapPtr;
153 bool sensorNumMapUpated = false;
Kuiying Wanga8b5b262021-02-06 23:38:22 +0800154 static uint16_t prevSensorUpdatedIndex = 0;
Willy Tude54f482021-01-26 15:59:09 -0800155 std::shared_ptr<SensorSubTree> sensorTree;
Kuiying Wanga8b5b262021-02-06 23:38:22 +0800156 uint16_t curSensorUpdatedIndex = details::getSensorSubtree(sensorTree);
Willy Tude54f482021-01-26 15:59:09 -0800157 if (!sensorTree)
158 {
159 return sensorNumMapUpated;
160 }
161
Kuiying Wanga8b5b262021-02-06 23:38:22 +0800162 if ((curSensorUpdatedIndex == prevSensorUpdatedIndex) && sensorNumMapPtr)
Willy Tude54f482021-01-26 15:59:09 -0800163 {
164 sensorNumMap = sensorNumMapPtr;
165 return sensorNumMapUpated;
166 }
Kuiying Wanga8b5b262021-02-06 23:38:22 +0800167 prevSensorUpdatedIndex = curSensorUpdatedIndex;
Willy Tude54f482021-01-26 15:59:09 -0800168
169 sensorNumMapPtr = std::make_shared<SensorNumMap>();
170
171 uint16_t sensorNum = 0;
172 uint16_t sensorIndex = 0;
173 for (const auto& sensor : *sensorTree)
174 {
175 sensorNumMapPtr->insert(
176 SensorNumMap::value_type(sensorNum, sensor.first));
177 sensorIndex++;
178 if (sensorIndex == maxSensorsPerLUN)
179 {
180 sensorIndex = lun1Sensor0;
181 }
182 else if (sensorIndex == (lun1Sensor0 | maxSensorsPerLUN))
183 {
184 // Skip assigning LUN 0x2 any sensors
185 sensorIndex = lun3Sensor0;
186 }
187 else if (sensorIndex == (lun3Sensor0 | maxSensorsPerLUN))
188 {
189 // this is an error, too many IPMI sensors
190 throw std::out_of_range("Maximum number of IPMI sensors exceeded.");
191 }
192 sensorNum = sensorIndex;
193 }
194 sensorNumMap = sensorNumMapPtr;
195 sensorNumMapUpated = true;
196 return sensorNumMapUpated;
197}
198} // namespace details
199
200bool getSensorSubtree(SensorSubTree& subtree)
201{
202 std::shared_ptr<SensorSubTree> sensorTree;
203 details::getSensorSubtree(sensorTree);
204 if (!sensorTree)
205 {
206 return false;
207 }
208
209 subtree = *sensorTree;
210 return true;
211}
212
Scron Chang2703b022021-07-06 15:47:45 +0800213#ifdef FEATURE_HYBRID_SENSORS
214// Static sensors are listed in sensor-gen.cpp.
215ipmi::sensor::IdInfoMap::const_iterator
216 findStaticSensor(const std::string& path)
217{
218 return std::find_if(
219 ipmi::sensor::sensors.begin(), ipmi::sensor::sensors.end(),
220 [&path](const ipmi::sensor::IdInfoMap::value_type& findSensor) {
Patrick Williamsfbc6c9d2023-05-10 07:50:16 -0500221 return findSensor.second.sensorPath == path;
Scron Chang2703b022021-07-06 15:47:45 +0800222 });
223}
224#endif
225
Willy Tude54f482021-01-26 15:59:09 -0800226std::string getSensorTypeStringFromPath(const std::string& path)
227{
228 // get sensor type string from path, path is defined as
229 // /xyz/openbmc_project/sensors/<type>/label
230 size_t typeEnd = path.rfind("/");
231 if (typeEnd == std::string::npos)
232 {
233 return path;
234 }
235 size_t typeStart = path.rfind("/", typeEnd - 1);
236 if (typeStart == std::string::npos)
237 {
238 return path;
239 }
240 // Start at the character after the '/'
241 typeStart++;
242 return path.substr(typeStart, typeEnd - typeStart);
243}
244
245uint8_t getSensorTypeFromPath(const std::string& path)
246{
247 uint8_t sensorType = 0;
248 std::string type = getSensorTypeStringFromPath(path);
249 auto findSensor = sensorTypes.find(type.c_str());
250 if (findSensor != sensorTypes.end())
251 {
Scron Chang2b42d7e2021-07-06 15:45:47 +0800252 sensorType =
253 static_cast<uint8_t>(std::get<sensorTypeCodes>(findSensor->second));
Willy Tude54f482021-01-26 15:59:09 -0800254 } // else default 0x0 RESERVED
255
256 return sensorType;
257}
258
259uint16_t getSensorNumberFromPath(const std::string& path)
260{
261 std::shared_ptr<SensorNumMap> sensorNumMapPtr;
262 details::getSensorNumMap(sensorNumMapPtr);
263 if (!sensorNumMapPtr)
264 {
265 return invalidSensorNumber;
266 }
267
268 try
269 {
270 return sensorNumMapPtr->right.at(path);
271 }
Patrick Williamsa2ad2da2021-10-06 12:21:46 -0500272 catch (const std::out_of_range& e)
Willy Tude54f482021-01-26 15:59:09 -0800273 {
274 phosphor::logging::log<phosphor::logging::level::ERR>(e.what());
275 return invalidSensorNumber;
276 }
277}
278
279uint8_t getSensorEventTypeFromPath(const std::string& path)
280{
Scron Chang2b42d7e2021-07-06 15:45:47 +0800281 uint8_t sensorEventType = 0;
282 std::string type = getSensorTypeStringFromPath(path);
283 auto findSensor = sensorTypes.find(type.c_str());
284 if (findSensor != sensorTypes.end())
285 {
286 sensorEventType = static_cast<uint8_t>(
287 std::get<sensorEventTypeCodes>(findSensor->second));
288 }
289
290 return sensorEventType;
Willy Tude54f482021-01-26 15:59:09 -0800291}
292
293std::string getPathFromSensorNumber(uint16_t sensorNum)
294{
295 std::shared_ptr<SensorNumMap> sensorNumMapPtr;
296 details::getSensorNumMap(sensorNumMapPtr);
297 if (!sensorNumMapPtr)
298 {
299 return std::string();
300 }
301
302 try
303 {
304 return sensorNumMapPtr->left.at(sensorNum);
305 }
Patrick Williamsa2ad2da2021-10-06 12:21:46 -0500306 catch (const std::out_of_range& e)
Willy Tude54f482021-01-26 15:59:09 -0800307 {
308 phosphor::logging::log<phosphor::logging::level::ERR>(e.what());
309 return std::string();
310 }
311}
312
313namespace ipmi
314{
315
316std::map<std::string, std::vector<std::string>>
317 getObjectInterfaces(const char* path)
318{
319 std::map<std::string, std::vector<std::string>> interfacesResponse;
320 std::vector<std::string> interfaces;
321 std::shared_ptr<sdbusplus::asio::connection> dbus = getSdBus();
322
Patrick Williams5d82f472022-07-22 19:26:53 -0500323 sdbusplus::message_t getObjectMessage =
Willy Tude54f482021-01-26 15:59:09 -0800324 dbus->new_method_call("xyz.openbmc_project.ObjectMapper",
325 "/xyz/openbmc_project/object_mapper",
326 "xyz.openbmc_project.ObjectMapper", "GetObject");
327 getObjectMessage.append(path, interfaces);
328
329 try
330 {
Patrick Williams5d82f472022-07-22 19:26:53 -0500331 sdbusplus::message_t response = dbus->call(getObjectMessage);
Willy Tude54f482021-01-26 15:59:09 -0800332 response.read(interfacesResponse);
333 }
334 catch (const std::exception& e)
335 {
336 phosphor::logging::log<phosphor::logging::level::ERR>(
337 "Failed to GetObject", phosphor::logging::entry("PATH=%s", path),
338 phosphor::logging::entry("WHAT=%s", e.what()));
339 }
340
341 return interfacesResponse;
342}
343
344std::map<std::string, Value> getEntityManagerProperties(const char* path,
345 const char* interface)
346{
347 std::map<std::string, Value> properties;
348 std::shared_ptr<sdbusplus::asio::connection> dbus = getSdBus();
349
Patrick Williams5d82f472022-07-22 19:26:53 -0500350 sdbusplus::message_t getProperties =
Willy Tude54f482021-01-26 15:59:09 -0800351 dbus->new_method_call("xyz.openbmc_project.EntityManager", path,
352 "org.freedesktop.DBus.Properties", "GetAll");
353 getProperties.append(interface);
354
355 try
356 {
Patrick Williams5d82f472022-07-22 19:26:53 -0500357 sdbusplus::message_t response = dbus->call(getProperties);
Willy Tude54f482021-01-26 15:59:09 -0800358 response.read(properties);
359 }
360 catch (const std::exception& e)
361 {
362 phosphor::logging::log<phosphor::logging::level::ERR>(
363 "Failed to GetAll", phosphor::logging::entry("PATH=%s", path),
364 phosphor::logging::entry("INTF=%s", interface),
365 phosphor::logging::entry("WHAT=%s", e.what()));
366 }
367
368 return properties;
369}
370
Willy Tu4eca2512022-06-20 21:14:51 -0700371// Fetch the ipmiDecoratorPaths to get the list of dbus objects that
372// have ipmi decorator to prevent unnessary dbus call to fetch the info
373std::optional<std::unordered_set<std::string>>&
374 getIpmiDecoratorPaths(const std::optional<ipmi::Context::ptr>& ctx)
375{
376 static std::optional<std::unordered_set<std::string>> ipmiDecoratorPaths;
377
378 if (!ctx.has_value() || ipmiDecoratorPaths != std::nullopt)
379 {
380 return ipmiDecoratorPaths;
381 }
382
383 boost::system::error_code ec;
384 std::vector<std::string> paths =
385 (*ctx)->bus->yield_method_call<std::vector<std::string>>(
386 (*ctx)->yield, ec, "xyz.openbmc_project.ObjectMapper",
387 "/xyz/openbmc_project/object_mapper",
388 "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", "/",
389 int32_t(0),
390 std::array<const char*, 1>{
391 "xyz.openbmc_project.Inventory.Decorator.Ipmi"});
392 if (ec)
393 {
394 return ipmiDecoratorPaths;
395 }
396
Patrick Williamsfbc6c9d2023-05-10 07:50:16 -0500397 ipmiDecoratorPaths = std::unordered_set<std::string>(paths.begin(),
398 paths.end());
Willy Tu4eca2512022-06-20 21:14:51 -0700399 return ipmiDecoratorPaths;
400}
401
Willy Tude54f482021-01-26 15:59:09 -0800402const std::string* getSensorConfigurationInterface(
403 const std::map<std::string, std::vector<std::string>>&
404 sensorInterfacesResponse)
405{
406 auto entityManagerService =
407 sensorInterfacesResponse.find("xyz.openbmc_project.EntityManager");
408 if (entityManagerService == sensorInterfacesResponse.end())
409 {
410 return nullptr;
411 }
412
413 // Find the fan configuration first (fans can have multiple configuration
414 // interfaces).
415 for (const auto& entry : entityManagerService->second)
416 {
417 if (entry == "xyz.openbmc_project.Configuration.AspeedFan" ||
418 entry == "xyz.openbmc_project.Configuration.I2CFan" ||
419 entry == "xyz.openbmc_project.Configuration.NuvotonFan")
420 {
421 return &entry;
422 }
423 }
424
425 for (const auto& entry : entityManagerService->second)
426 {
427 if (boost::algorithm::starts_with(entry,
428 "xyz.openbmc_project.Configuration."))
429 {
430 return &entry;
431 }
432 }
433
434 return nullptr;
435}
436
437// Follow Association properties for Sensor back to the Board dbus object to
438// check for an EntityId and EntityInstance property.
Willy Tu4eca2512022-06-20 21:14:51 -0700439void updateIpmiFromAssociation(
440 const std::string& path,
441 const std::unordered_set<std::string>& ipmiDecoratorPaths,
442 const DbusInterfaceMap& sensorMap, uint8_t& entityId,
443 uint8_t& entityInstance)
Willy Tude54f482021-01-26 15:59:09 -0800444{
445 namespace fs = std::filesystem;
446
447 auto sensorAssociationObject =
448 sensorMap.find("xyz.openbmc_project.Association.Definitions");
449 if (sensorAssociationObject == sensorMap.end())
450 {
451 if constexpr (debug)
452 {
453 std::fprintf(stderr, "path=%s, no association interface found\n",
454 path.c_str());
455 }
456
457 return;
458 }
459
460 auto associationObject =
461 sensorAssociationObject->second.find("Associations");
462 if (associationObject == sensorAssociationObject->second.end())
463 {
464 if constexpr (debug)
465 {
466 std::fprintf(stderr, "path=%s, no association records found\n",
467 path.c_str());
468 }
469
470 return;
471 }
472
473 std::vector<Association> associationValues =
474 std::get<std::vector<Association>>(associationObject->second);
475
476 // loop through the Associations looking for the right one:
477 for (const auto& entry : associationValues)
478 {
479 // forward, reverse, endpoint
480 const std::string& forward = std::get<0>(entry);
481 const std::string& reverse = std::get<1>(entry);
482 const std::string& endpoint = std::get<2>(entry);
483
484 // We only currently concern ourselves with chassis+all_sensors.
485 if (!(forward == "chassis" && reverse == "all_sensors"))
486 {
487 continue;
488 }
489
490 // the endpoint is the board entry provided by
491 // Entity-Manager. so let's grab its properties if it has
492 // the right interface.
493
494 // just try grabbing the properties first.
Willy Tu4eca2512022-06-20 21:14:51 -0700495 ipmi::PropertyMap::iterator entityIdProp;
496 ipmi::PropertyMap::iterator entityInstanceProp;
497 if (ipmiDecoratorPaths.contains(endpoint))
498 {
499 std::map<std::string, Value> ipmiProperties =
500 getEntityManagerProperties(
501 endpoint.c_str(),
502 "xyz.openbmc_project.Inventory.Decorator.Ipmi");
Willy Tude54f482021-01-26 15:59:09 -0800503
Willy Tu4eca2512022-06-20 21:14:51 -0700504 entityIdProp = ipmiProperties.find("EntityId");
505 entityInstanceProp = ipmiProperties.find("EntityInstance");
506 if (entityIdProp != ipmiProperties.end())
507 {
508 entityId = static_cast<uint8_t>(
509 std::get<uint64_t>(entityIdProp->second));
510 }
511 if (entityInstanceProp != ipmiProperties.end())
512 {
513 entityInstance = static_cast<uint8_t>(
514 std::get<uint64_t>(entityInstanceProp->second));
515 }
Willy Tude54f482021-01-26 15:59:09 -0800516 }
517
518 // Now check the entity-manager entry for this sensor to see
519 // if it has its own value and use that instead.
520 //
521 // In theory, checking this first saves us from checking
522 // both, except in most use-cases identified, there won't be
523 // a per sensor override, so we need to always check both.
524 std::string sensorNameFromPath = fs::path(path).filename();
525
526 std::string sensorConfigPath = endpoint + "/" + sensorNameFromPath;
527
528 // Download the interfaces for the sensor from
529 // Entity-Manager to find the name of the configuration
530 // interface.
531 std::map<std::string, std::vector<std::string>>
532 sensorInterfacesResponse =
533 getObjectInterfaces(sensorConfigPath.c_str());
534
535 const std::string* configurationInterface =
536 getSensorConfigurationInterface(sensorInterfacesResponse);
537
538 // We didnt' find a configuration interface for this sensor, but we
539 // followed the Association property to get here, so we're done
540 // searching.
541 if (!configurationInterface)
542 {
543 break;
544 }
545
546 // We found a configuration interface.
547 std::map<std::string, Value> configurationProperties =
548 getEntityManagerProperties(sensorConfigPath.c_str(),
549 configurationInterface->c_str());
550
551 entityIdProp = configurationProperties.find("EntityId");
552 entityInstanceProp = configurationProperties.find("EntityInstance");
553 if (entityIdProp != configurationProperties.end())
554 {
555 entityId =
556 static_cast<uint8_t>(std::get<uint64_t>(entityIdProp->second));
557 }
558 if (entityInstanceProp != configurationProperties.end())
559 {
560 entityInstance = static_cast<uint8_t>(
561 std::get<uint64_t>(entityInstanceProp->second));
562 }
563
564 // stop searching Association records.
565 break;
566 } // end for Association vectors.
567
568 if constexpr (debug)
569 {
570 std::fprintf(stderr, "path=%s, entityId=%d, entityInstance=%d\n",
571 path.c_str(), entityId, entityInstance);
572 }
573}
574
575} // namespace ipmi