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