1 /*
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
19 #include <ipmid/utils.hpp>
20 #include <nlohmann/json.hpp>
21 #include <phosphor-logging/lg2.hpp>
22
23 #include <fstream>
24 #include <optional>
25 #include <unordered_set>
26
27 #ifdef FEATURE_HYBRID_SENSORS
28
29 #include <ipmid/utils.hpp>
30 namespace ipmi
31 {
32 namespace sensor
33 {
34 extern const IdInfoMap sensors;
35 } // namespace sensor
36 } // namespace ipmi
37
38 #endif
39
40 boost::container::flat_map<
41 const char*, std::pair<SensorTypeCodes, SensorEventTypeCodes>, CmpStr>
42 sensorTypes{
43 {{"temperature", std::make_pair(SensorTypeCodes::temperature,
44 SensorEventTypeCodes::threshold)},
45 {"voltage", std::make_pair(SensorTypeCodes::voltage,
46 SensorEventTypeCodes::threshold)},
47 {"current", std::make_pair(SensorTypeCodes::current,
48 SensorEventTypeCodes::threshold)},
49 {"fan_tach", std::make_pair(SensorTypeCodes::fan,
50 SensorEventTypeCodes::threshold)},
51 {"fan_pwm", std::make_pair(SensorTypeCodes::fan,
52 SensorEventTypeCodes::threshold)},
53 {"intrusion", std::make_pair(SensorTypeCodes::physical_security,
54 SensorEventTypeCodes::sensorSpecified)},
55 {"processor", std::make_pair(SensorTypeCodes::processor,
56 SensorEventTypeCodes::sensorSpecified)},
57 {"power", std::make_pair(SensorTypeCodes::other,
58 SensorEventTypeCodes::threshold)},
59 {"memory", std::make_pair(SensorTypeCodes::memory,
60 SensorEventTypeCodes::sensorSpecified)},
61 {"state", std::make_pair(SensorTypeCodes::power_unit,
62 SensorEventTypeCodes::sensorSpecified)},
63 {"buttons", std::make_pair(SensorTypeCodes::buttons,
64 SensorEventTypeCodes::sensorSpecified)},
65 {"watchdog", std::make_pair(SensorTypeCodes::watchdog2,
66 SensorEventTypeCodes::sensorSpecified)},
67 {"entity", std::make_pair(SensorTypeCodes::entity,
68 SensorEventTypeCodes::sensorSpecified)},
69 {"energy", std::make_pair(SensorTypeCodes::other,
70 SensorEventTypeCodes::threshold)}}};
71
72 namespace details
73 {
74
75 // IPMI supports a smaller number of sensors than are available via Redfish.
76 // Trim the list of sensors, via a configuration file.
77 // Read the IPMI Sensor Filtering section in docs/configuration.md for
78 // a more detailed description.
filterSensors(SensorSubTree & subtree)79 static void filterSensors(SensorSubTree& subtree)
80 {
81 constexpr const char* filterFilename =
82 "/usr/share/ipmi-providers/sensor_filter.json";
83 std::ifstream filterFile(filterFilename);
84 if (!filterFile.good())
85 {
86 return;
87 }
88 nlohmann::json sensorFilterJSON =
89 nlohmann::json::parse(filterFile, nullptr, false);
90 nlohmann::json::iterator svcFilterit =
91 sensorFilterJSON.find("ServiceFilter");
92 if (svcFilterit == sensorFilterJSON.end())
93 {
94 return;
95 }
96
97 subtree.erase(std::remove_if(subtree.begin(), subtree.end(),
98 [svcFilterit](SensorSubTree::value_type& kv) {
99 auto& [_, serviceToIfaces] = kv;
100
101 for (auto service = svcFilterit->begin();
102 service != svcFilterit->end();
103 ++service)
104 {
105 serviceToIfaces.erase(*service);
106 }
107 return serviceToIfaces.empty();
108 }),
109 subtree.end());
110 }
111
getSensorSubtree(std::shared_ptr<SensorSubTree> & subtree)112 uint16_t getSensorSubtree(std::shared_ptr<SensorSubTree>& subtree)
113 {
114 static std::shared_ptr<SensorSubTree> sensorTreePtr;
115 static uint16_t sensorUpdatedIndex = 0;
116 std::shared_ptr<sdbusplus::asio::connection> dbus = getSdBus();
117 static sdbusplus::bus::match_t sensorAdded(
118 *dbus,
119 "type='signal',member='InterfacesAdded',arg0path='/xyz/openbmc_project/"
120 "sensors/'",
121 [](sdbusplus::message_t&) { sensorTreePtr.reset(); });
122
123 static sdbusplus::bus::match_t sensorRemoved(
124 *dbus,
125 "type='signal',member='InterfacesRemoved',arg0path='/xyz/"
126 "openbmc_project/sensors/'",
127 [](sdbusplus::message_t&) { sensorTreePtr.reset(); });
128
129 if (sensorTreePtr)
130 {
131 subtree = sensorTreePtr;
132 return sensorUpdatedIndex;
133 }
134
135 sensorTreePtr = std::make_shared<SensorSubTree>();
136
137 static constexpr const int32_t depth = 2;
138
139 auto lbdUpdateSensorTree = [&dbus](const char* path,
140 const auto& interfaces) {
141 auto mapperCall = dbus->new_method_call(
142 "xyz.openbmc_project.ObjectMapper",
143 "/xyz/openbmc_project/object_mapper",
144 "xyz.openbmc_project.ObjectMapper", "GetSubTree");
145 SensorSubTree sensorTreePartial;
146
147 mapperCall.append(path, depth, interfaces);
148
149 try
150 {
151 auto mapperReply = dbus->call(mapperCall);
152 mapperReply.read(sensorTreePartial);
153 }
154 catch (const sdbusplus::exception_t& e)
155 {
156 lg2::error("Failed to update subtree, path: {PATH}, error: {ERROR}",
157 "PATH", path, "ERROR", e);
158 return false;
159 }
160 if constexpr (debug)
161 {
162 std::fprintf(stderr, "IPMI updated: %zu sensors under %s\n",
163 sensorTreePartial.size(), path);
164 }
165 sensorTreePtr->merge(std::move(sensorTreePartial));
166 return true;
167 };
168
169 // Add sensors to SensorTree
170 static constexpr const std::array sensorInterfaces = {
171 "xyz.openbmc_project.Sensor.Value",
172 "xyz.openbmc_project.Sensor.ValueMutability",
173 "xyz.openbmc_project.Sensor.Threshold.Warning",
174 "xyz.openbmc_project.Sensor.Threshold.Critical"};
175 static constexpr const std::array vrInterfaces = {
176 "xyz.openbmc_project.Control.VoltageRegulatorMode"};
177
178 bool sensorRez =
179 lbdUpdateSensorTree("/xyz/openbmc_project/sensors", sensorInterfaces);
180
181 #ifdef FEATURE_HYBRID_SENSORS
182
183 if (!ipmi::sensor::sensors.empty())
184 {
185 for (const auto& sensor : ipmi::sensor::sensors)
186 {
187 // Threshold sensors should not be emplaced in here.
188 if (boost::starts_with(sensor.second.sensorPath,
189 "/xyz/openbmc_project/sensors/"))
190 {
191 continue;
192 }
193
194 // The bus service name is not listed in ipmi::sensor::Info. Give it
195 // an empty string. For those function using non-threshold sensors,
196 // the bus service name will be retrieved in an alternative way.
197 boost::container::flat_map<std::string, std::vector<std::string>>
198 connectionMap{
199 {"", {sensor.second.propertyInterfaces.begin()->first}}};
200 sensorTreePtr->emplace(sensor.second.sensorPath, connectionMap);
201 }
202 }
203
204 #endif
205
206 // Error if searching for sensors failed.
207 if (!sensorRez)
208 {
209 return sensorUpdatedIndex;
210 }
211
212 filterSensors(*sensorTreePtr);
213 // Add VR control as optional search path.
214 (void)lbdUpdateSensorTree("/xyz/openbmc_project/vr", vrInterfaces);
215
216 subtree = sensorTreePtr;
217 sensorUpdatedIndex++;
218 // The SDR is being regenerated, wipe the old stats
219 sdrStatsTable.wipeTable();
220 sdrWriteTable.wipeTable();
221 return sensorUpdatedIndex;
222 }
223
getSensorNumMap(std::shared_ptr<SensorNumMap> & sensorNumMap)224 bool getSensorNumMap(std::shared_ptr<SensorNumMap>& sensorNumMap)
225 {
226 static std::shared_ptr<SensorNumMap> sensorNumMapPtr;
227 bool sensorNumMapUpated = false;
228 static uint16_t prevSensorUpdatedIndex = 0;
229 std::shared_ptr<SensorSubTree> sensorTree;
230 uint16_t curSensorUpdatedIndex = details::getSensorSubtree(sensorTree);
231 if (!sensorTree)
232 {
233 return sensorNumMapUpated;
234 }
235
236 if ((curSensorUpdatedIndex == prevSensorUpdatedIndex) && sensorNumMapPtr)
237 {
238 sensorNumMap = sensorNumMapPtr;
239 return sensorNumMapUpated;
240 }
241 prevSensorUpdatedIndex = curSensorUpdatedIndex;
242
243 sensorNumMapPtr = std::make_shared<SensorNumMap>();
244
245 uint16_t sensorNum = 0;
246 uint16_t sensorIndex = 0;
247 for (const auto& sensor : *sensorTree)
248 {
249 sensorNumMapPtr->insert(
250 SensorNumMap::value_type(sensorNum, sensor.first));
251 sensorIndex++;
252 if (sensorIndex == maxSensorsPerLUN)
253 {
254 sensorIndex = lun1Sensor0;
255 }
256 else if (sensorIndex == (lun1Sensor0 | maxSensorsPerLUN))
257 {
258 // Skip assigning LUN 0x2 any sensors
259 sensorIndex = lun3Sensor0;
260 }
261 else if (sensorIndex == (lun3Sensor0 | maxSensorsPerLUN))
262 {
263 // this is an error, too many IPMI sensors
264 throw std::out_of_range("Maximum number of IPMI sensors exceeded.");
265 }
266 sensorNum = sensorIndex;
267 }
268 sensorNumMap = sensorNumMapPtr;
269 sensorNumMapUpated = true;
270 return sensorNumMapUpated;
271 }
272 } // namespace details
273
getSensorSubtree(SensorSubTree & subtree)274 bool getSensorSubtree(SensorSubTree& subtree)
275 {
276 std::shared_ptr<SensorSubTree> sensorTree;
277 details::getSensorSubtree(sensorTree);
278 if (!sensorTree)
279 {
280 return false;
281 }
282
283 subtree = *sensorTree;
284 return true;
285 }
286
287 #ifdef FEATURE_HYBRID_SENSORS
288 // Static sensors are listed in sensor-gen.cpp.
289 ipmi::sensor::IdInfoMap::const_iterator
findStaticSensor(const std::string & path)290 findStaticSensor(const std::string& path)
291 {
292 return std::find_if(
293 ipmi::sensor::sensors.begin(), ipmi::sensor::sensors.end(),
294 [&path](const ipmi::sensor::IdInfoMap::value_type& findSensor) {
295 return findSensor.second.sensorPath == path;
296 });
297 }
298 #endif
299
getSensorTypeStringFromPath(const std::string & path)300 std::string getSensorTypeStringFromPath(const std::string& path)
301 {
302 // get sensor type string from path, path is defined as
303 // /xyz/openbmc_project/sensors/<type>/label
304 size_t typeEnd = path.rfind("/");
305 if (typeEnd == std::string::npos)
306 {
307 return path;
308 }
309 size_t typeStart = path.rfind("/", typeEnd - 1);
310 if (typeStart == std::string::npos)
311 {
312 return path;
313 }
314 // Start at the character after the '/'
315 typeStart++;
316 return path.substr(typeStart, typeEnd - typeStart);
317 }
318
getSensorTypeFromPath(const std::string & path)319 uint8_t getSensorTypeFromPath(const std::string& path)
320 {
321 uint8_t sensorType = 0;
322 std::string type = getSensorTypeStringFromPath(path);
323 auto findSensor = sensorTypes.find(type.c_str());
324 if (findSensor != sensorTypes.end())
325 {
326 sensorType =
327 static_cast<uint8_t>(std::get<sensorTypeCodes>(findSensor->second));
328 } // else default 0x0 RESERVED
329
330 return sensorType;
331 }
332
getSensorNumberFromPath(const std::string & path)333 uint16_t getSensorNumberFromPath(const std::string& path)
334 {
335 std::shared_ptr<SensorNumMap> sensorNumMapPtr;
336 details::getSensorNumMap(sensorNumMapPtr);
337 if (!sensorNumMapPtr)
338 {
339 return invalidSensorNumber;
340 }
341
342 try
343 {
344 return sensorNumMapPtr->right.at(path);
345 }
346 catch (const std::out_of_range& e)
347 {
348 return invalidSensorNumber;
349 }
350 }
351
getSensorEventTypeFromPath(const std::string & path)352 uint8_t getSensorEventTypeFromPath(const std::string& path)
353 {
354 uint8_t sensorEventType = 0;
355 std::string type = getSensorTypeStringFromPath(path);
356 auto findSensor = sensorTypes.find(type.c_str());
357 if (findSensor != sensorTypes.end())
358 {
359 sensorEventType = static_cast<uint8_t>(
360 std::get<sensorEventTypeCodes>(findSensor->second));
361 }
362
363 return sensorEventType;
364 }
365
getPathFromSensorNumber(uint16_t sensorNum)366 std::string getPathFromSensorNumber(uint16_t sensorNum)
367 {
368 std::shared_ptr<SensorNumMap> sensorNumMapPtr;
369 details::getSensorNumMap(sensorNumMapPtr);
370 if (!sensorNumMapPtr)
371 {
372 return std::string();
373 }
374
375 try
376 {
377 return sensorNumMapPtr->left.at(sensorNum);
378 }
379 catch (const std::out_of_range& e)
380 {
381 return std::string();
382 }
383 }
384
385 namespace ipmi
386 {
387
388 std::optional<std::map<std::string, std::vector<std::string>>>
getObjectInterfaces(const char * path)389 getObjectInterfaces(const char* path)
390 {
391 std::map<std::string, std::vector<std::string>> interfacesResponse;
392 std::vector<std::string> interfaces;
393 std::shared_ptr<sdbusplus::asio::connection> dbus = getSdBus();
394
395 sdbusplus::message_t getObjectMessage =
396 dbus->new_method_call("xyz.openbmc_project.ObjectMapper",
397 "/xyz/openbmc_project/object_mapper",
398 "xyz.openbmc_project.ObjectMapper", "GetObject");
399 getObjectMessage.append(path, interfaces);
400
401 try
402 {
403 sdbusplus::message_t response = dbus->call(getObjectMessage);
404 response.read(interfacesResponse);
405 }
406 catch (const std::exception& e)
407 {
408 return std::nullopt;
409 }
410
411 return interfacesResponse;
412 }
413
414 std::map<std::string, Value>
getEntityManagerProperties(const char * path,const char * interface)415 getEntityManagerProperties(const char* path, const char* interface)
416 {
417 std::map<std::string, Value> properties;
418 std::shared_ptr<sdbusplus::asio::connection> dbus = getSdBus();
419
420 sdbusplus::message_t getProperties =
421 dbus->new_method_call("xyz.openbmc_project.EntityManager", path,
422 "org.freedesktop.DBus.Properties", "GetAll");
423 getProperties.append(interface);
424
425 try
426 {
427 sdbusplus::message_t response = dbus->call(getProperties);
428 response.read(properties);
429 }
430 catch (const std::exception& e)
431 {
432 lg2::error("Failed to GetAll, path: {PATH}, interface: {INTERFACE}, "
433 "error: {ERROR}",
434 "PATH", path, "INTERFACE", interface, "ERROR", e);
435 }
436
437 return properties;
438 }
439
440 // Fetch the ipmiDecoratorPaths to get the list of dbus objects that
441 // have ipmi decorator to prevent unnessary dbus call to fetch the info
442 std::optional<std::unordered_set<std::string>>&
getIpmiDecoratorPaths(const std::optional<ipmi::Context::ptr> & ctx)443 getIpmiDecoratorPaths(const std::optional<ipmi::Context::ptr>& ctx)
444 {
445 static std::optional<std::unordered_set<std::string>> ipmiDecoratorPaths;
446
447 if (!ctx.has_value() || ipmiDecoratorPaths != std::nullopt)
448 {
449 return ipmiDecoratorPaths;
450 }
451
452 boost::system::error_code ec;
453 std::vector<std::string> paths =
454 (*ctx)->bus->yield_method_call<std::vector<std::string>>(
455 (*ctx)->yield, ec, "xyz.openbmc_project.ObjectMapper",
456 "/xyz/openbmc_project/object_mapper",
457 "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", "/",
458 int32_t(0),
459 std::array<const char*, 1>{
460 "xyz.openbmc_project.Inventory.Decorator.Ipmi"});
461 if (ec)
462 {
463 return ipmiDecoratorPaths;
464 }
465
466 ipmiDecoratorPaths =
467 std::unordered_set<std::string>(paths.begin(), paths.end());
468 return ipmiDecoratorPaths;
469 }
470
getSensorConfigurationInterface(const std::map<std::string,std::vector<std::string>> & sensorInterfacesResponse)471 const std::string* getSensorConfigurationInterface(
472 const std::map<std::string, std::vector<std::string>>&
473 sensorInterfacesResponse)
474 {
475 auto entityManagerService =
476 sensorInterfacesResponse.find("xyz.openbmc_project.EntityManager");
477 if (entityManagerService == sensorInterfacesResponse.end())
478 {
479 return nullptr;
480 }
481
482 // Find the fan configuration first (fans can have multiple configuration
483 // interfaces).
484 for (const auto& entry : entityManagerService->second)
485 {
486 if (entry == "xyz.openbmc_project.Configuration.AspeedFan" ||
487 entry == "xyz.openbmc_project.Configuration.I2CFan" ||
488 entry == "xyz.openbmc_project.Configuration.NuvotonFan")
489 {
490 return &entry;
491 }
492 }
493
494 for (const auto& entry : entityManagerService->second)
495 {
496 if (boost::algorithm::starts_with(entry,
497 "xyz.openbmc_project.Configuration."))
498 {
499 return &entry;
500 }
501 }
502
503 return nullptr;
504 }
505
506 // Follow Association properties for Sensor back to the Board dbus object to
507 // check for an EntityId and EntityInstance property.
updateIpmiFromAssociation(const std::string & path,const std::unordered_set<std::string> & ipmiDecoratorPaths,const DbusInterfaceMap & sensorMap,uint8_t & entityId,uint8_t & entityInstance)508 void updateIpmiFromAssociation(
509 const std::string& path,
510 const std::unordered_set<std::string>& ipmiDecoratorPaths,
511 const DbusInterfaceMap& sensorMap, uint8_t& entityId,
512 uint8_t& entityInstance)
513 {
514 namespace fs = std::filesystem;
515
516 auto sensorAssociationObject =
517 sensorMap.find("xyz.openbmc_project.Association.Definitions");
518 if (sensorAssociationObject == sensorMap.end())
519 {
520 if constexpr (debug)
521 {
522 std::fprintf(stderr, "path=%s, no association interface found\n",
523 path.c_str());
524 }
525
526 return;
527 }
528
529 auto associationObject =
530 sensorAssociationObject->second.find("Associations");
531 if (associationObject == sensorAssociationObject->second.end())
532 {
533 if constexpr (debug)
534 {
535 std::fprintf(stderr, "path=%s, no association records found\n",
536 path.c_str());
537 }
538
539 return;
540 }
541
542 std::vector<Association> associationValues =
543 std::get<std::vector<Association>>(associationObject->second);
544
545 // loop through the Associations looking for the right one:
546 for (const auto& entry : associationValues)
547 {
548 // forward, reverse, endpoint
549 const std::string& forward = std::get<0>(entry);
550 const std::string& reverse = std::get<1>(entry);
551 const std::string& endpoint = std::get<2>(entry);
552
553 // We only currently concern ourselves with chassis+all_sensors.
554 if (!(forward == "chassis" && reverse == "all_sensors"))
555 {
556 continue;
557 }
558
559 // the endpoint is the board entry provided by
560 // Entity-Manager. so let's grab its properties if it has
561 // the right interface.
562
563 // just try grabbing the properties first.
564 ipmi::PropertyMap::iterator entityIdProp;
565 ipmi::PropertyMap::iterator entityInstanceProp;
566 if (ipmiDecoratorPaths.contains(endpoint))
567 {
568 std::map<std::string, Value> ipmiProperties =
569 getEntityManagerProperties(
570 endpoint.c_str(),
571 "xyz.openbmc_project.Inventory.Decorator.Ipmi");
572
573 entityIdProp = ipmiProperties.find("EntityId");
574 entityInstanceProp = ipmiProperties.find("EntityInstance");
575 if (entityIdProp != ipmiProperties.end())
576 {
577 entityId = static_cast<uint8_t>(
578 std::get<uint64_t>(entityIdProp->second));
579 }
580 if (entityInstanceProp != ipmiProperties.end())
581 {
582 entityInstance = static_cast<uint8_t>(
583 std::get<uint64_t>(entityInstanceProp->second));
584 }
585 }
586
587 // Now check the entity-manager entry for this sensor to see
588 // if it has its own value and use that instead.
589 //
590 // In theory, checking this first saves us from checking
591 // both, except in most use-cases identified, there won't be
592 // a per sensor override, so we need to always check both.
593 std::string sensorNameFromPath = fs::path(path).filename();
594
595 std::string sensorConfigPath = endpoint + "/" + sensorNameFromPath;
596
597 // Download the interfaces for the sensor from
598 // Entity-Manager to find the name of the configuration
599 // interface.
600 std::optional<std::map<std::string, std::vector<std::string>>>
601 sensorInterfacesResponseOpt =
602 getObjectInterfaces(sensorConfigPath.c_str());
603
604 if (!sensorInterfacesResponseOpt.has_value())
605 {
606 lg2::debug("Failed to GetObject, path: {PATH}", "PATH",
607 sensorConfigPath);
608 continue;
609 }
610
611 const std::string* configurationInterface =
612 getSensorConfigurationInterface(
613 sensorInterfacesResponseOpt.value());
614
615 // If there are multi association path settings and only one path exist,
616 // we need to continue if cannot find configuration interface for this
617 // sensor.
618 if (!configurationInterface)
619 {
620 continue;
621 }
622
623 // We found a configuration interface.
624 std::map<std::string, Value> configurationProperties =
625 getEntityManagerProperties(sensorConfigPath.c_str(),
626 configurationInterface->c_str());
627
628 entityIdProp = configurationProperties.find("EntityId");
629 entityInstanceProp = configurationProperties.find("EntityInstance");
630 if (entityIdProp != configurationProperties.end())
631 {
632 entityId =
633 static_cast<uint8_t>(std::get<uint64_t>(entityIdProp->second));
634 }
635 if (entityInstanceProp != configurationProperties.end())
636 {
637 entityInstance = static_cast<uint8_t>(
638 std::get<uint64_t>(entityInstanceProp->second));
639 }
640
641 // stop searching Association records.
642 break;
643 } // end for Association vectors.
644
645 if constexpr (debug)
646 {
647 std::fprintf(stderr, "path=%s, entityId=%d, entityInstance=%d\n",
648 path.c_str(), entityId, entityInstance);
649 }
650 }
651
652 } // namespace ipmi
653