xref: /openbmc/dbus-sensors/src/FanMain.cpp (revision 40c4d685)
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(
259                         RedundancySensor(std::get<uint64_t>(findCount->second),
260                                          sensorList, 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,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     std::shared_ptr<sdbusplus::asio::connection>& dbusConnection,
278     const std::shared_ptr<boost::container::flat_set<std::string>>&
279         sensorsChanged,
280     size_t retries = 0)
281 {
282     auto getter = std::make_shared<GetSensorConfiguration>(
283         dbusConnection,
284         [&io, &objectServer, &tachSensors, &pwmSensors, &dbusConnection,
285          sensorsChanged](const ManagedObjectType& sensorConfigurations) {
286         bool firstScan = sensorsChanged == nullptr;
287         std::vector<fs::path> paths;
288         if (!findFiles(fs::path("/sys/class/hwmon"), R"(fan\d+_input)", paths))
289         {
290             std::cerr << "No fan sensors in system\n";
291             return;
292         }
293 
294         // iterate through all found fan sensors, and try to match them with
295         // configuration
296         for (const auto& path : paths)
297         {
298             std::smatch match;
299             std::string pathStr = path.string();
300 
301             std::regex_search(pathStr, match, inputRegex);
302             std::string indexStr = *(match.begin() + 1);
303 
304             fs::path directory = path.parent_path();
305             FanTypes fanType = getFanType(directory);
306             std::string cfgIntf = configInterfaceName(sensorTypes[fanType]);
307 
308             // convert to 0 based
309             size_t index = std::stoul(indexStr) - 1;
310 
311             const char* baseType = nullptr;
312             const SensorData* sensorData = nullptr;
313             const std::string* interfacePath = nullptr;
314             const SensorBaseConfiguration* baseConfiguration = nullptr;
315             for (const auto& [path, cfgData] : sensorConfigurations)
316             {
317                 // find the base of the configuration to see if indexes
318                 // match
319                 auto sensorBaseFind = cfgData.find(cfgIntf);
320                 if (sensorBaseFind == cfgData.end())
321                 {
322                     continue;
323                 }
324 
325                 baseConfiguration = &(*sensorBaseFind);
326                 interfacePath = &path.str;
327                 baseType = sensorTypes[fanType];
328 
329                 auto findIndex = baseConfiguration->second.find("Index");
330                 if (findIndex == baseConfiguration->second.end())
331                 {
332                     std::cerr << baseConfiguration->first << " missing index\n";
333                     continue;
334                 }
335                 unsigned int configIndex = std::visit(
336                     VariantToUnsignedIntVisitor(), findIndex->second);
337                 if (configIndex != index)
338                 {
339                     continue;
340                 }
341                 if (fanType == FanTypes::aspeed ||
342                     fanType == FanTypes::nuvoton || fanType == FanTypes::hpe)
343                 {
344                     // there will be only 1 aspeed or nuvoton or hpe sensor
345                     // object in sysfs, we found the fan
346                     sensorData = &cfgData;
347                     break;
348                 }
349                 if (fanType == FanTypes::i2c)
350                 {
351                     std::string deviceName =
352                         fs::read_symlink(directory / "device").filename();
353 
354                     size_t bus = 0;
355                     size_t addr = 0;
356                     if (!getDeviceBusAddr(deviceName, bus, addr))
357                     {
358                         continue;
359                     }
360 
361                     auto findBus = baseConfiguration->second.find("Bus");
362                     auto findAddress =
363                         baseConfiguration->second.find("Address");
364                     if (findBus == baseConfiguration->second.end() ||
365                         findAddress == baseConfiguration->second.end())
366                     {
367                         std::cerr << baseConfiguration->first
368                                   << " missing bus or address\n";
369                         continue;
370                     }
371                     unsigned int configBus = std::visit(
372                         VariantToUnsignedIntVisitor(), findBus->second);
373                     unsigned int configAddress = std::visit(
374                         VariantToUnsignedIntVisitor(), findAddress->second);
375 
376                     if (configBus == bus && configAddress == addr)
377                     {
378                         sensorData = &cfgData;
379                         break;
380                     }
381                 }
382             }
383             if (sensorData == nullptr)
384             {
385                 std::cerr << "failed to find match for " << path.string()
386                           << "\n";
387                 continue;
388             }
389 
390             auto findSensorName = baseConfiguration->second.find("Name");
391 
392             if (findSensorName == baseConfiguration->second.end())
393             {
394                 std::cerr << "could not determine configuration name for "
395                           << path.string() << "\n";
396                 continue;
397             }
398             std::string sensorName =
399                 std::get<std::string>(findSensorName->second);
400 
401             // on rescans, only update sensors we were signaled by
402             auto findSensor = tachSensors.find(sensorName);
403             if (!firstScan && findSensor != tachSensors.end())
404             {
405                 bool found = false;
406                 for (auto it = sensorsChanged->begin();
407                      it != sensorsChanged->end(); it++)
408                 {
409                     if (it->ends_with(findSensor->second->name))
410                     {
411                         sensorsChanged->erase(it);
412                         findSensor->second = nullptr;
413                         found = true;
414                         break;
415                     }
416                 }
417                 if (!found)
418                 {
419                     continue;
420                 }
421             }
422             std::vector<thresholds::Threshold> sensorThresholds;
423             if (!parseThresholdsFromConfig(*sensorData, sensorThresholds))
424             {
425                 std::cerr << "error populating thresholds for " << sensorName
426                           << "\n";
427             }
428 
429             auto presenceConfig =
430                 sensorData->find(cfgIntf + std::string(".Presence"));
431 
432             std::unique_ptr<PresenceSensor> presenceSensor(nullptr);
433 
434             // presence sensors are optional
435             if (presenceConfig != sensorData->end())
436             {
437                 auto findPolarity = presenceConfig->second.find("Polarity");
438                 auto findPinName = presenceConfig->second.find("PinName");
439 
440                 if (findPinName == presenceConfig->second.end() ||
441                     findPolarity == presenceConfig->second.end())
442                 {
443                     std::cerr << "Malformed Presence Configuration\n";
444                 }
445                 else
446                 {
447                     bool inverted =
448                         std::get<std::string>(findPolarity->second) == "Low";
449                     if (const auto* pinName =
450                             std::get_if<std::string>(&findPinName->second))
451                     {
452                         presenceSensor = std::make_unique<PresenceSensor>(
453                             *pinName, inverted, io, sensorName);
454                     }
455                     else
456                     {
457                         std::cerr << "Malformed Presence pinName for sensor "
458                                   << sensorName << " \n";
459                     }
460                 }
461             }
462             std::optional<RedundancySensor>* redundancy = nullptr;
463             if (fanType == FanTypes::aspeed)
464             {
465                 redundancy = &systemRedundancy;
466             }
467 
468             PowerState powerState = getPowerState(baseConfiguration->second);
469 
470             constexpr double defaultMaxReading = 25000;
471             constexpr double defaultMinReading = 0;
472             std::pair<double, double> limits =
473                 std::make_pair(defaultMinReading, defaultMaxReading);
474 
475             auto connector =
476                 sensorData->find(cfgIntf + std::string(".Connector"));
477 
478             std::optional<std::string> led;
479             std::string pwmName;
480             fs::path pwmPath;
481 
482             // The Mutable parameter is optional, defaulting to false
483             bool isValueMutable = false;
484             if (connector != sensorData->end())
485             {
486                 auto findPwm = connector->second.find("Pwm");
487                 if (findPwm != connector->second.end())
488                 {
489                     size_t pwm = std::visit(VariantToUnsignedIntVisitor(),
490                                             findPwm->second);
491                     if (!findPwmPath(directory, pwm, pwmPath))
492                     {
493                         std::cerr << "Connector for " << sensorName
494                                   << " no pwm channel found!\n";
495                         continue;
496                     }
497 
498                     fs::path pwmEnableFile = "pwm" + std::to_string(pwm + 1) +
499                                              "_enable";
500                     fs::path enablePath = pwmPath.parent_path() / pwmEnableFile;
501                     enablePwm(enablePath);
502 
503                     /* use pwm name override if found in configuration else
504                      * use default */
505                     auto findOverride = connector->second.find("PwmName");
506                     if (findOverride != connector->second.end())
507                     {
508                         pwmName = std::visit(VariantToStringVisitor(),
509                                              findOverride->second);
510                     }
511                     else
512                     {
513                         pwmName = "Pwm_" + std::to_string(pwm + 1);
514                     }
515 
516                     // Check PWM sensor mutability
517                     auto findMutable = connector->second.find("Mutable");
518                     if (findMutable != connector->second.end())
519                     {
520                         const auto* ptrMutable =
521                             std::get_if<bool>(&(findMutable->second));
522                         if (ptrMutable != nullptr)
523                         {
524                             isValueMutable = *ptrMutable;
525                         }
526                     }
527                 }
528                 else
529                 {
530                     std::cerr << "Connector for " << sensorName
531                               << " missing pwm!\n";
532                 }
533 
534                 auto findLED = connector->second.find("LED");
535                 if (findLED != connector->second.end())
536                 {
537                     const auto* ledName =
538                         std::get_if<std::string>(&(findLED->second));
539                     if (ledName == nullptr)
540                     {
541                         std::cerr << "Wrong format for LED of " << sensorName
542                                   << "\n";
543                     }
544                     else
545                     {
546                         led = *ledName;
547                     }
548                 }
549             }
550 
551             findLimits(limits, baseConfiguration);
552 
553             enableFanInput(path);
554 
555             auto& tachSensor = tachSensors[sensorName];
556             tachSensor = nullptr;
557             tachSensor = std::make_shared<TachSensor>(
558                 path.string(), baseType, objectServer, dbusConnection,
559                 std::move(presenceSensor), redundancy, io, sensorName,
560                 std::move(sensorThresholds), *interfacePath, limits, powerState,
561                 led);
562             tachSensor->setupRead();
563 
564             if (!pwmPath.empty() && fs::exists(pwmPath) &&
565                 (pwmSensors.count(pwmPath) == 0U))
566             {
567                 pwmSensors[pwmPath] = std::make_unique<PwmSensor>(
568                     pwmName, pwmPath, dbusConnection, objectServer,
569                     *interfacePath, "Fan", isValueMutable);
570             }
571         }
572 
573         createRedundancySensor(tachSensors, dbusConnection, objectServer);
574     });
575     getter->getConfiguration(
576         std::vector<std::string>{sensorTypes.begin(), sensorTypes.end()},
577         retries);
578 }
579 
main()580 int main()
581 {
582     boost::asio::io_context io;
583     auto systemBus = std::make_shared<sdbusplus::asio::connection>(io);
584     sdbusplus::asio::object_server objectServer(systemBus, true);
585 
586     objectServer.add_manager("/xyz/openbmc_project/sensors");
587     objectServer.add_manager("/xyz/openbmc_project/control");
588     objectServer.add_manager("/xyz/openbmc_project/inventory");
589     systemBus->request_name("xyz.openbmc_project.FanSensor");
590     boost::container::flat_map<std::string, std::shared_ptr<TachSensor>>
591         tachSensors;
592     boost::container::flat_map<std::string, std::unique_ptr<PwmSensor>>
593         pwmSensors;
594     auto sensorsChanged =
595         std::make_shared<boost::container::flat_set<std::string>>();
596 
597     boost::asio::post(io, [&]() {
598         createSensors(io, objectServer, tachSensors, pwmSensors, systemBus,
599                       nullptr);
600     });
601 
602     boost::asio::steady_timer filterTimer(io);
603     std::function<void(sdbusplus::message_t&)> eventHandler =
604         [&](sdbusplus::message_t& message) {
605         if (message.is_method_error())
606         {
607             std::cerr << "callback method error\n";
608             return;
609         }
610         sensorsChanged->insert(message.get_path());
611         // this implicitly cancels the timer
612         filterTimer.expires_after(std::chrono::seconds(1));
613 
614         filterTimer.async_wait([&](const boost::system::error_code& ec) {
615             if (ec == boost::asio::error::operation_aborted)
616             {
617                 /* we were canceled*/
618                 return;
619             }
620             if (ec)
621             {
622                 std::cerr << "timer error\n";
623                 return;
624             }
625             createSensors(io, objectServer, tachSensors, pwmSensors, systemBus,
626                           sensorsChanged, 5);
627         });
628     };
629 
630     std::vector<std::unique_ptr<sdbusplus::bus::match_t>> matches =
631         setupPropertiesChangedMatches(*systemBus, sensorTypes, eventHandler);
632 
633     // redundancy sensor
634     std::function<void(sdbusplus::message_t&)> redundancyHandler =
635         [&tachSensors, &systemBus, &objectServer](sdbusplus::message_t&) {
636         createRedundancySensor(tachSensors, systemBus, objectServer);
637     };
638     auto match = std::make_unique<sdbusplus::bus::match_t>(
639         static_cast<sdbusplus::bus_t&>(*systemBus),
640         "type='signal',member='PropertiesChanged',path_namespace='" +
641             std::string(inventoryPath) + "',arg0namespace='" +
642             redundancyConfiguration + "'",
643         std::move(redundancyHandler));
644     matches.emplace_back(std::move(match));
645 
646     setupManufacturingModeMatch(*systemBus);
647     io.run();
648     return 0;
649 }
650