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