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