xref: /openbmc/dbus-sensors/src/TachSensor.cpp (revision 2adc95cb)
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 "TachSensor.hpp"
18 
19 #include "Utils.hpp"
20 
21 #include <unistd.h>
22 
23 #include <boost/algorithm/string/predicate.hpp>
24 #include <boost/algorithm/string/replace.hpp>
25 #include <boost/date_time/posix_time/posix_time.hpp>
26 #include <fstream>
27 #include <iostream>
28 #include <limits>
29 #include <sdbusplus/asio/connection.hpp>
30 #include <sdbusplus/asio/object_server.hpp>
31 #include <string>
32 
33 static constexpr unsigned int pwmPollMs = 500;
34 static constexpr size_t warnAfterErrorCount = 10;
35 
36 TachSensor::TachSensor(const std::string& path, const std::string& objectType,
37                        sdbusplus::asio::object_server& objectServer,
38                        std::shared_ptr<sdbusplus::asio::connection>& conn,
39                        std::unique_ptr<PresenceSensor>&& presenceSensor,
40                        std::optional<RedundancySensor>* redundancy,
41                        boost::asio::io_service& io, const std::string& fanName,
42                        std::vector<thresholds::Threshold>&& _thresholds,
43                        const std::string& sensorConfiguration,
44                        const std::pair<size_t, size_t>& limits) :
45     Sensor(boost::replace_all_copy(fanName, " ", "_"), std::move(_thresholds),
46            sensorConfiguration, objectType, limits.second, limits.first),
47     path(path), objServer(objectServer), presence(std::move(presenceSensor)),
48     redundancy(redundancy), inputDev(io, open(path.c_str(), O_RDONLY)),
49     waitTimer(io), errCount(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     association = objectServer.add_interface(
68         "/xyz/openbmc_project/sensors/fan_tach/" + name,
69         association::interface);
70 
71     if (presence)
72     {
73         itemIface =
74             objectServer.add_interface("/xyz/openbmc_project/inventory/" + name,
75                                        "xyz.openbmc_project.Inventory.Item");
76         itemIface->register_property("PrettyName",
77                                      std::string()); // unused property
78         itemIface->register_property("Present", true);
79         itemIface->initialize();
80         itemAssoc = objectServer.add_interface(
81             "/xyz/openbmc_project/inventory/" + name, association::interface);
82         itemAssoc->register_property(
83             "associations",
84             std::vector<Association>{
85                 {"sensors", "inventory",
86                  "/xyz/openbmc_project/sensors/fan_tach/" + name}});
87         itemAssoc->initialize();
88     }
89     setInitialProperties(conn);
90     setupPowerMatch(conn);
91     setupRead();
92 }
93 
94 TachSensor::~TachSensor()
95 {
96     // close the input dev to cancel async operations
97     inputDev.close();
98     waitTimer.cancel();
99     objServer.remove_interface(thresholdInterfaceWarning);
100     objServer.remove_interface(thresholdInterfaceCritical);
101     objServer.remove_interface(sensorInterface);
102     objServer.remove_interface(association);
103     objServer.remove_interface(itemIface);
104     objServer.remove_interface(itemAssoc);
105 }
106 
107 void TachSensor::setupRead(void)
108 {
109     boost::asio::async_read_until(
110         inputDev, readBuf, '\n',
111         [&](const boost::system::error_code& ec,
112             std::size_t /*bytes_transfered*/) { handleResponse(ec); });
113 }
114 
115 void TachSensor::handleResponse(const boost::system::error_code& err)
116 {
117     if (err == boost::system::errc::bad_file_descriptor)
118     {
119         return; // we're being destroyed
120     }
121     bool missing = false;
122     size_t pollTime = pwmPollMs;
123     if (presence)
124     {
125         if (!presence->getValue())
126         {
127             updateValue(std::numeric_limits<double>::quiet_NaN());
128             missing = true;
129             pollTime = sensorFailedPollTimeMs;
130         }
131         itemIface->set_property("Present", !missing);
132     }
133     std::istream responseStream(&readBuf);
134     if (!missing)
135     {
136         if (!err)
137         {
138             std::string response;
139             try
140             {
141                 std::getline(responseStream, response);
142                 float nvalue = std::stof(response);
143                 responseStream.clear();
144                 if (static_cast<double>(nvalue) != value)
145                 {
146                     updateValue(nvalue);
147                 }
148                 errCount = 0;
149             }
150             catch (const std::invalid_argument&)
151             {
152                 errCount++;
153             }
154         }
155         else
156         {
157             if (!isPowerOn())
158             {
159                 errCount = 0;
160                 updateValue(std::numeric_limits<double>::quiet_NaN());
161             }
162             else
163             {
164                 pollTime = sensorFailedPollTimeMs;
165                 errCount++;
166             }
167         }
168         if (errCount >= warnAfterErrorCount)
169         {
170             // only print once
171             if (errCount == warnAfterErrorCount)
172             {
173                 std::cerr << "Failure to read sensor " << name << " at " << path
174                           << " ec:" << err << "\n";
175             }
176             updateValue(0);
177         }
178     }
179     responseStream.clear();
180     inputDev.close();
181     int fd = open(path.c_str(), O_RDONLY);
182     if (fd <= 0)
183     {
184         return; // we're no longer valid
185     }
186     inputDev.assign(fd);
187     waitTimer.expires_from_now(boost::posix_time::milliseconds(pollTime));
188     waitTimer.async_wait([&](const boost::system::error_code& ec) {
189         if (ec == boost::asio::error::operation_aborted)
190         {
191             return; // we're being canceled
192         }
193         setupRead();
194     });
195 }
196 
197 void TachSensor::checkThresholds(void)
198 {
199     if (!isPowerOn())
200     {
201         return;
202     }
203 
204     bool status = thresholds::checkThresholds(this);
205 
206     if (redundancy && *redundancy)
207     {
208         (*redundancy)
209             ->update("/xyz/openbmc_project/sensors/fan_tach/" + name, !status);
210     }
211 }
212 
213 PresenceSensor::PresenceSensor(const size_t index, bool inverted,
214                                boost::asio::io_service& io,
215                                const std::string& name) :
216     inverted(inverted),
217     inputDev(io), name(name)
218 {
219     // todo: use gpiodaemon
220     std::string device = gpioPath + std::string("gpio") + std::to_string(index);
221     fd = open((device + "/value").c_str(), O_RDONLY);
222     if (fd < 0)
223     {
224         std::cerr << "Error opening gpio " << index << "\n";
225         return;
226     }
227 
228     std::ofstream deviceFile(device + "/edge");
229     if (!deviceFile.good())
230     {
231         std::cerr << "Error setting edge " << device << "\n";
232         return;
233     }
234     deviceFile << "both";
235     deviceFile.close();
236 
237     inputDev.assign(boost::asio::ip::tcp::v4(), fd);
238     monitorPresence();
239     read();
240 }
241 
242 PresenceSensor::~PresenceSensor()
243 {
244     inputDev.close();
245     close(fd);
246 }
247 
248 void PresenceSensor::monitorPresence(void)
249 {
250     inputDev.async_wait(boost::asio::ip::tcp::socket::wait_error,
251                         [this](const boost::system::error_code& ec) {
252                             if (ec == boost::system::errc::bad_file_descriptor)
253                             {
254                                 return; // we're being destroyed
255                             }
256                             else if (ec)
257                             {
258                                 std::cerr
259                                     << "Error on presence sensor socket\n";
260                             }
261                             else
262                             {
263                                 read();
264                             }
265                             monitorPresence();
266                         });
267 }
268 
269 void PresenceSensor::read(void)
270 {
271     constexpr size_t readSize = sizeof("0");
272     std::string readBuf;
273     readBuf.resize(readSize);
274     lseek(fd, 0, SEEK_SET);
275     size_t r = ::read(fd, readBuf.data(), readSize);
276     if (r != readSize)
277     {
278         std::cerr << "Error reading gpio\n";
279     }
280     else
281     {
282         bool value = std::stoi(readBuf);
283         if (inverted)
284         {
285             value = !value;
286         }
287         if (value != status)
288         {
289             status = value;
290             if (status)
291             {
292                 logFanInserted(name);
293             }
294             else
295             {
296                 logFanRemoved(name);
297             }
298         }
299     }
300 }
301 
302 bool PresenceSensor::getValue(void)
303 {
304     return status;
305 }
306 
307 RedundancySensor::RedundancySensor(size_t count,
308                                    const std::vector<std::string>& children,
309                                    sdbusplus::asio::object_server& objectServer,
310                                    const std::string& sensorConfiguration) :
311     count(count),
312     iface(objectServer.add_interface(
313         "/xyz/openbmc_project/control/FanRedundancy/Tach",
314         "xyz.openbmc_project.Control.FanRedundancy")),
315     association(objectServer.add_interface(
316         "/xyz/openbmc_project/control/FanRedundancy/Tach",
317         association::interface)),
318     objectServer(objectServer)
319 {
320     createAssociation(association, sensorConfiguration);
321     iface->register_property("Collection", children);
322     iface->register_property("Status", std::string("Full"));
323     iface->register_property("AllowedFailures", static_cast<uint8_t>(count));
324     iface->initialize();
325 }
326 RedundancySensor::~RedundancySensor()
327 {
328     objectServer.remove_interface(association);
329     objectServer.remove_interface(iface);
330 }
331 void RedundancySensor::update(const std::string& name, bool failed)
332 {
333     statuses[name] = failed;
334     size_t failedCount = 0;
335 
336     std::string newState = redundancy::full;
337     for (const auto& status : statuses)
338     {
339         if (status.second)
340         {
341             failedCount++;
342         }
343         if (failedCount > count)
344         {
345             newState = redundancy::failed;
346             break;
347         }
348         else if (failedCount)
349         {
350             newState = redundancy::degraded;
351         }
352     }
353     if (state != newState)
354     {
355         if (state == redundancy::full)
356         {
357             logFanRedundancyLost();
358         }
359         else if (newState == redundancy::full)
360         {
361             logFanRedundancyRestored();
362         }
363         state = newState;
364         iface->set_property("Status", state);
365     }
366 }
367