xref: /openbmc/dbus-sensors/src/TachSensor.cpp (revision 8a17c303)
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 
42 TachSensor::TachSensor(const std::string& path, const std::string& objectType,
43                        sdbusplus::asio::object_server& objectServer,
44                        std::shared_ptr<sdbusplus::asio::connection>& conn,
45                        std::unique_ptr<PresenceSensor>&& presenceSensor,
46                        std::optional<RedundancySensor>* redundancy,
47                        boost::asio::io_service& io, const std::string& fanName,
48                        std::vector<thresholds::Threshold>&& thresholdsIn,
49                        const std::string& sensorConfiguration,
50                        const std::pair<size_t, size_t>& limits,
51                        const PowerState& powerState,
52                        const std::optional<std::string>& ledIn) :
53     Sensor(boost::replace_all_copy(fanName, " ", "_"), std::move(thresholdsIn),
54            sensorConfiguration, objectType, false, limits.second, limits.first,
55            conn, powerState),
56     objServer(objectServer), redundancy(redundancy),
57     presence(std::move(presenceSensor)),
58     inputDev(io, open(path.c_str(), O_RDONLY)), waitTimer(io), path(path),
59     led(ledIn)
60 {
61     sensorInterface = objectServer.add_interface(
62         "/xyz/openbmc_project/sensors/fan_tach/" + name,
63         "xyz.openbmc_project.Sensor.Value");
64 
65     if (thresholds::hasWarningInterface(thresholds))
66     {
67         thresholdInterfaceWarning = objectServer.add_interface(
68             "/xyz/openbmc_project/sensors/fan_tach/" + name,
69             "xyz.openbmc_project.Sensor.Threshold.Warning");
70     }
71     if (thresholds::hasCriticalInterface(thresholds))
72     {
73         thresholdInterfaceCritical = objectServer.add_interface(
74             "/xyz/openbmc_project/sensors/fan_tach/" + name,
75             "xyz.openbmc_project.Sensor.Threshold.Critical");
76     }
77     association = objectServer.add_interface(
78         "/xyz/openbmc_project/sensors/fan_tach/" + name,
79         association::interface);
80 
81     if (presence)
82     {
83         itemIface =
84             objectServer.add_interface("/xyz/openbmc_project/inventory/" + name,
85                                        "xyz.openbmc_project.Inventory.Item");
86         itemIface->register_property("PrettyName",
87                                      std::string()); // unused property
88         itemIface->register_property("Present", true);
89         itemIface->initialize();
90         itemAssoc = objectServer.add_interface(
91             "/xyz/openbmc_project/inventory/" + name, association::interface);
92         itemAssoc->register_property(
93             "associations",
94             std::vector<Association>{
95                 {"sensors", "inventory",
96                  "/xyz/openbmc_project/sensors/fan_tach/" + name}});
97         itemAssoc->initialize();
98     }
99     setInitialProperties(conn, sensor_paths::unitRPMs);
100     setupRead();
101 }
102 
103 TachSensor::~TachSensor()
104 {
105     // close the input dev to cancel async operations
106     inputDev.close();
107     waitTimer.cancel();
108     objServer.remove_interface(thresholdInterfaceWarning);
109     objServer.remove_interface(thresholdInterfaceCritical);
110     objServer.remove_interface(sensorInterface);
111     objServer.remove_interface(association);
112     objServer.remove_interface(itemIface);
113     objServer.remove_interface(itemAssoc);
114 }
115 
116 void TachSensor::setupRead(void)
117 {
118     boost::asio::async_read_until(
119         inputDev, readBuf, '\n',
120         [&](const boost::system::error_code& ec,
121             std::size_t /*bytes_transfered*/) { handleResponse(ec); });
122 }
123 
124 void TachSensor::handleResponse(const boost::system::error_code& err)
125 {
126     if (err == boost::system::errc::bad_file_descriptor)
127     {
128         return; // we're being destroyed
129     }
130     bool missing = false;
131     size_t pollTime = pwmPollMs;
132     if (presence)
133     {
134         if (!presence->getValue())
135         {
136             markAvailable(false);
137             missing = true;
138             pollTime = sensorFailedPollTimeMs;
139         }
140         itemIface->set_property("Present", !missing);
141     }
142     std::istream responseStream(&readBuf);
143     if (!missing)
144     {
145         if (!err)
146         {
147             std::string response;
148             try
149             {
150                 std::getline(responseStream, response);
151                 rawValue = std::stod(response);
152                 responseStream.clear();
153                 updateValue(rawValue);
154             }
155             catch (const std::invalid_argument&)
156             {
157                 incrementError();
158                 pollTime = sensorFailedPollTimeMs;
159             }
160         }
161         else
162         {
163             incrementError();
164             pollTime = sensorFailedPollTimeMs;
165         }
166     }
167     responseStream.clear();
168     inputDev.close();
169     int fd = open(path.c_str(), O_RDONLY);
170     if (fd < 0)
171     {
172         return; // we're no longer valid
173     }
174     inputDev.assign(fd);
175     waitTimer.expires_from_now(boost::posix_time::milliseconds(pollTime));
176     waitTimer.async_wait([&](const boost::system::error_code& ec) {
177         if (ec == boost::asio::error::operation_aborted)
178         {
179             return; // we're being canceled
180         }
181         setupRead();
182     });
183 }
184 
185 void TachSensor::checkThresholds(void)
186 {
187     bool status = thresholds::checkThresholds(this);
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 (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