xref: /openbmc/dbus-sensors/src/TachSensor.cpp (revision 99c4409a)
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(
122         inputDev, readBuf, '\n',
123         [&](const boost::system::error_code& ec,
124             std::size_t /*bytes_transfered*/) { handleResponse(ec); });
125 }
126 
127 void TachSensor::handleResponse(const boost::system::error_code& err)
128 {
129     if (err == boost::system::errc::bad_file_descriptor)
130     {
131         return; // we're being destroyed
132     }
133     bool missing = false;
134     size_t pollTime = pwmPollMs;
135     if (presence)
136     {
137         if (!presence->getValue())
138         {
139             markAvailable(false);
140             missing = true;
141             pollTime = sensorFailedPollTimeMs;
142         }
143         itemIface->set_property("Present", !missing);
144     }
145     std::istream responseStream(&readBuf);
146     if (!missing)
147     {
148         if (!err)
149         {
150             std::string response;
151             try
152             {
153                 std::getline(responseStream, response);
154                 rawValue = std::stod(response);
155                 responseStream.clear();
156                 updateValue(rawValue);
157             }
158             catch (const std::invalid_argument&)
159             {
160                 incrementError();
161                 pollTime = sensorFailedPollTimeMs;
162             }
163         }
164         else
165         {
166             incrementError();
167             pollTime = sensorFailedPollTimeMs;
168         }
169     }
170     responseStream.clear();
171     inputDev.close();
172 
173     // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
174     int fd = open(path.c_str(), O_RDONLY);
175     if (fd < 0)
176     {
177         return; // we're no longer valid
178     }
179     inputDev.assign(fd);
180     waitTimer.expires_from_now(boost::posix_time::milliseconds(pollTime));
181     waitTimer.async_wait([&](const boost::system::error_code& ec) {
182         if (ec == boost::asio::error::operation_aborted)
183         {
184             return; // we're being canceled
185         }
186         setupRead();
187     });
188 }
189 
190 void TachSensor::checkThresholds(void)
191 {
192     bool status = thresholds::checkThresholds(this);
193 
194     if (redundancy && *redundancy)
195     {
196         (*redundancy)
197             ->update("/xyz/openbmc_project/sensors/fan_tach/" + name, !status);
198     }
199 
200     bool curLed = !status;
201     if (led && ledState != curLed)
202     {
203         ledState = curLed;
204         setLed(dbusConnection, *led, curLed);
205     }
206 }
207 
208 PresenceSensor::PresenceSensor(const std::string& gpioName, bool inverted,
209                                boost::asio::io_service& io,
210                                const std::string& name) :
211     gpioLine(gpiod::find_line(gpioName)),
212     gpioFd(io), name(name)
213 {
214     if (!gpioLine)
215     {
216         std::cerr << "Error requesting gpio: " << gpioName << "\n";
217         status = false;
218         return;
219     }
220 
221     try
222     {
223         gpioLine.request({"FanSensor", gpiod::line_request::EVENT_BOTH_EDGES,
224                           inverted ? gpiod::line_request::FLAG_ACTIVE_LOW : 0});
225         status = gpioLine.get_value();
226 
227         int gpioLineFd = gpioLine.event_get_fd();
228         if (gpioLineFd < 0)
229         {
230             std::cerr << "Failed to get " << gpioName << " fd\n";
231             return;
232         }
233 
234         gpioFd.assign(gpioLineFd);
235     }
236     catch (const std::system_error&)
237     {
238         std::cerr << "Error reading gpio: " << gpioName << "\n";
239         status = false;
240         return;
241     }
242 
243     monitorPresence();
244 }
245 
246 PresenceSensor::~PresenceSensor()
247 {
248     gpioFd.close();
249     gpioLine.release();
250 }
251 
252 void PresenceSensor::monitorPresence(void)
253 {
254     gpioFd.async_wait(boost::asio::posix::stream_descriptor::wait_read,
255                       [this](const boost::system::error_code& ec) {
256                           if (ec == boost::system::errc::bad_file_descriptor)
257                           {
258                               return; // we're being destroyed
259                           }
260                           if (ec)
261                           {
262                               std::cerr << "Error on presence sensor " << name
263                                         << " \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