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