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