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