xref: /openbmc/dbus-sensors/src/TachSensor.cpp (revision 7bc2bab2)
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                        boost::asio::io_service &io, const std::string &fanName,
39                        std::vector<thresholds::Threshold> &&_thresholds,
40                        const std::string &sensorConfiguration) :
41     Sensor(),
42     path(path), objServer(objectServer), dbusConnection(conn),
43     presence(std::move(presence)),
44     name(boost::replace_all_copy(fanName, " ", "_")),
45     configuration(sensorConfiguration),
46     inputDev(io, open(path.c_str(), O_RDONLY)), waitTimer(io), errCount(0),
47     // todo, get these from config
48     maxValue(25000), minValue(0)
49 {
50     thresholds = std::move(_thresholds);
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     if (presence)
98     {
99         if (!presence->getValue())
100         {
101             sensorInterface->set_property(
102                 "Value", std::numeric_limits<double>::quiet_NaN());
103             missing = true;
104         }
105     }
106     std::istream responseStream(&readBuf);
107     if (!missing)
108     {
109         if (!err)
110         {
111             std::string response;
112             try
113             {
114                 std::getline(responseStream, response);
115                 float nvalue = std::stof(response);
116                 responseStream.clear();
117                 if (nvalue != value)
118                 {
119                     updateValue(nvalue);
120                 }
121                 errCount = 0;
122             }
123             catch (const std::invalid_argument &)
124             {
125                 errCount++;
126             }
127         }
128         else
129         {
130 
131             errCount++;
132         }
133         // only send value update once
134         if (errCount == warnAfterErrorCount)
135         {
136             // only an error if power is on
137             if (isPowerOn(dbusConnection))
138             {
139                 std::cerr << "Failure to read sensor " << name << " at " << path
140                           << "\n";
141                 updateValue(0);
142             }
143             else
144             {
145                 errCount = 0; // check power again in 10 cycles
146                 sensorInterface->set_property(
147                     "Value", std::numeric_limits<double>::quiet_NaN());
148             }
149         }
150     }
151     responseStream.clear();
152     inputDev.close();
153     int fd = open(path.c_str(), O_RDONLY);
154     if (fd <= 0)
155     {
156         return; // we're no longer valid
157     }
158     inputDev.assign(fd);
159     size_t pollTime = pwmPollMs;
160     if (missing)
161     {
162         pollTime *= 10;
163     }
164     waitTimer.expires_from_now(boost::posix_time::milliseconds(pollTime));
165     waitTimer.async_wait([&](const boost::system::error_code &ec) {
166         if (ec == boost::asio::error::operation_aborted)
167         {
168             return; // we're being canceled
169         }
170         setupRead();
171     });
172 }
173 
174 void TachSensor::checkThresholds(void)
175 {
176     thresholds::checkThresholds(this);
177 }
178 
179 void TachSensor::updateValue(const double &newValue)
180 {
181     sensorInterface->set_property("Value", newValue);
182     value = newValue;
183     checkThresholds();
184 }
185 
186 void TachSensor::setInitialProperties(
187     std::shared_ptr<sdbusplus::asio::connection> &conn)
188 {
189     // todo, get max and min from configuration
190     sensorInterface->register_property("MaxValue", maxValue);
191     sensorInterface->register_property("MinValue", minValue);
192     sensorInterface->register_property("Value", value);
193 
194     for (auto &threshold : thresholds)
195     {
196         std::shared_ptr<sdbusplus::asio::dbus_interface> iface;
197         std::string level;
198         std::string alarm;
199         if (threshold.level == thresholds::Level::CRITICAL)
200         {
201             iface = thresholdInterfaceCritical;
202             if (threshold.direction == thresholds::Direction::HIGH)
203             {
204                 level = "CriticalHigh";
205                 alarm = "CriticalAlarmHigh";
206             }
207             else
208             {
209                 level = "CriticalLow";
210                 alarm = "CriticalAlarmLow";
211             }
212         }
213         else if (threshold.level == thresholds::Level::WARNING)
214         {
215             iface = thresholdInterfaceWarning;
216             if (threshold.direction == thresholds::Direction::HIGH)
217             {
218                 level = "WarningHigh";
219                 alarm = "WarningAlarmHigh";
220             }
221             else
222             {
223                 level = "WarningLow";
224                 alarm = "WarningAlarmLow";
225             }
226         }
227         else
228         {
229             std::cerr << "Unknown threshold level" << threshold.level << "\n";
230             continue;
231         }
232         if (!iface)
233         {
234             std::cout << "trying to set uninitialized interface\n";
235             continue;
236         }
237         iface->register_property(
238             level, threshold.value,
239             [&](const double &request, double &oldValue) {
240                 oldValue = request; // todo, just let the config do this?
241                 threshold.value = request;
242                 thresholds::persistThreshold(
243                     configuration,
244                     "xyz.openbmc_project.Configuration.AspeedFan", threshold,
245                     conn);
246                 return 1;
247             });
248         iface->register_property(alarm, false);
249     }
250     if (!sensorInterface->initialize())
251     {
252         std::cerr << "error initializing value interface\n";
253     }
254     if (thresholdInterfaceWarning && !thresholdInterfaceWarning->initialize())
255     {
256         std::cerr << "error initializing warning threshold interface\n";
257     }
258 
259     if (thresholdInterfaceCritical && !thresholdInterfaceCritical->initialize())
260     {
261         std::cerr << "error initializing critical threshold interface\n";
262     }
263 }
264 
265 PresenceSensor::PresenceSensor(const size_t index, bool inverted,
266                                boost::asio::io_service &io) :
267     inverted(inverted),
268     inputDev(io)
269 {
270     // todo: use gpiodaemon
271     std::string device = gpioPath + std::string("gpio") + std::to_string(index);
272     fd = open((device + "/value").c_str(), O_RDONLY);
273     if (fd < 0)
274     {
275         std::cerr << "Error opening gpio " << index << "\n";
276         return;
277     }
278 
279     std::ofstream deviceFile(device + "/edge");
280     if (!deviceFile.good())
281     {
282         std::cerr << "Error setting edge " << device << "\n";
283         return;
284     }
285     deviceFile << "both";
286     deviceFile.close();
287 
288     inputDev.assign(boost::asio::ip::tcp::v4(), fd);
289     monitorPresence();
290     read();
291 }
292 
293 PresenceSensor::~PresenceSensor()
294 {
295     inputDev.close();
296     close(fd);
297 }
298 
299 void PresenceSensor::monitorPresence(void)
300 {
301     inputDev.async_wait(boost::asio::ip::tcp::socket::wait_error,
302                         [this](const boost::system::error_code &ec) {
303                             if (ec == boost::system::errc::bad_file_descriptor)
304                             {
305                                 return; // we're being destroyed
306                             }
307                             else if (ec)
308                             {
309                                 std::cerr
310                                     << "Error on presence sensor socket\n";
311                             }
312                             else
313                             {
314                                 read();
315                             }
316                             monitorPresence();
317                         });
318 }
319 
320 void PresenceSensor::read(void)
321 {
322     constexpr size_t readSize = sizeof("0");
323     std::string readBuf;
324     readBuf.resize(readSize);
325     lseek(fd, 0, SEEK_SET);
326     size_t r = ::read(fd, readBuf.data(), readSize);
327     if (r != 1)
328     {
329         std::cerr << "Error reading gpio\n";
330     }
331     else
332     {
333         bool value = std::stoi(readBuf);
334         if (inverted)
335         {
336             value = !value;
337         }
338         status = value;
339     }
340 }
341 
342 bool PresenceSensor::getValue(void)
343 {
344     return status;
345 }