xref: /openbmc/dbus-sensors/src/TachSensor.cpp (revision 7b7a9dea)
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/asio/read_until.hpp>
24 #include <boost/date_time/posix_time/posix_time.hpp>
25 #include <gpiod.hpp>
26 #include <sdbusplus/asio/connection.hpp>
27 #include <sdbusplus/asio/object_server.hpp>
28 
29 #include <fstream>
30 #include <iostream>
31 #include <istream>
32 #include <limits>
33 #include <memory>
34 #include <optional>
35 #include <stdexcept>
36 #include <string>
37 #include <utility>
38 #include <vector>
39 
40 static constexpr unsigned int pwmPollMs = 500;
41 static constexpr size_t warnAfterErrorCount = 10;
42 
43 TachSensor::TachSensor(const std::string& path, const std::string& objectType,
44                        sdbusplus::asio::object_server& objectServer,
45                        std::shared_ptr<sdbusplus::asio::connection>& conn,
46                        std::unique_ptr<PresenceSensor>&& presenceSensor,
47                        std::optional<RedundancySensor>* redundancy,
48                        boost::asio::io_service& io, const std::string& fanName,
49                        std::vector<thresholds::Threshold>&& thresholdsIn,
50                        const std::string& sensorConfiguration,
51                        const std::pair<size_t, size_t>& limits,
52                        const PowerState& powerState,
53                        const std::optional<std::string>& ledIn) :
54     Sensor(boost::replace_all_copy(fanName, " ", "_"), std::move(thresholdsIn),
55            sensorConfiguration, objectType, limits.second, limits.first, conn,
56            powerState),
57     objServer(objectServer), redundancy(redundancy),
58     presence(std::move(presenceSensor)),
59     inputDev(io, open(path.c_str(), O_RDONLY)), waitTimer(io), path(path),
60     led(ledIn)
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     bool curLed = !status;
197     if (led && ledState != curLed)
198     {
199         ledState = curLed;
200         setLed(dbusConnection, *led, curLed);
201     }
202 }
203 
204 PresenceSensor::PresenceSensor(const std::string& gpioName, bool inverted,
205                                boost::asio::io_service& io,
206                                const std::string& name) :
207     inverted(inverted),
208     gpioLine(gpiod::find_line(gpioName)), gpioFd(io), name(name)
209 {
210     if (!gpioLine)
211     {
212         std::cerr << "Error requesting gpio: " << gpioName << "\n";
213         status = false;
214         return;
215     }
216 
217     try
218     {
219         gpioLine.request({"FanSensor", gpiod::line_request::EVENT_BOTH_EDGES,
220                           inverted ? gpiod::line_request::FLAG_ACTIVE_LOW : 0});
221         status = gpioLine.get_value();
222 
223         int gpioLineFd = gpioLine.event_get_fd();
224         if (gpioLineFd < 0)
225         {
226             std::cerr << "Failed to get " << gpioName << " fd\n";
227             return;
228         }
229 
230         gpioFd.assign(gpioLineFd);
231     }
232     catch (std::system_error&)
233     {
234         std::cerr << "Error reading gpio: " << gpioName << "\n";
235         status = false;
236         return;
237     }
238 
239     monitorPresence();
240 }
241 
242 PresenceSensor::~PresenceSensor()
243 {
244     gpioFd.close();
245     gpioLine.release();
246 }
247 
248 void PresenceSensor::monitorPresence(void)
249 {
250     gpioFd.async_wait(boost::asio::posix::stream_descriptor::wait_read,
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                           if (ec)
257                           {
258                               std::cerr << "Error on presence sensor " << name
259                                         << " \n";
260                               ;
261                           }
262                           else
263                           {
264                               read();
265                           }
266                           monitorPresence();
267                       });
268 }
269 
270 void PresenceSensor::read(void)
271 {
272     gpioLine.event_read();
273     status = gpioLine.get_value();
274     // Read is invoked when an edge event is detected by monitorPresence
275     if (status)
276     {
277         logFanInserted(name);
278     }
279     else
280     {
281         logFanRemoved(name);
282     }
283 }
284 
285 bool PresenceSensor::getValue(void)
286 {
287     return status;
288 }
289 
290 RedundancySensor::RedundancySensor(size_t count,
291                                    const std::vector<std::string>& children,
292                                    sdbusplus::asio::object_server& objectServer,
293                                    const std::string& sensorConfiguration) :
294     count(count),
295     iface(objectServer.add_interface(
296         "/xyz/openbmc_project/control/FanRedundancy/Tach",
297         "xyz.openbmc_project.Control.FanRedundancy")),
298     association(objectServer.add_interface(
299         "/xyz/openbmc_project/control/FanRedundancy/Tach",
300         association::interface)),
301     objectServer(objectServer)
302 {
303     createAssociation(association, sensorConfiguration);
304     iface->register_property("Collection", children);
305     iface->register_property("Status", std::string("Full"));
306     iface->register_property("AllowedFailures", static_cast<uint8_t>(count));
307     iface->initialize();
308 }
309 RedundancySensor::~RedundancySensor()
310 {
311     objectServer.remove_interface(association);
312     objectServer.remove_interface(iface);
313 }
314 void RedundancySensor::update(const std::string& name, bool failed)
315 {
316     statuses[name] = failed;
317     size_t failedCount = 0;
318 
319     std::string newState = redundancy::full;
320     for (const auto& status : statuses)
321     {
322         if (status.second)
323         {
324             failedCount++;
325         }
326         if (failedCount > count)
327         {
328             newState = redundancy::failed;
329             break;
330         }
331         if (failedCount)
332         {
333             newState = redundancy::degraded;
334         }
335     }
336     if (state != newState)
337     {
338         if (state == redundancy::full)
339         {
340             logFanRedundancyLost();
341         }
342         else if (newState == redundancy::full)
343         {
344             logFanRedundancyRestored();
345         }
346         state = newState;
347         iface->set_property("Status", state);
348     }
349 }
350