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