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