blob: 232144fa00c071bc6b966d1336c33fb01c7f4574 [file] [log] [blame]
James Feist6714a252018-09-10 15:26:18 -07001/*
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
Patrick Ventureca44b2f2019-10-31 11:02:26 -070017#include "PwmSensor.hpp"
18#include "TachSensor.hpp"
19#include "Utils.hpp"
20#include "VariantVisitors.hpp"
21
James Feist6714a252018-09-10 15:26:18 -070022#include <boost/algorithm/string/predicate.hpp>
23#include <boost/algorithm/string/replace.hpp>
Patrick Venture96e97db2019-10-31 13:44:38 -070024#include <boost/container/flat_map.hpp>
James Feist6714a252018-09-10 15:26:18 -070025#include <boost/container/flat_set.hpp>
26#include <boost/lexical_cast.hpp>
James Feist38fb5982020-05-28 10:09:54 -070027#include <sdbusplus/asio/connection.hpp>
28#include <sdbusplus/asio/object_server.hpp>
29#include <sdbusplus/bus/match.hpp>
30
31#include <array>
James Feist24f02f22019-04-15 11:05:39 -070032#include <filesystem>
James Feist6714a252018-09-10 15:26:18 -070033#include <fstream>
Patrick Venture96e97db2019-10-31 13:44:38 -070034#include <functional>
35#include <memory>
36#include <optional>
James Feist6714a252018-09-10 15:26:18 -070037#include <regex>
Patrick Venture96e97db2019-10-31 13:44:38 -070038#include <string>
39#include <utility>
40#include <variant>
41#include <vector>
James Feist6714a252018-09-10 15:26:18 -070042
43static constexpr bool DEBUG = false;
44
James Feistcf3bce62019-01-08 10:07:19 -080045namespace fs = std::filesystem;
James Feist3eb82622019-02-08 13:10:22 -080046
Peter Lundgren8843b622019-09-12 10:33:41 -070047static constexpr std::array<const char*, 3> sensorTypes = {
James Feist95b079b2018-11-21 09:28:00 -080048 "xyz.openbmc_project.Configuration.AspeedFan",
Peter Lundgren8843b622019-09-12 10:33:41 -070049 "xyz.openbmc_project.Configuration.I2CFan",
50 "xyz.openbmc_project.Configuration.NuvotonFan"};
James Feistdc6c55f2018-10-31 12:53:20 -070051constexpr const char* redundancyConfiguration =
52 "xyz.openbmc_project.Configuration.FanRedundancy";
Jae Hyun Yoo9ced0a32018-10-25 10:42:39 -070053static std::regex inputRegex(R"(fan(\d+)_input)");
James Feist6714a252018-09-10 15:26:18 -070054
James Feist95b079b2018-11-21 09:28:00 -080055enum class FanTypes
56{
57 aspeed,
Peter Lundgren8843b622019-09-12 10:33:41 -070058 i2c,
59 nuvoton
James Feist95b079b2018-11-21 09:28:00 -080060};
61
James Feistdc6c55f2018-10-31 12:53:20 -070062// todo: power supply fan redundancy
James Feist7b18b1e2019-05-14 13:42:09 -070063std::optional<RedundancySensor> systemRedundancy;
James Feist95b079b2018-11-21 09:28:00 -080064
65FanTypes getFanType(const fs::path& parentPath)
66{
67 fs::path linkPath = parentPath / "device";
68 std::string canonical = fs::read_symlink(linkPath);
Jae Hyun Yoo241356e2020-02-20 12:48:04 -080069 if (boost::ends_with(canonical, "1e786000.pwm-tacho-controller") ||
70 boost::ends_with(canonical, "1e610000.pwm-tacho-controller"))
James Feist95b079b2018-11-21 09:28:00 -080071 {
72 return FanTypes::aspeed;
73 }
Peter Lundgren8843b622019-09-12 10:33:41 -070074 else if (boost::ends_with(canonical, "f0103000.pwm-fan-controller"))
75 {
76 return FanTypes::nuvoton;
77 }
James Feist95b079b2018-11-21 09:28:00 -080078 // todo: will we need to support other types?
79 return FanTypes::i2c;
80}
James Feistdc6c55f2018-10-31 12:53:20 -070081
Kuiying Wangd5407412020-09-09 16:06:56 +080082void createRedundancySensor(
83 const boost::container::flat_map<std::string, std::unique_ptr<TachSensor>>&
84 sensors,
85 std::shared_ptr<sdbusplus::asio::connection> conn,
86 sdbusplus::asio::object_server& objectServer)
87{
88
89 conn->async_method_call(
90 [&objectServer, &sensors](boost::system::error_code& ec,
91 const ManagedObjectType managedObj) {
92 if (ec)
93 {
94 std::cerr << "Error calling entity manager \n";
95 return;
96 }
97 for (const auto& pathPair : managedObj)
98 {
99 for (const auto& interfacePair : pathPair.second)
100 {
101 if (interfacePair.first == redundancyConfiguration)
102 {
103 // currently only support one
104 auto findCount =
105 interfacePair.second.find("AllowedFailures");
106 if (findCount == interfacePair.second.end())
107 {
108 std::cerr << "Malformed redundancy record \n";
109 return;
110 }
111 std::vector<std::string> sensorList;
112
113 for (const auto& sensor : sensors)
114 {
115 sensorList.push_back(
116 "/xyz/openbmc_project/sensors/fan_tach/" +
117 sensor.second->name);
118 }
119 systemRedundancy.reset();
120 systemRedundancy.emplace(RedundancySensor(
121 std::get<uint64_t>(findCount->second), sensorList,
122 objectServer, pathPair.first));
123
124 return;
125 }
126 }
127 }
128 },
129 "xyz.openbmc_project.EntityManager", "/",
130 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
131}
132
James Feist6714a252018-09-10 15:26:18 -0700133void createSensors(
134 boost::asio::io_service& io, sdbusplus::asio::object_server& objectServer,
135 boost::container::flat_map<std::string, std::unique_ptr<TachSensor>>&
136 tachSensors,
137 boost::container::flat_map<std::string, std::unique_ptr<PwmSensor>>&
138 pwmSensors,
139 std::shared_ptr<sdbusplus::asio::connection>& dbusConnection,
James Feist5591cf082020-07-15 16:44:54 -0700140 const std::shared_ptr<boost::container::flat_set<std::string>>&
James Feistf27a55c2020-08-04 14:27:30 -0700141 sensorsChanged,
142 size_t retries = 0)
James Feist6714a252018-09-10 15:26:18 -0700143{
James Feist6714a252018-09-10 15:26:18 -0700144
James Feistde5e9702019-09-18 16:13:02 -0700145 auto getter = std::make_shared<GetSensorConfiguration>(
146 dbusConnection,
147 std::move([&io, &objectServer, &tachSensors, &pwmSensors,
James Feist5591cf082020-07-15 16:44:54 -0700148 &dbusConnection, sensorsChanged](
James Feistde5e9702019-09-18 16:13:02 -0700149 const ManagedObjectType& sensorConfigurations) {
150 bool firstScan = sensorsChanged == nullptr;
151 std::vector<fs::path> paths;
152 if (!findFiles(fs::path("/sys/class/hwmon"), R"(fan\d+_input)",
153 paths))
James Feist95b079b2018-11-21 09:28:00 -0800154 {
James Feistde5e9702019-09-18 16:13:02 -0700155 std::cerr << "No temperature sensors in system\n";
156 return;
James Feist95b079b2018-11-21 09:28:00 -0800157 }
James Feist6714a252018-09-10 15:26:18 -0700158
Jason Lingd320a2e2020-07-11 14:08:14 -0700159 // pwm index, sysfs path, pwm name
160 std::vector<std::tuple<uint8_t, std::string, std::string>>
161 pwmNumbers;
James Feistde5e9702019-09-18 16:13:02 -0700162
163 // iterate through all found fan sensors, and try to match them with
164 // configuration
165 for (const auto& path : paths)
James Feist6714a252018-09-10 15:26:18 -0700166 {
James Feistde5e9702019-09-18 16:13:02 -0700167 std::smatch match;
168 std::string pathStr = path.string();
169
170 std::regex_search(pathStr, match, inputRegex);
171 std::string indexStr = *(match.begin() + 1);
172
173 auto directory = path.parent_path();
174 FanTypes fanType = getFanType(directory);
175 size_t bus = 0;
176 size_t address = 0;
177 if (fanType == FanTypes::i2c)
James Feist6714a252018-09-10 15:26:18 -0700178 {
James Feistde5e9702019-09-18 16:13:02 -0700179 std::string link =
180 fs::read_symlink(directory / "device").filename();
181
182 size_t findDash = link.find("-");
183 if (findDash == std::string::npos ||
184 link.size() <= findDash + 1)
185 {
186 std::cerr << "Error finding device from symlink";
187 }
188 bus = std::stoi(link.substr(0, findDash));
189 address = std::stoi(link.substr(findDash + 1), nullptr, 16);
James Feist6714a252018-09-10 15:26:18 -0700190 }
James Feistde5e9702019-09-18 16:13:02 -0700191 // convert to 0 based
192 size_t index = std::stoul(indexStr) - 1;
193
194 const char* baseType;
195 const SensorData* sensorData = nullptr;
196 const std::string* interfacePath = nullptr;
197 const SensorBaseConfiguration* baseConfiguration = nullptr;
198 for (const std::pair<sdbusplus::message::object_path,
199 SensorData>& sensor : sensorConfigurations)
James Feist95b079b2018-11-21 09:28:00 -0800200 {
James Feistde5e9702019-09-18 16:13:02 -0700201 // find the base of the configuration to see if indexes
202 // match
203 for (const char* type : sensorTypes)
204 {
205 auto sensorBaseFind = sensor.second.find(type);
206 if (sensorBaseFind != sensor.second.end())
207 {
208 baseConfiguration = &(*sensorBaseFind);
209 interfacePath = &(sensor.first.str);
210 baseType = type;
211 break;
212 }
213 }
214 if (baseConfiguration == nullptr)
215 {
216 continue;
217 }
Zhikui Ren347dd4e2019-12-12 13:39:50 -0800218
James Feistde5e9702019-09-18 16:13:02 -0700219 auto findIndex = baseConfiguration->second.find("Index");
220 if (findIndex == baseConfiguration->second.end())
221 {
222 std::cerr << baseConfiguration->first
223 << " missing index\n";
224 continue;
225 }
226 unsigned int configIndex = std::visit(
227 VariantToUnsignedIntVisitor(), findIndex->second);
228 if (configIndex != index)
229 {
230 continue;
231 }
232 if (fanType == FanTypes::aspeed ||
233 fanType == FanTypes::nuvoton)
234 {
235 // there will be only 1 aspeed or nuvoton sensor object
236 // in sysfs, we found the fan
237 sensorData = &(sensor.second);
238 break;
239 }
240 else if (baseType ==
241 std::string(
242 "xyz.openbmc_project.Configuration.I2CFan"))
243 {
244 auto findBus = baseConfiguration->second.find("Bus");
245 auto findAddress =
246 baseConfiguration->second.find("Address");
247 if (findBus == baseConfiguration->second.end() ||
248 findAddress == baseConfiguration->second.end())
249 {
250 std::cerr << baseConfiguration->first
251 << " missing bus or address\n";
252 continue;
253 }
254 unsigned int configBus = std::visit(
255 VariantToUnsignedIntVisitor(), findBus->second);
256 unsigned int configAddress = std::visit(
257 VariantToUnsignedIntVisitor(), findAddress->second);
258
259 if (configBus == bus && configAddress == address)
260 {
261 sensorData = &(sensor.second);
262 break;
263 }
264 }
265 }
266 if (sensorData == nullptr)
267 {
268 std::cerr << "failed to find match for " << path.string()
269 << "\n";
James Feist95b079b2018-11-21 09:28:00 -0800270 continue;
271 }
James Feist95b079b2018-11-21 09:28:00 -0800272
James Feistde5e9702019-09-18 16:13:02 -0700273 auto findSensorName = baseConfiguration->second.find("Name");
Zhikui Ren347dd4e2019-12-12 13:39:50 -0800274
James Feistde5e9702019-09-18 16:13:02 -0700275 if (findSensorName == baseConfiguration->second.end())
James Feist95b079b2018-11-21 09:28:00 -0800276 {
James Feistde5e9702019-09-18 16:13:02 -0700277 std::cerr << "could not determine configuration name for "
278 << path.string() << "\n";
279 continue;
280 }
281 std::string sensorName =
282 std::get<std::string>(findSensorName->second);
Zhikui Ren347dd4e2019-12-12 13:39:50 -0800283
James Feistde5e9702019-09-18 16:13:02 -0700284 // on rescans, only update sensors we were signaled by
285 auto findSensor = tachSensors.find(sensorName);
286 if (!firstScan && findSensor != tachSensors.end())
287 {
288 bool found = false;
289 for (auto it = sensorsChanged->begin();
290 it != sensorsChanged->end(); it++)
291 {
292 if (boost::ends_with(*it, findSensor->second->name))
293 {
294 sensorsChanged->erase(it);
295 findSensor->second = nullptr;
296 found = true;
297 break;
298 }
299 }
300 if (!found)
301 {
302 continue;
303 }
304 }
305 std::vector<thresholds::Threshold> sensorThresholds;
306 if (!parseThresholdsFromConfig(*sensorData, sensorThresholds))
307 {
308 std::cerr << "error populating thresholds for "
309 << sensorName << "\n";
310 }
311
312 auto presenceConfig =
313 sensorData->find(baseType + std::string(".Presence"));
314
315 std::unique_ptr<PresenceSensor> presenceSensor(nullptr);
316
317 // presence sensors are optional
318 if (presenceConfig != sensorData->end())
319 {
James Feistde5e9702019-09-18 16:13:02 -0700320 auto findPolarity = presenceConfig->second.find("Polarity");
Zhikui Ren347dd4e2019-12-12 13:39:50 -0800321 auto findPinName = presenceConfig->second.find("PinName");
James Feistde5e9702019-09-18 16:13:02 -0700322
Zhikui Ren347dd4e2019-12-12 13:39:50 -0800323 if (findPinName == presenceConfig->second.end() ||
James Feistde5e9702019-09-18 16:13:02 -0700324 findPolarity == presenceConfig->second.end())
325 {
326 std::cerr << "Malformed Presence Configuration\n";
327 }
328 else
329 {
James Feistde5e9702019-09-18 16:13:02 -0700330 bool inverted = std::get<std::string>(
331 findPolarity->second) == "Low";
Zhikui Ren347dd4e2019-12-12 13:39:50 -0800332 if (auto pinName =
333 std::get_if<std::string>(&findPinName->second))
334 {
335 presenceSensor = std::make_unique<PresenceSensor>(
336 *pinName, inverted, io, sensorName);
337 }
338 else
339 {
340 std::cerr
341 << "Malformed Presence pinName for sensor "
342 << sensorName << " \n";
343 }
James Feistde5e9702019-09-18 16:13:02 -0700344 }
345 }
346 std::optional<RedundancySensor>* redundancy = nullptr;
347 if (fanType == FanTypes::aspeed)
348 {
349 redundancy = &systemRedundancy;
350 }
351
Josh Lehanf920e092020-08-07 00:12:54 -0700352 PowerState powerState = PowerState::on;
353 auto findPower = baseConfiguration->second.find("PowerState");
354 if (findPower != baseConfiguration->second.end())
355 {
356 auto ptrPower =
357 std::get_if<std::string>(&(findPower->second));
358 if (ptrPower)
359 {
360 setReadState(*ptrPower, powerState);
361 }
362 }
363
James Feistde5e9702019-09-18 16:13:02 -0700364 constexpr double defaultMaxReading = 25000;
365 constexpr double defaultMinReading = 0;
366 auto limits =
367 std::make_pair(defaultMinReading, defaultMaxReading);
368
James Feist49a8ccd2020-09-16 16:09:52 -0700369 auto connector =
370 sensorData->find(baseType + std::string(".Connector"));
371
372 std::optional<std::string> led;
373
374 if (connector != sensorData->end())
375 {
376 auto findPwm = connector->second.find("Pwm");
377 if (findPwm != connector->second.end())
378 {
379
380 size_t pwm = std::visit(VariantToUnsignedIntVisitor(),
381 findPwm->second);
382 /* use pwm name override if found in configuration else
383 * use default */
384 auto findOverride = connector->second.find("PwmName");
385 std::string pwmName;
386 if (findOverride != connector->second.end())
387 {
388 pwmName = std::visit(VariantToStringVisitor(),
389 findOverride->second);
390 }
391 else
392 {
393 pwmName = "Pwm_" + std::to_string(pwm + 1);
394 }
395 pwmNumbers.emplace_back(pwm, *interfacePath, pwmName);
396 }
397 else
398 {
399 std::cerr << "Connector for " << sensorName
400 << " missing pwm!\n";
401 }
402
403 auto findLED = connector->second.find("LED");
404 if (findLED != connector->second.end())
405 {
406 auto ledName =
407 std::get_if<std::string>(&(findLED->second));
408 if (ledName == nullptr)
409 {
410 std::cerr << "Wrong format for LED of "
411 << sensorName << "\n";
412 }
413 else
414 {
415 led = *ledName;
416 }
417 }
418 }
419
James Feistde5e9702019-09-18 16:13:02 -0700420 findLimits(limits, baseConfiguration);
421 tachSensors[sensorName] = std::make_unique<TachSensor>(
422 path.string(), baseType, objectServer, dbusConnection,
423 std::move(presenceSensor), redundancy, io, sensorName,
Josh Lehanf920e092020-08-07 00:12:54 -0700424 std::move(sensorThresholds), *interfacePath, limits,
James Feist49a8ccd2020-09-16 16:09:52 -0700425 powerState, led);
James Feist95b079b2018-11-21 09:28:00 -0800426 }
Kuiying Wangd5407412020-09-09 16:06:56 +0800427 createRedundancySensor(tachSensors, dbusConnection, objectServer);
James Feistde5e9702019-09-18 16:13:02 -0700428 std::vector<fs::path> pwms;
429 if (!findFiles(fs::path("/sys/class/hwmon"), R"(pwm\d+$)", pwms))
James Feist6714a252018-09-10 15:26:18 -0700430 {
James Feistde5e9702019-09-18 16:13:02 -0700431 std::cerr << "No pwm in system\n";
432 return;
433 }
434 for (const fs::path& pwm : pwms)
435 {
436 if (pwmSensors.find(pwm) != pwmSensors.end())
James Feist6714a252018-09-10 15:26:18 -0700437 {
James Feistde5e9702019-09-18 16:13:02 -0700438 continue;
James Feist6714a252018-09-10 15:26:18 -0700439 }
James Feistde5e9702019-09-18 16:13:02 -0700440 const std::string* path = nullptr;
Jason Lingd320a2e2020-07-11 14:08:14 -0700441 const std::string* pwmName = nullptr;
442
443 for (const auto& [index, configPath, name] : pwmNumbers)
James Feistde5e9702019-09-18 16:13:02 -0700444 {
445 if (boost::ends_with(pwm.string(),
446 std::to_string(index + 1)))
447 {
448 path = &configPath;
Jason Lingd320a2e2020-07-11 14:08:14 -0700449 pwmName = &name;
James Feistde5e9702019-09-18 16:13:02 -0700450 break;
451 }
452 }
453
454 if (path == nullptr)
455 {
456 continue;
457 }
458
459 // only add new elements
460 const std::string& sysPath = pwm.string();
James Feistde5e9702019-09-18 16:13:02 -0700461 pwmSensors.insert(
462 std::pair<std::string, std::unique_ptr<PwmSensor>>(
463 sysPath, std::make_unique<PwmSensor>(
Jason Lingd320a2e2020-07-11 14:08:14 -0700464 *pwmName, sysPath, dbusConnection,
AppaRao Pulid9d8caf2020-02-27 20:56:59 +0530465 objectServer, *path, "Fan")));
James Feist6714a252018-09-10 15:26:18 -0700466 }
James Feistde5e9702019-09-18 16:13:02 -0700467 }));
468 getter->getConfiguration(
James Feistf27a55c2020-08-04 14:27:30 -0700469 std::vector<std::string>{sensorTypes.begin(), sensorTypes.end()},
470 retries);
James Feist6714a252018-09-10 15:26:18 -0700471}
472
James Feistb6c0b912019-07-09 12:21:44 -0700473int main()
James Feist6714a252018-09-10 15:26:18 -0700474{
475 boost::asio::io_service io;
476 auto systemBus = std::make_shared<sdbusplus::asio::connection>(io);
477 systemBus->request_name("xyz.openbmc_project.FanSensor");
478 sdbusplus::asio::object_server objectServer(systemBus);
479 boost::container::flat_map<std::string, std::unique_ptr<TachSensor>>
480 tachSensors;
481 boost::container::flat_map<std::string, std::unique_ptr<PwmSensor>>
482 pwmSensors;
483 std::vector<std::unique_ptr<sdbusplus::bus::match::match>> matches;
James Feist5591cf082020-07-15 16:44:54 -0700484 auto sensorsChanged =
485 std::make_shared<boost::container::flat_set<std::string>>();
James Feist6714a252018-09-10 15:26:18 -0700486
487 io.post([&]() {
488 createSensors(io, objectServer, tachSensors, pwmSensors, systemBus,
489 nullptr);
490 });
491
492 boost::asio::deadline_timer filterTimer(io);
493 std::function<void(sdbusplus::message::message&)> eventHandler =
494 [&](sdbusplus::message::message& message) {
495 if (message.is_method_error())
496 {
497 std::cerr << "callback method error\n";
498 return;
499 }
500 sensorsChanged->insert(message.get_path());
501 // this implicitly cancels the timer
502 filterTimer.expires_from_now(boost::posix_time::seconds(1));
503
504 filterTimer.async_wait([&](const boost::system::error_code& ec) {
505 if (ec == boost::asio::error::operation_aborted)
506 {
507 /* we were canceled*/
508 return;
509 }
510 else if (ec)
511 {
512 std::cerr << "timer error\n";
513 return;
514 }
515 createSensors(io, objectServer, tachSensors, pwmSensors,
James Feistf27a55c2020-08-04 14:27:30 -0700516 systemBus, sensorsChanged, 5);
James Feist6714a252018-09-10 15:26:18 -0700517 });
518 };
519
Jae Hyun Yoo9ced0a32018-10-25 10:42:39 -0700520 for (const char* type : sensorTypes)
James Feist6714a252018-09-10 15:26:18 -0700521 {
522 auto match = std::make_unique<sdbusplus::bus::match::match>(
523 static_cast<sdbusplus::bus::bus&>(*systemBus),
524 "type='signal',member='PropertiesChanged',path_namespace='" +
Jae Hyun Yoo9ced0a32018-10-25 10:42:39 -0700525 std::string(inventoryPath) + "',arg0namespace='" + type + "'",
James Feist6714a252018-09-10 15:26:18 -0700526 eventHandler);
527 matches.emplace_back(std::move(match));
528 }
529
James Feistdc6c55f2018-10-31 12:53:20 -0700530 // redundancy sensor
531 std::function<void(sdbusplus::message::message&)> redundancyHandler =
532 [&tachSensors, &systemBus,
James Feistb6c0b912019-07-09 12:21:44 -0700533 &objectServer](sdbusplus::message::message&) {
James Feistdc6c55f2018-10-31 12:53:20 -0700534 createRedundancySensor(tachSensors, systemBus, objectServer);
535 };
536 auto match = std::make_unique<sdbusplus::bus::match::match>(
537 static_cast<sdbusplus::bus::bus&>(*systemBus),
538 "type='signal',member='PropertiesChanged',path_namespace='" +
539 std::string(inventoryPath) + "',arg0namespace='" +
540 redundancyConfiguration + "'",
James Feistb6c0b912019-07-09 12:21:44 -0700541 std::move(redundancyHandler));
James Feistdc6c55f2018-10-31 12:53:20 -0700542 matches.emplace_back(std::move(match));
543
James Feist6714a252018-09-10 15:26:18 -0700544 io.run();
545}