1 /*
2 // Copyright (c) 2017 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 "PwmSensor.hpp"
18 #include "TachSensor.hpp"
19 #include "Thresholds.hpp"
20 #include "Utils.hpp"
21 #include "VariantVisitors.hpp"
22
23 #include <boost/algorithm/string/replace.hpp>
24 #include <boost/asio/error.hpp>
25 #include <boost/asio/io_context.hpp>
26 #include <boost/asio/post.hpp>
27 #include <boost/asio/steady_timer.hpp>
28 #include <boost/container/flat_map.hpp>
29 #include <boost/container/flat_set.hpp>
30 #include <sdbusplus/asio/connection.hpp>
31 #include <sdbusplus/asio/object_server.hpp>
32 #include <sdbusplus/bus.hpp>
33 #include <sdbusplus/bus/match.hpp>
34 #include <sdbusplus/message.hpp>
35
36 #include <array>
37 #include <chrono>
38 #include <cstddef>
39 #include <cstdint>
40 #include <filesystem>
41 #include <fstream>
42 #include <functional>
43 #include <ios>
44 #include <iostream>
45 #include <map>
46 #include <memory>
47 #include <optional>
48 #include <regex>
49 #include <string>
50 #include <system_error>
51 #include <utility>
52 #include <variant>
53 #include <vector>
54
55 namespace fs = std::filesystem;
56
57 // The following two structures need to be consistent
58 static auto sensorTypes{std::to_array<const char*>(
59 {"AspeedFan", "I2CFan", "NuvotonFan", "HPEFan"})};
60
61 enum FanTypes
62 {
63 aspeed = 0,
64 i2c,
65 nuvoton,
66 hpe,
67 max,
68 };
69
70 static_assert(std::tuple_size<decltype(sensorTypes)>::value == FanTypes::max,
71 "sensorTypes element number is not equal to FanTypes number");
72
73 constexpr const char* redundancyConfiguration =
74 "xyz.openbmc_project.Configuration.FanRedundancy";
75 static std::regex inputRegex(R"(fan(\d+)_input)");
76
77 // todo: power supply fan redundancy
78 std::optional<RedundancySensor> systemRedundancy;
79
80 static const std::map<std::string, FanTypes> compatibleFanTypes = {
81 {"aspeed,ast2400-pwm-tacho", FanTypes::aspeed},
82 {"aspeed,ast2500-pwm-tacho", FanTypes::aspeed},
83 {"aspeed,ast2600-pwm-tach", FanTypes::aspeed},
84 {"nuvoton,npcm750-pwm-fan", FanTypes::nuvoton},
85 {"nuvoton,npcm845-pwm-fan", FanTypes::nuvoton},
86 {"hpe,gxp-fan-ctrl", FanTypes::hpe}
87 // add compatible string here for new fan type
88 };
89
getFanType(const fs::path & parentPath)90 FanTypes getFanType(const fs::path& parentPath)
91 {
92 fs::path linkPath = parentPath / "of_node";
93 if (!fs::exists(linkPath))
94 {
95 return FanTypes::i2c;
96 }
97
98 std::string canonical = fs::canonical(linkPath);
99 std::string compatiblePath = canonical + "/compatible";
100 std::ifstream compatibleStream(compatiblePath);
101
102 if (!compatibleStream)
103 {
104 std::cerr << "Error opening " << compatiblePath << "\n";
105 return FanTypes::i2c;
106 }
107
108 std::string compatibleString;
109 while (std::getline(compatibleStream, compatibleString))
110 {
111 compatibleString.pop_back(); // trim EOL before comparisons
112
113 std::map<std::string, FanTypes>::const_iterator compatibleIterator =
114 compatibleFanTypes.find(compatibleString);
115
116 if (compatibleIterator != compatibleFanTypes.end())
117 {
118 return compatibleIterator->second;
119 }
120 }
121
122 return FanTypes::i2c;
123 }
enablePwm(const fs::path & filePath)124 void enablePwm(const fs::path& filePath)
125 {
126 std::fstream enableFile(filePath, std::ios::in | std::ios::out);
127 if (!enableFile.good())
128 {
129 std::cerr << "Error read/write " << filePath << "\n";
130 return;
131 }
132
133 std::string regulateMode;
134 std::getline(enableFile, regulateMode);
135 if (regulateMode == "0")
136 {
137 enableFile << 1;
138 }
139 }
findPwmfanPath(unsigned int configPwmfanIndex,fs::path & pwmPath)140 bool findPwmfanPath(unsigned int configPwmfanIndex, fs::path& pwmPath)
141 {
142 /* Search PWM since pwm-fan had separated
143 * PWM from tach directory and 1 channel only*/
144 std::vector<fs::path> pwmfanPaths;
145 std::string pwnfanDevName("pwm-fan");
146
147 pwnfanDevName += std::to_string(configPwmfanIndex);
148
149 if (!findFiles(fs::path("/sys/class/hwmon"), R"(pwm\d+)", pwmfanPaths))
150 {
151 std::cerr << "No PWMs are found!\n";
152 return false;
153 }
154 for (const auto& path : pwmfanPaths)
155 {
156 std::error_code ec;
157 fs::path link = fs::read_symlink(path.parent_path() / "device", ec);
158
159 if (ec)
160 {
161 std::cerr << "read_symlink() failed: " << ec.message() << " ("
162 << ec.value() << ")\n";
163 continue;
164 }
165
166 if (link.filename().string() == pwnfanDevName)
167 {
168 pwmPath = path;
169 return true;
170 }
171 }
172 return false;
173 }
findPwmPath(const fs::path & directory,unsigned int pwm,fs::path & pwmPath)174 bool findPwmPath(const fs::path& directory, unsigned int pwm, fs::path& pwmPath)
175 {
176 std::error_code ec;
177
178 /* Assuming PWM file is appeared in the same directory as fanX_input */
179 auto path = directory / ("pwm" + std::to_string(pwm + 1));
180 bool exists = fs::exists(path, ec);
181
182 if (ec || !exists)
183 {
184 /* PWM file not exist or error happened */
185 if (ec)
186 {
187 std::cerr << "exists() failed: " << ec.message() << " ("
188 << ec.value() << ")\n";
189 }
190 /* try search form pwm-fanX directory */
191 return findPwmfanPath(pwm, pwmPath);
192 }
193
194 pwmPath = path;
195 return true;
196 }
197
198 // The argument to this function should be the fanN_input file that we want to
199 // enable. The function will locate the corresponding fanN_enable file if it
200 // exists. Note that some drivers don't provide this file if the sensors are
201 // always enabled.
enableFanInput(const fs::path & fanInputPath)202 void enableFanInput(const fs::path& fanInputPath)
203 {
204 std::error_code ec;
205 std::string path(fanInputPath.string());
206 boost::replace_last(path, "input", "enable");
207
208 bool exists = fs::exists(path, ec);
209 if (ec || !exists)
210 {
211 return;
212 }
213
214 std::fstream enableFile(path, std::ios::out);
215 if (!enableFile.good())
216 {
217 return;
218 }
219 enableFile << 1;
220 }
221
createRedundancySensor(const boost::container::flat_map<std::string,std::shared_ptr<TachSensor>> & sensors,const std::shared_ptr<sdbusplus::asio::connection> & conn,sdbusplus::asio::object_server & objectServer)222 void createRedundancySensor(
223 const boost::container::flat_map<std::string, std::shared_ptr<TachSensor>>&
224 sensors,
225 const std::shared_ptr<sdbusplus::asio::connection>& conn,
226 sdbusplus::asio::object_server& objectServer)
227 {
228 conn->async_method_call(
229 [&objectServer, &sensors](boost::system::error_code& ec,
230 const ManagedObjectType& managedObj) {
231 if (ec)
232 {
233 std::cerr << "Error calling entity manager \n";
234 return;
235 }
236 for (const auto& [path, interfaces] : managedObj)
237 {
238 for (const auto& [intf, cfg] : interfaces)
239 {
240 if (intf == redundancyConfiguration)
241 {
242 // currently only support one
243 auto findCount = cfg.find("AllowedFailures");
244 if (findCount == cfg.end())
245 {
246 std::cerr << "Malformed redundancy record \n";
247 return;
248 }
249 std::vector<std::string> sensorList;
250
251 for (const auto& [name, sensor] : sensors)
252 {
253 sensorList.push_back(
254 "/xyz/openbmc_project/sensors/fan_tach/" +
255 sensor->name);
256 }
257 systemRedundancy.reset();
258 systemRedundancy.emplace(RedundancySensor(
259 std::get<uint64_t>(findCount->second), sensorList,
260 objectServer, path));
261
262 return;
263 }
264 }
265 }
266 },
267 "xyz.openbmc_project.EntityManager", "/xyz/openbmc_project/inventory",
268 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
269 }
270
createSensors(boost::asio::io_context & io,sdbusplus::asio::object_server & objectServer,boost::container::flat_map<std::string,std::shared_ptr<TachSensor>> & tachSensors,boost::container::flat_map<std::string,std::unique_ptr<PwmSensor>> & pwmSensors,boost::container::flat_map<std::string,std::weak_ptr<PresenceSensor>> & presenceSensors,std::shared_ptr<sdbusplus::asio::connection> & dbusConnection,const std::shared_ptr<boost::container::flat_set<std::string>> & sensorsChanged,size_t retries=0)271 void createSensors(
272 boost::asio::io_context& io, sdbusplus::asio::object_server& objectServer,
273 boost::container::flat_map<std::string, std::shared_ptr<TachSensor>>&
274 tachSensors,
275 boost::container::flat_map<std::string, std::unique_ptr<PwmSensor>>&
276 pwmSensors,
277 boost::container::flat_map<std::string, std::weak_ptr<PresenceSensor>>&
278 presenceSensors,
279 std::shared_ptr<sdbusplus::asio::connection>& dbusConnection,
280 const std::shared_ptr<boost::container::flat_set<std::string>>&
281 sensorsChanged,
282 size_t retries = 0)
283 {
284 auto getter = std::make_shared<GetSensorConfiguration>(
285 dbusConnection,
286 [&io, &objectServer, &tachSensors, &pwmSensors, &presenceSensors,
287 &dbusConnection,
288 sensorsChanged](const ManagedObjectType& sensorConfigurations) {
289 bool firstScan = sensorsChanged == nullptr;
290 std::vector<fs::path> paths;
291 if (!findFiles(fs::path("/sys/class/hwmon"), R"(fan\d+_input)",
292 paths))
293 {
294 std::cerr << "No fan sensors in system\n";
295 return;
296 }
297
298 // iterate through all found fan sensors, and try to match them with
299 // configuration
300 for (const auto& path : paths)
301 {
302 std::smatch match;
303 std::string pathStr = path.string();
304
305 std::regex_search(pathStr, match, inputRegex);
306 std::string indexStr = *(match.begin() + 1);
307
308 fs::path directory = path.parent_path();
309 FanTypes fanType = getFanType(directory);
310 std::string cfgIntf = configInterfaceName(sensorTypes[fanType]);
311
312 // convert to 0 based
313 size_t index = std::stoul(indexStr) - 1;
314
315 const char* baseType = nullptr;
316 const SensorData* sensorData = nullptr;
317 const std::string* interfacePath = nullptr;
318 const SensorBaseConfiguration* baseConfiguration = nullptr;
319 for (const auto& [path, cfgData] : sensorConfigurations)
320 {
321 // find the base of the configuration to see if indexes
322 // match
323 auto sensorBaseFind = cfgData.find(cfgIntf);
324 if (sensorBaseFind == cfgData.end())
325 {
326 continue;
327 }
328
329 baseConfiguration = &(*sensorBaseFind);
330 interfacePath = &path.str;
331 baseType = sensorTypes[fanType];
332
333 auto findIndex = baseConfiguration->second.find("Index");
334 if (findIndex == baseConfiguration->second.end())
335 {
336 std::cerr
337 << baseConfiguration->first << " missing index\n";
338 continue;
339 }
340 unsigned int configIndex = std::visit(
341 VariantToUnsignedIntVisitor(), findIndex->second);
342 if (configIndex != index)
343 {
344 continue;
345 }
346 if (fanType == FanTypes::aspeed ||
347 fanType == FanTypes::nuvoton ||
348 fanType == FanTypes::hpe)
349 {
350 // there will be only 1 aspeed or nuvoton or hpe sensor
351 // object in sysfs, we found the fan
352 sensorData = &cfgData;
353 break;
354 }
355 if (fanType == FanTypes::i2c)
356 {
357 std::string deviceName =
358 fs::read_symlink(directory / "device").filename();
359
360 size_t bus = 0;
361 size_t addr = 0;
362 if (!getDeviceBusAddr(deviceName, bus, addr))
363 {
364 continue;
365 }
366
367 auto findBus = baseConfiguration->second.find("Bus");
368 auto findAddress =
369 baseConfiguration->second.find("Address");
370 if (findBus == baseConfiguration->second.end() ||
371 findAddress == baseConfiguration->second.end())
372 {
373 std::cerr << baseConfiguration->first
374 << " missing bus or address\n";
375 continue;
376 }
377 unsigned int configBus = std::visit(
378 VariantToUnsignedIntVisitor(), findBus->second);
379 unsigned int configAddress = std::visit(
380 VariantToUnsignedIntVisitor(), findAddress->second);
381
382 if (configBus == bus && configAddress == addr)
383 {
384 sensorData = &cfgData;
385 break;
386 }
387 }
388 }
389 if (sensorData == nullptr)
390 {
391 std::cerr
392 << "failed to find match for " << path.string() << "\n";
393 continue;
394 }
395
396 auto findSensorName = baseConfiguration->second.find("Name");
397
398 if (findSensorName == baseConfiguration->second.end())
399 {
400 std::cerr << "could not determine configuration name for "
401 << path.string() << "\n";
402 continue;
403 }
404 std::string sensorName =
405 std::get<std::string>(findSensorName->second);
406
407 // on rescans, only update sensors we were signaled by
408 auto findSensor = tachSensors.find(sensorName);
409 if (!firstScan && findSensor != tachSensors.end())
410 {
411 bool found = false;
412 for (auto it = sensorsChanged->begin();
413 it != sensorsChanged->end(); it++)
414 {
415 if (it->ends_with(findSensor->second->name))
416 {
417 sensorsChanged->erase(it);
418 findSensor->second = nullptr;
419 found = true;
420 break;
421 }
422 }
423 if (!found)
424 {
425 continue;
426 }
427 }
428 std::vector<thresholds::Threshold> sensorThresholds;
429 if (!parseThresholdsFromConfig(*sensorData, sensorThresholds))
430 {
431 std::cerr << "error populating thresholds for "
432 << sensorName << "\n";
433 }
434
435 auto presenceConfig =
436 sensorData->find(cfgIntf + std::string(".Presence"));
437
438 std::shared_ptr<PresenceSensor> presenceSensor(nullptr);
439
440 // presence sensors are optional
441 if (presenceConfig != sensorData->end())
442 {
443 auto findPolarity = presenceConfig->second.find("Polarity");
444 auto findPinName = presenceConfig->second.find("PinName");
445
446 if (findPinName == presenceConfig->second.end() ||
447 findPolarity == presenceConfig->second.end())
448 {
449 std::cerr << "Malformed Presence Configuration\n";
450 }
451 else
452 {
453 bool inverted = std::get<std::string>(
454 findPolarity->second) == "Low";
455 const auto* pinName =
456 std::get_if<std::string>(&findPinName->second);
457
458 if (pinName != nullptr)
459 {
460 auto findPresenceSensor =
461 presenceSensors.find(*pinName);
462 if (findPresenceSensor != presenceSensors.end())
463 {
464 auto p = findPresenceSensor->second.lock();
465 if (p)
466 {
467 presenceSensor = p;
468 }
469 }
470 if (!presenceSensor)
471 {
472 presenceSensor =
473 std::make_shared<PresenceSensor>(
474 *pinName, inverted, io, sensorName);
475 presenceSensors[*pinName] = presenceSensor;
476 }
477 }
478 else
479 {
480 std::cerr
481 << "Malformed Presence pinName for sensor "
482 << sensorName << " \n";
483 }
484 }
485 }
486 std::optional<RedundancySensor>* redundancy = nullptr;
487 if (fanType == FanTypes::aspeed)
488 {
489 redundancy = &systemRedundancy;
490 }
491
492 PowerState powerState =
493 getPowerState(baseConfiguration->second);
494
495 constexpr double defaultMaxReading = 25000;
496 constexpr double defaultMinReading = 0;
497 std::pair<double, double> limits =
498 std::make_pair(defaultMinReading, defaultMaxReading);
499
500 auto connector =
501 sensorData->find(cfgIntf + std::string(".Connector"));
502
503 std::optional<std::string> led;
504 std::string pwmName;
505 fs::path pwmPath;
506
507 // The Mutable parameter is optional, defaulting to false
508 bool isValueMutable = false;
509 if (connector != sensorData->end())
510 {
511 auto findPwm = connector->second.find("Pwm");
512 if (findPwm != connector->second.end())
513 {
514 size_t pwm = std::visit(VariantToUnsignedIntVisitor(),
515 findPwm->second);
516 if (!findPwmPath(directory, pwm, pwmPath))
517 {
518 std::cerr << "Connector for " << sensorName
519 << " no pwm channel found!\n";
520 continue;
521 }
522
523 fs::path pwmEnableFile =
524 "pwm" + std::to_string(pwm + 1) + "_enable";
525 fs::path enablePath =
526 pwmPath.parent_path() / pwmEnableFile;
527 enablePwm(enablePath);
528
529 /* use pwm name override if found in configuration else
530 * use default */
531 auto findOverride = connector->second.find("PwmName");
532 if (findOverride != connector->second.end())
533 {
534 pwmName = std::visit(VariantToStringVisitor(),
535 findOverride->second);
536 }
537 else
538 {
539 pwmName = "Pwm_" + std::to_string(pwm + 1);
540 }
541
542 // Check PWM sensor mutability
543 auto findMutable = connector->second.find("Mutable");
544 if (findMutable != connector->second.end())
545 {
546 const auto* ptrMutable =
547 std::get_if<bool>(&(findMutable->second));
548 if (ptrMutable != nullptr)
549 {
550 isValueMutable = *ptrMutable;
551 }
552 }
553 }
554 else
555 {
556 std::cerr << "Connector for " << sensorName
557 << " missing pwm!\n";
558 }
559
560 auto findLED = connector->second.find("LED");
561 if (findLED != connector->second.end())
562 {
563 const auto* ledName =
564 std::get_if<std::string>(&(findLED->second));
565 if (ledName == nullptr)
566 {
567 std::cerr << "Wrong format for LED of "
568 << sensorName << "\n";
569 }
570 else
571 {
572 led = *ledName;
573 }
574 }
575 }
576
577 findLimits(limits, baseConfiguration);
578
579 enableFanInput(path);
580
581 auto& tachSensor = tachSensors[sensorName];
582 tachSensor = nullptr;
583 tachSensor = std::make_shared<TachSensor>(
584 path.string(), baseType, objectServer, dbusConnection,
585 presenceSensor, redundancy, io, sensorName,
586 std::move(sensorThresholds), *interfacePath, limits,
587 powerState, led);
588 tachSensor->setupRead();
589
590 if (!pwmPath.empty() && fs::exists(pwmPath) &&
591 (pwmSensors.count(pwmPath) == 0U))
592 {
593 pwmSensors[pwmPath] = std::make_unique<PwmSensor>(
594 pwmName, pwmPath, dbusConnection, objectServer,
595 *interfacePath, "Fan", isValueMutable);
596 }
597 }
598
599 createRedundancySensor(tachSensors, dbusConnection, objectServer);
600 });
601 getter->getConfiguration(
602 std::vector<std::string>{sensorTypes.begin(), sensorTypes.end()},
603 retries);
604 }
605
main()606 int main()
607 {
608 boost::asio::io_context io;
609 auto systemBus = std::make_shared<sdbusplus::asio::connection>(io);
610 sdbusplus::asio::object_server objectServer(systemBus, true);
611
612 objectServer.add_manager("/xyz/openbmc_project/sensors");
613 objectServer.add_manager("/xyz/openbmc_project/control");
614 objectServer.add_manager("/xyz/openbmc_project/inventory");
615 systemBus->request_name("xyz.openbmc_project.FanSensor");
616 boost::container::flat_map<std::string, std::shared_ptr<TachSensor>>
617 tachSensors;
618 boost::container::flat_map<std::string, std::unique_ptr<PwmSensor>>
619 pwmSensors;
620 boost::container::flat_map<std::string, std::weak_ptr<PresenceSensor>>
621 presenceSensors;
622 auto sensorsChanged =
623 std::make_shared<boost::container::flat_set<std::string>>();
624
625 boost::asio::post(io, [&]() {
626 createSensors(io, objectServer, tachSensors, pwmSensors,
627 presenceSensors, systemBus, nullptr);
628 });
629
630 boost::asio::steady_timer filterTimer(io);
631 std::function<void(sdbusplus::message_t&)> eventHandler =
632 [&](sdbusplus::message_t& message) {
633 if (message.is_method_error())
634 {
635 std::cerr << "callback method error\n";
636 return;
637 }
638 sensorsChanged->insert(message.get_path());
639 // this implicitly cancels the timer
640 filterTimer.expires_after(std::chrono::seconds(1));
641
642 filterTimer.async_wait([&](const boost::system::error_code& ec) {
643 if (ec == boost::asio::error::operation_aborted)
644 {
645 /* we were canceled*/
646 return;
647 }
648 if (ec)
649 {
650 std::cerr << "timer error\n";
651 return;
652 }
653 createSensors(io, objectServer, tachSensors, pwmSensors,
654 presenceSensors, systemBus, sensorsChanged, 5);
655 });
656 };
657
658 std::vector<std::unique_ptr<sdbusplus::bus::match_t>> matches =
659 setupPropertiesChangedMatches(*systemBus, sensorTypes, eventHandler);
660
661 // redundancy sensor
662 std::function<void(sdbusplus::message_t&)> redundancyHandler =
663 [&tachSensors, &systemBus, &objectServer](sdbusplus::message_t&) {
664 createRedundancySensor(tachSensors, systemBus, objectServer);
665 };
666 auto match = std::make_unique<sdbusplus::bus::match_t>(
667 static_cast<sdbusplus::bus_t&>(*systemBus),
668 "type='signal',member='PropertiesChanged',path_namespace='" +
669 std::string(inventoryPath) + "',arg0namespace='" +
670 redundancyConfiguration + "'",
671 std::move(redundancyHandler));
672 matches.emplace_back(std::move(match));
673
674 setupManufacturingModeMatch(*systemBus);
675 io.run();
676 return 0;
677 }
678