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