blob: fdd0295570e5998c05cfad8f92cfd3fd07bf93d3 [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
Patrick Venture96e97db2019-10-31 13:44:38 -070022#include <array>
James Feist6714a252018-09-10 15:26:18 -070023#include <boost/algorithm/string/predicate.hpp>
24#include <boost/algorithm/string/replace.hpp>
Patrick Venture96e97db2019-10-31 13:44:38 -070025#include <boost/container/flat_map.hpp>
James Feist6714a252018-09-10 15:26:18 -070026#include <boost/container/flat_set.hpp>
27#include <boost/lexical_cast.hpp>
James Feist24f02f22019-04-15 11:05:39 -070028#include <filesystem>
James Feist6714a252018-09-10 15:26:18 -070029#include <fstream>
Patrick Venture96e97db2019-10-31 13:44:38 -070030#include <functional>
31#include <memory>
32#include <optional>
James Feist6714a252018-09-10 15:26:18 -070033#include <regex>
34#include <sdbusplus/asio/connection.hpp>
35#include <sdbusplus/asio/object_server.hpp>
Patrick Venture96e97db2019-10-31 13:44:38 -070036#include <sdbusplus/bus/match.hpp>
37#include <string>
38#include <utility>
39#include <variant>
40#include <vector>
James Feist6714a252018-09-10 15:26:18 -070041
42static constexpr bool DEBUG = false;
43
James Feistcf3bce62019-01-08 10:07:19 -080044namespace fs = std::filesystem;
James Feist3eb82622019-02-08 13:10:22 -080045
Peter Lundgren8843b622019-09-12 10:33:41 -070046static constexpr std::array<const char*, 3> sensorTypes = {
James Feist95b079b2018-11-21 09:28:00 -080047 "xyz.openbmc_project.Configuration.AspeedFan",
Peter Lundgren8843b622019-09-12 10:33:41 -070048 "xyz.openbmc_project.Configuration.I2CFan",
49 "xyz.openbmc_project.Configuration.NuvotonFan"};
James Feistdc6c55f2018-10-31 12:53:20 -070050constexpr const char* redundancyConfiguration =
51 "xyz.openbmc_project.Configuration.FanRedundancy";
Jae Hyun Yoo9ced0a32018-10-25 10:42:39 -070052static std::regex inputRegex(R"(fan(\d+)_input)");
James Feist6714a252018-09-10 15:26:18 -070053
James Feist95b079b2018-11-21 09:28:00 -080054enum class FanTypes
55{
56 aspeed,
Peter Lundgren8843b622019-09-12 10:33:41 -070057 i2c,
58 nuvoton
James Feist95b079b2018-11-21 09:28:00 -080059};
60
James Feistdc6c55f2018-10-31 12:53:20 -070061// todo: power supply fan redundancy
James Feist7b18b1e2019-05-14 13:42:09 -070062std::optional<RedundancySensor> systemRedundancy;
James Feist95b079b2018-11-21 09:28:00 -080063
64FanTypes getFanType(const fs::path& parentPath)
65{
66 fs::path linkPath = parentPath / "device";
67 std::string canonical = fs::read_symlink(linkPath);
68 if (boost::ends_with(canonical, "1e786000.pwm-tacho-controller"))
69 {
70 return FanTypes::aspeed;
71 }
Peter Lundgren8843b622019-09-12 10:33:41 -070072 else if (boost::ends_with(canonical, "f0103000.pwm-fan-controller"))
73 {
74 return FanTypes::nuvoton;
75 }
James Feist95b079b2018-11-21 09:28:00 -080076 // todo: will we need to support other types?
77 return FanTypes::i2c;
78}
James Feistdc6c55f2018-10-31 12:53:20 -070079
James Feist6714a252018-09-10 15:26:18 -070080void createSensors(
81 boost::asio::io_service& io, sdbusplus::asio::object_server& objectServer,
82 boost::container::flat_map<std::string, std::unique_ptr<TachSensor>>&
83 tachSensors,
84 boost::container::flat_map<std::string, std::unique_ptr<PwmSensor>>&
85 pwmSensors,
86 std::shared_ptr<sdbusplus::asio::connection>& dbusConnection,
87 const std::unique_ptr<boost::container::flat_set<std::string>>&
88 sensorsChanged)
89{
James Feist6714a252018-09-10 15:26:18 -070090
James Feistde5e9702019-09-18 16:13:02 -070091 auto getter = std::make_shared<GetSensorConfiguration>(
92 dbusConnection,
93 std::move([&io, &objectServer, &tachSensors, &pwmSensors,
94 &dbusConnection, &sensorsChanged](
95 const ManagedObjectType& sensorConfigurations) {
96 bool firstScan = sensorsChanged == nullptr;
97 std::vector<fs::path> paths;
98 if (!findFiles(fs::path("/sys/class/hwmon"), R"(fan\d+_input)",
99 paths))
James Feist95b079b2018-11-21 09:28:00 -0800100 {
James Feistde5e9702019-09-18 16:13:02 -0700101 std::cerr << "No temperature sensors in system\n";
102 return;
James Feist95b079b2018-11-21 09:28:00 -0800103 }
James Feist6714a252018-09-10 15:26:18 -0700104
James Feistde5e9702019-09-18 16:13:02 -0700105 std::vector<std::pair<uint8_t, std::string>> pwmNumbers;
106
107 // iterate through all found fan sensors, and try to match them with
108 // configuration
109 for (const auto& path : paths)
James Feist6714a252018-09-10 15:26:18 -0700110 {
James Feistde5e9702019-09-18 16:13:02 -0700111 std::smatch match;
112 std::string pathStr = path.string();
113
114 std::regex_search(pathStr, match, inputRegex);
115 std::string indexStr = *(match.begin() + 1);
116
117 auto directory = path.parent_path();
118 FanTypes fanType = getFanType(directory);
119 size_t bus = 0;
120 size_t address = 0;
121 if (fanType == FanTypes::i2c)
James Feist6714a252018-09-10 15:26:18 -0700122 {
James Feistde5e9702019-09-18 16:13:02 -0700123 std::string link =
124 fs::read_symlink(directory / "device").filename();
125
126 size_t findDash = link.find("-");
127 if (findDash == std::string::npos ||
128 link.size() <= findDash + 1)
129 {
130 std::cerr << "Error finding device from symlink";
131 }
132 bus = std::stoi(link.substr(0, findDash));
133 address = std::stoi(link.substr(findDash + 1), nullptr, 16);
James Feist6714a252018-09-10 15:26:18 -0700134 }
James Feistde5e9702019-09-18 16:13:02 -0700135 // convert to 0 based
136 size_t index = std::stoul(indexStr) - 1;
137
138 const char* baseType;
139 const SensorData* sensorData = nullptr;
140 const std::string* interfacePath = nullptr;
141 const SensorBaseConfiguration* baseConfiguration = nullptr;
142 for (const std::pair<sdbusplus::message::object_path,
143 SensorData>& sensor : sensorConfigurations)
James Feist95b079b2018-11-21 09:28:00 -0800144 {
James Feistde5e9702019-09-18 16:13:02 -0700145 // find the base of the configuration to see if indexes
146 // match
147 for (const char* type : sensorTypes)
148 {
149 auto sensorBaseFind = sensor.second.find(type);
150 if (sensorBaseFind != sensor.second.end())
151 {
152 baseConfiguration = &(*sensorBaseFind);
153 interfacePath = &(sensor.first.str);
154 baseType = type;
155 break;
156 }
157 }
158 if (baseConfiguration == nullptr)
159 {
160 continue;
161 }
162 auto findIndex = baseConfiguration->second.find("Index");
163 if (findIndex == baseConfiguration->second.end())
164 {
165 std::cerr << baseConfiguration->first
166 << " missing index\n";
167 continue;
168 }
169 unsigned int configIndex = std::visit(
170 VariantToUnsignedIntVisitor(), findIndex->second);
171 if (configIndex != index)
172 {
173 continue;
174 }
175 if (fanType == FanTypes::aspeed ||
176 fanType == FanTypes::nuvoton)
177 {
178 // there will be only 1 aspeed or nuvoton sensor object
179 // in sysfs, we found the fan
180 sensorData = &(sensor.second);
181 break;
182 }
183 else if (baseType ==
184 std::string(
185 "xyz.openbmc_project.Configuration.I2CFan"))
186 {
187 auto findBus = baseConfiguration->second.find("Bus");
188 auto findAddress =
189 baseConfiguration->second.find("Address");
190 if (findBus == baseConfiguration->second.end() ||
191 findAddress == baseConfiguration->second.end())
192 {
193 std::cerr << baseConfiguration->first
194 << " missing bus or address\n";
195 continue;
196 }
197 unsigned int configBus = std::visit(
198 VariantToUnsignedIntVisitor(), findBus->second);
199 unsigned int configAddress = std::visit(
200 VariantToUnsignedIntVisitor(), findAddress->second);
201
202 if (configBus == bus && configAddress == address)
203 {
204 sensorData = &(sensor.second);
205 break;
206 }
207 }
208 }
209 if (sensorData == nullptr)
210 {
211 std::cerr << "failed to find match for " << path.string()
212 << "\n";
James Feist95b079b2018-11-21 09:28:00 -0800213 continue;
214 }
James Feist95b079b2018-11-21 09:28:00 -0800215
James Feistde5e9702019-09-18 16:13:02 -0700216 auto findSensorName = baseConfiguration->second.find("Name");
217 if (findSensorName == baseConfiguration->second.end())
James Feist95b079b2018-11-21 09:28:00 -0800218 {
James Feistde5e9702019-09-18 16:13:02 -0700219 std::cerr << "could not determine configuration name for "
220 << path.string() << "\n";
221 continue;
222 }
223 std::string sensorName =
224 std::get<std::string>(findSensorName->second);
225 // on rescans, only update sensors we were signaled by
226 auto findSensor = tachSensors.find(sensorName);
227 if (!firstScan && findSensor != tachSensors.end())
228 {
229 bool found = false;
230 for (auto it = sensorsChanged->begin();
231 it != sensorsChanged->end(); it++)
232 {
233 if (boost::ends_with(*it, findSensor->second->name))
234 {
235 sensorsChanged->erase(it);
236 findSensor->second = nullptr;
237 found = true;
238 break;
239 }
240 }
241 if (!found)
242 {
243 continue;
244 }
245 }
246 std::vector<thresholds::Threshold> sensorThresholds;
247 if (!parseThresholdsFromConfig(*sensorData, sensorThresholds))
248 {
249 std::cerr << "error populating thresholds for "
250 << sensorName << "\n";
251 }
252
253 auto presenceConfig =
254 sensorData->find(baseType + std::string(".Presence"));
255
256 std::unique_ptr<PresenceSensor> presenceSensor(nullptr);
257
258 // presence sensors are optional
259 if (presenceConfig != sensorData->end())
260 {
261 auto findIndex = presenceConfig->second.find("Index");
262 auto findPolarity = presenceConfig->second.find("Polarity");
263
264 if (findIndex == presenceConfig->second.end() ||
265 findPolarity == presenceConfig->second.end())
266 {
267 std::cerr << "Malformed Presence Configuration\n";
268 }
269 else
270 {
271 size_t index = std::get<uint64_t>(findIndex->second);
272 bool inverted = std::get<std::string>(
273 findPolarity->second) == "Low";
274 presenceSensor = std::make_unique<PresenceSensor>(
275 index, inverted, io, sensorName);
276 }
277 }
278 std::optional<RedundancySensor>* redundancy = nullptr;
279 if (fanType == FanTypes::aspeed)
280 {
281 redundancy = &systemRedundancy;
282 }
283
284 constexpr double defaultMaxReading = 25000;
285 constexpr double defaultMinReading = 0;
286 auto limits =
287 std::make_pair(defaultMinReading, defaultMaxReading);
288
289 findLimits(limits, baseConfiguration);
290 tachSensors[sensorName] = std::make_unique<TachSensor>(
291 path.string(), baseType, objectServer, dbusConnection,
292 std::move(presenceSensor), redundancy, io, sensorName,
293 std::move(sensorThresholds), *interfacePath, limits);
294
295 auto connector =
296 sensorData->find(baseType + std::string(".Connector"));
297 if (connector != sensorData->end())
298 {
299 auto findPwm = connector->second.find("Pwm");
300 if (findPwm == connector->second.end())
301 {
302 std::cerr << "Connector Missing PWM!\n";
303 continue;
304 }
305
306 size_t pwm = std::visit(VariantToUnsignedIntVisitor(),
307 findPwm->second);
308 pwmNumbers.emplace_back(pwm, *interfacePath);
James Feist95b079b2018-11-21 09:28:00 -0800309 }
310 }
James Feistde5e9702019-09-18 16:13:02 -0700311 std::vector<fs::path> pwms;
312 if (!findFiles(fs::path("/sys/class/hwmon"), R"(pwm\d+$)", pwms))
James Feist6714a252018-09-10 15:26:18 -0700313 {
James Feistde5e9702019-09-18 16:13:02 -0700314 std::cerr << "No pwm in system\n";
315 return;
316 }
317 for (const fs::path& pwm : pwms)
318 {
319 if (pwmSensors.find(pwm) != pwmSensors.end())
James Feist6714a252018-09-10 15:26:18 -0700320 {
James Feistde5e9702019-09-18 16:13:02 -0700321 continue;
James Feist6714a252018-09-10 15:26:18 -0700322 }
James Feistde5e9702019-09-18 16:13:02 -0700323 const std::string* path = nullptr;
324 for (const auto& [index, configPath] : pwmNumbers)
325 {
326 if (boost::ends_with(pwm.string(),
327 std::to_string(index + 1)))
328 {
329 path = &configPath;
330 break;
331 }
332 }
333
334 if (path == nullptr)
335 {
336 continue;
337 }
338
339 // only add new elements
340 const std::string& sysPath = pwm.string();
341 const std::string& pwmName =
342 "Pwm_" + sysPath.substr(sysPath.find_last_of("pwm") + 1);
343 pwmSensors.insert(
344 std::pair<std::string, std::unique_ptr<PwmSensor>>(
345 sysPath, std::make_unique<PwmSensor>(
346 pwmName, sysPath, objectServer, *path)));
James Feist6714a252018-09-10 15:26:18 -0700347 }
James Feistde5e9702019-09-18 16:13:02 -0700348 }));
349 getter->getConfiguration(
350 std::vector<std::string>{sensorTypes.begin(), sensorTypes.end()});
James Feist6714a252018-09-10 15:26:18 -0700351}
352
James Feistdc6c55f2018-10-31 12:53:20 -0700353void createRedundancySensor(
354 const boost::container::flat_map<std::string, std::unique_ptr<TachSensor>>&
355 sensors,
356 std::shared_ptr<sdbusplus::asio::connection> conn,
357 sdbusplus::asio::object_server& objectServer)
358{
359
360 conn->async_method_call(
361 [&objectServer, &sensors](boost::system::error_code& ec,
362 const ManagedObjectType managedObj) {
363 if (ec)
364 {
365 std::cerr << "Error calling entity manager \n";
366 return;
367 }
368 for (const auto& pathPair : managedObj)
369 {
370 for (const auto& interfacePair : pathPair.second)
371 {
372 if (interfacePair.first == redundancyConfiguration)
373 {
374 // currently only support one
375 auto findCount =
376 interfacePair.second.find("AllowedFailures");
377 if (findCount == interfacePair.second.end())
378 {
379 std::cerr << "Malformed redundancy record \n";
380 return;
381 }
382 std::vector<std::string> sensorList;
383
384 for (const auto& sensor : sensors)
385 {
386 sensorList.push_back(
387 "/xyz/openbmc_project/sensors/fan_tach/" +
388 sensor.second->name);
389 }
James Feist7b18b1e2019-05-14 13:42:09 -0700390 systemRedundancy.reset();
391 systemRedundancy.emplace(RedundancySensor(
James Feist3eb82622019-02-08 13:10:22 -0800392 std::get<uint64_t>(findCount->second), sensorList,
James Feist7b18b1e2019-05-14 13:42:09 -0700393 objectServer, pathPair.first));
James Feistdc6c55f2018-10-31 12:53:20 -0700394
395 return;
396 }
397 }
398 }
399 },
400 "xyz.openbmc_project.EntityManager", "/",
401 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
402}
403
James Feistb6c0b912019-07-09 12:21:44 -0700404int main()
James Feist6714a252018-09-10 15:26:18 -0700405{
406 boost::asio::io_service io;
407 auto systemBus = std::make_shared<sdbusplus::asio::connection>(io);
408 systemBus->request_name("xyz.openbmc_project.FanSensor");
409 sdbusplus::asio::object_server objectServer(systemBus);
410 boost::container::flat_map<std::string, std::unique_ptr<TachSensor>>
411 tachSensors;
412 boost::container::flat_map<std::string, std::unique_ptr<PwmSensor>>
413 pwmSensors;
414 std::vector<std::unique_ptr<sdbusplus::bus::match::match>> matches;
415 std::unique_ptr<boost::container::flat_set<std::string>> sensorsChanged =
416 std::make_unique<boost::container::flat_set<std::string>>();
417
418 io.post([&]() {
419 createSensors(io, objectServer, tachSensors, pwmSensors, systemBus,
420 nullptr);
James Feistdc6c55f2018-10-31 12:53:20 -0700421 createRedundancySensor(tachSensors, systemBus, objectServer);
James Feist6714a252018-09-10 15:26:18 -0700422 });
423
424 boost::asio::deadline_timer filterTimer(io);
425 std::function<void(sdbusplus::message::message&)> eventHandler =
426 [&](sdbusplus::message::message& message) {
427 if (message.is_method_error())
428 {
429 std::cerr << "callback method error\n";
430 return;
431 }
432 sensorsChanged->insert(message.get_path());
433 // this implicitly cancels the timer
434 filterTimer.expires_from_now(boost::posix_time::seconds(1));
435
436 filterTimer.async_wait([&](const boost::system::error_code& ec) {
437 if (ec == boost::asio::error::operation_aborted)
438 {
439 /* we were canceled*/
440 return;
441 }
442 else if (ec)
443 {
444 std::cerr << "timer error\n";
445 return;
446 }
447 createSensors(io, objectServer, tachSensors, pwmSensors,
448 systemBus, sensorsChanged);
449 });
450 };
451
Jae Hyun Yoo9ced0a32018-10-25 10:42:39 -0700452 for (const char* type : sensorTypes)
James Feist6714a252018-09-10 15:26:18 -0700453 {
454 auto match = std::make_unique<sdbusplus::bus::match::match>(
455 static_cast<sdbusplus::bus::bus&>(*systemBus),
456 "type='signal',member='PropertiesChanged',path_namespace='" +
Jae Hyun Yoo9ced0a32018-10-25 10:42:39 -0700457 std::string(inventoryPath) + "',arg0namespace='" + type + "'",
James Feist6714a252018-09-10 15:26:18 -0700458 eventHandler);
459 matches.emplace_back(std::move(match));
460 }
461
James Feistdc6c55f2018-10-31 12:53:20 -0700462 // redundancy sensor
463 std::function<void(sdbusplus::message::message&)> redundancyHandler =
464 [&tachSensors, &systemBus,
James Feistb6c0b912019-07-09 12:21:44 -0700465 &objectServer](sdbusplus::message::message&) {
James Feistdc6c55f2018-10-31 12:53:20 -0700466 createRedundancySensor(tachSensors, systemBus, objectServer);
467 };
468 auto match = std::make_unique<sdbusplus::bus::match::match>(
469 static_cast<sdbusplus::bus::bus&>(*systemBus),
470 "type='signal',member='PropertiesChanged',path_namespace='" +
471 std::string(inventoryPath) + "',arg0namespace='" +
472 redundancyConfiguration + "'",
James Feistb6c0b912019-07-09 12:21:44 -0700473 std::move(redundancyHandler));
James Feistdc6c55f2018-10-31 12:53:20 -0700474 matches.emplace_back(std::move(match));
475
James Feist6714a252018-09-10 15:26:18 -0700476 io.run();
477}