xref: /openbmc/dbus-sensors/src/FanMain.cpp (revision 2aaf7175)
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