xref: /openbmc/dbus-sensors/src/TachSensor.cpp (revision 95b079b7)
1 /*
2 // Copyright (c) 2018 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 <unistd.h>
18 
19 #include <TachSensor.hpp>
20 #include <Utils.hpp>
21 #include <boost/algorithm/string/predicate.hpp>
22 #include <boost/algorithm/string/replace.hpp>
23 #include <boost/date_time/posix_time/posix_time.hpp>
24 #include <fstream>
25 #include <iostream>
26 #include <limits>
27 #include <sdbusplus/asio/connection.hpp>
28 #include <sdbusplus/asio/object_server.hpp>
29 #include <string>
30 
31 static constexpr unsigned int pwmPollMs = 500;
32 static constexpr size_t warnAfterErrorCount = 10;
33 
34 TachSensor::TachSensor(const std::string &path,
35                        sdbusplus::asio::object_server &objectServer,
36                        std::shared_ptr<sdbusplus::asio::connection> &conn,
37                        std::unique_ptr<PresenceSensor> &&presence,
38                        const std::shared_ptr<RedundancySensor> &redundancy,
39                        boost::asio::io_service &io, const std::string &fanName,
40                        std::vector<thresholds::Threshold> &&_thresholds,
41                        const std::string &sensorConfiguration) :
42     Sensor(boost::replace_all_copy(fanName, " ", "_"), path,
43            std::move(_thresholds)),
44     objServer(objectServer), dbusConnection(conn),
45     presence(std::move(presence)), redundancy(redundancy),
46     configuration(sensorConfiguration),
47     inputDev(io, open(path.c_str(), O_RDONLY)), waitTimer(io), errCount(0),
48     // todo, get these from config
49     maxValue(25000), minValue(0)
50 {
51     sensorInterface = objectServer.add_interface(
52         "/xyz/openbmc_project/sensors/fan_tach/" + name,
53         "xyz.openbmc_project.Sensor.Value");
54 
55     if (thresholds::hasWarningInterface(thresholds))
56     {
57         thresholdInterfaceWarning = objectServer.add_interface(
58             "/xyz/openbmc_project/sensors/fan_tach/" + name,
59             "xyz.openbmc_project.Sensor.Threshold.Warning");
60     }
61     if (thresholds::hasCriticalInterface(thresholds))
62     {
63         thresholdInterfaceCritical = objectServer.add_interface(
64             "/xyz/openbmc_project/sensors/fan_tach/" + name,
65             "xyz.openbmc_project.Sensor.Threshold.Critical");
66     }
67     setInitialProperties(conn);
68     isPowerOn(dbusConnection); // first call initializes
69     setupRead();
70 }
71 
72 TachSensor::~TachSensor()
73 {
74     // close the input dev to cancel async operations
75     inputDev.close();
76     waitTimer.cancel();
77     objServer.remove_interface(thresholdInterfaceWarning);
78     objServer.remove_interface(thresholdInterfaceCritical);
79     objServer.remove_interface(sensorInterface);
80 }
81 
82 void TachSensor::setupRead(void)
83 {
84     boost::asio::async_read_until(
85         inputDev, readBuf, '\n',
86         [&](const boost::system::error_code &ec,
87             std::size_t /*bytes_transfered*/) { handleResponse(ec); });
88 }
89 
90 void TachSensor::handleResponse(const boost::system::error_code &err)
91 {
92     if (err == boost::system::errc::bad_file_descriptor)
93     {
94         return; // we're being destroyed
95     }
96     bool missing = false;
97     size_t pollTime = pwmPollMs;
98     if (presence)
99     {
100         if (!presence->getValue())
101         {
102             updateValue(std::numeric_limits<double>::quiet_NaN());
103             missing = true;
104             pollTime = sensorFailedPollTimeMs;
105         }
106     }
107     std::istream responseStream(&readBuf);
108     if (!missing)
109     {
110         if (!err)
111         {
112             std::string response;
113             try
114             {
115                 std::getline(responseStream, response);
116                 float nvalue = std::stof(response);
117                 responseStream.clear();
118                 if (!isnan(overriddenValue))
119                 {
120                     nvalue = overriddenValue;
121                 }
122                 if (nvalue != value)
123                 {
124                     updateValue(nvalue);
125                 }
126                 errCount = 0;
127             }
128             catch (const std::invalid_argument &)
129             {
130                 errCount++;
131             }
132         }
133         else
134         {
135             pollTime = sensorFailedPollTimeMs;
136             errCount++;
137         }
138         if (errCount >= warnAfterErrorCount)
139         {
140             // only an error if power is on
141             if (isPowerOn(dbusConnection))
142             {
143                 // only print once
144                 if (errCount == warnAfterErrorCount)
145                 {
146                     std::cerr << "Failure to read sensor " << name << " at "
147                               << path << " ec:" << err << "\n";
148                 }
149                 updateValue(0);
150             }
151             else
152             {
153                 errCount = 0; // check power again in 10 cycles
154                 updateValue(std::numeric_limits<double>::quiet_NaN());
155             }
156         }
157     }
158     responseStream.clear();
159     inputDev.close();
160     int fd = open(path.c_str(), O_RDONLY);
161     if (fd <= 0)
162     {
163         return; // we're no longer valid
164     }
165     inputDev.assign(fd);
166     waitTimer.expires_from_now(boost::posix_time::milliseconds(pollTime));
167     waitTimer.async_wait([&](const boost::system::error_code &ec) {
168         if (ec == boost::asio::error::operation_aborted)
169         {
170             return; // we're being canceled
171         }
172         setupRead();
173     });
174 }
175 
176 void TachSensor::checkThresholds(void)
177 {
178     bool status = thresholds::checkThresholds(this);
179     if (redundancy)
180     {
181         redundancy->update("/xyz/openbmc_project/sensors/fan_tach/" + name,
182                            !status);
183     }
184 }
185 
186 void TachSensor::updateValue(const double &newValue)
187 {
188     // Indicate that it is internal set call
189     internalSet = true;
190     sensorInterface->set_property("Value", newValue);
191     internalSet = false;
192     value = newValue;
193     checkThresholds();
194 }
195 
196 void TachSensor::setInitialProperties(
197     std::shared_ptr<sdbusplus::asio::connection> &conn)
198 {
199     // todo, get max and min from configuration
200     sensorInterface->register_property("MaxValue", maxValue);
201     sensorInterface->register_property("MinValue", minValue);
202     sensorInterface->register_property(
203         "Value", value, [&](const double &newValue, double &oldValue) {
204             return setSensorValue(newValue, oldValue);
205         });
206 
207     for (auto &threshold : thresholds)
208     {
209         std::shared_ptr<sdbusplus::asio::dbus_interface> iface;
210         std::string level;
211         std::string alarm;
212         if (threshold.level == thresholds::Level::CRITICAL)
213         {
214             iface = thresholdInterfaceCritical;
215             if (threshold.direction == thresholds::Direction::HIGH)
216             {
217                 level = "CriticalHigh";
218                 alarm = "CriticalAlarmHigh";
219             }
220             else
221             {
222                 level = "CriticalLow";
223                 alarm = "CriticalAlarmLow";
224             }
225         }
226         else if (threshold.level == thresholds::Level::WARNING)
227         {
228             iface = thresholdInterfaceWarning;
229             if (threshold.direction == thresholds::Direction::HIGH)
230             {
231                 level = "WarningHigh";
232                 alarm = "WarningAlarmHigh";
233             }
234             else
235             {
236                 level = "WarningLow";
237                 alarm = "WarningAlarmLow";
238             }
239         }
240         else
241         {
242             std::cerr << "Unknown threshold level" << threshold.level << "\n";
243             continue;
244         }
245         if (!iface)
246         {
247             std::cout << "trying to set uninitialized interface\n";
248             continue;
249         }
250         iface->register_property(
251             level, threshold.value,
252             [&](const double &request, double &oldValue) {
253                 oldValue = request; // todo, just let the config do this?
254                 threshold.value = request;
255                 thresholds::persistThreshold(
256                     configuration,
257                     "xyz.openbmc_project.Configuration.AspeedFan", threshold,
258                     conn);
259                 return 1;
260             });
261         iface->register_property(alarm, false);
262     }
263     if (!sensorInterface->initialize())
264     {
265         std::cerr << "error initializing value interface\n";
266     }
267     if (thresholdInterfaceWarning && !thresholdInterfaceWarning->initialize())
268     {
269         std::cerr << "error initializing warning threshold interface\n";
270     }
271 
272     if (thresholdInterfaceCritical && !thresholdInterfaceCritical->initialize())
273     {
274         std::cerr << "error initializing critical threshold interface\n";
275     }
276 }
277 
278 PresenceSensor::PresenceSensor(const size_t index, bool inverted,
279                                boost::asio::io_service &io) :
280     inverted(inverted),
281     inputDev(io)
282 {
283     // todo: use gpiodaemon
284     std::string device = gpioPath + std::string("gpio") + std::to_string(index);
285     fd = open((device + "/value").c_str(), O_RDONLY);
286     if (fd < 0)
287     {
288         std::cerr << "Error opening gpio " << index << "\n";
289         return;
290     }
291 
292     std::ofstream deviceFile(device + "/edge");
293     if (!deviceFile.good())
294     {
295         std::cerr << "Error setting edge " << device << "\n";
296         return;
297     }
298     deviceFile << "both";
299     deviceFile.close();
300 
301     inputDev.assign(boost::asio::ip::tcp::v4(), fd);
302     monitorPresence();
303     read();
304 }
305 
306 PresenceSensor::~PresenceSensor()
307 {
308     inputDev.close();
309     close(fd);
310 }
311 
312 void PresenceSensor::monitorPresence(void)
313 {
314     inputDev.async_wait(boost::asio::ip::tcp::socket::wait_error,
315                         [this](const boost::system::error_code &ec) {
316                             if (ec == boost::system::errc::bad_file_descriptor)
317                             {
318                                 return; // we're being destroyed
319                             }
320                             else if (ec)
321                             {
322                                 std::cerr
323                                     << "Error on presence sensor socket\n";
324                             }
325                             else
326                             {
327                                 read();
328                             }
329                             monitorPresence();
330                         });
331 }
332 
333 void PresenceSensor::read(void)
334 {
335     constexpr size_t readSize = sizeof("0");
336     std::string readBuf;
337     readBuf.resize(readSize);
338     lseek(fd, 0, SEEK_SET);
339     size_t r = ::read(fd, readBuf.data(), readSize);
340     if (r != readSize)
341     {
342         std::cerr << "Error reading gpio\n";
343     }
344     else
345     {
346         bool value = std::stoi(readBuf);
347         if (inverted)
348         {
349             value = !value;
350         }
351         status = value;
352     }
353 }
354 
355 bool PresenceSensor::getValue(void)
356 {
357     return status;
358 }
359 
360 RedundancySensor::RedundancySensor(
361     size_t count, const std::vector<std::string> &children,
362     sdbusplus::asio::object_server &objectServer) :
363     count(count),
364     iface(objectServer.add_interface(
365         "/xyz/openbmc_project/control/FanRedundancy/Tach",
366         "xyz.openbmc_project.control.FanRedundancy")),
367     objectServer(objectServer)
368 {
369     iface->register_property("Collection", children);
370     iface->register_property("Status", std::string("Full"));
371     iface->register_property("AllowedFailures", static_cast<uint8_t>(count));
372     iface->initialize();
373 }
374 RedundancySensor::~RedundancySensor()
375 {
376     objectServer.remove_interface(iface);
377 }
378 void RedundancySensor::update(const std::string &name, bool failed)
379 {
380     statuses[name] = failed;
381     size_t failedCount = 0;
382 
383     std::string state = "Full";
384     for (const auto &status : statuses)
385     {
386         if (status.second)
387         {
388             failedCount++;
389         }
390         if (failedCount > count)
391         {
392             state = "Failed";
393             break;
394         }
395         else if (failedCount)
396         {
397             state = "Degraded";
398         }
399     }
400     iface->set_property("Status", state);
401 }
402