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