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