xref: /openbmc/dbus-sensors/src/TachSensor.cpp (revision 3928741a)
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/asio/read_until.hpp>
23 #include <boost/date_time/posix_time/posix_time.hpp>
24 #include <gpiod.hpp>
25 #include <sdbusplus/asio/connection.hpp>
26 #include <sdbusplus/asio/object_server.hpp>
27 
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, open(path.c_str(), O_RDONLY)), waitTimer(io), path(path),
58     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     setupRead();
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(void)
113 {
114     boost::asio::async_read_until(
115         inputDev, readBuf, '\n',
116         [&](const boost::system::error_code& ec,
117             std::size_t /*bytes_transfered*/) { handleResponse(ec); });
118 }
119 
120 void TachSensor::handleResponse(const boost::system::error_code& err)
121 {
122     if (err == boost::system::errc::bad_file_descriptor)
123     {
124         return; // we're being destroyed
125     }
126     bool missing = false;
127     size_t pollTime = pwmPollMs;
128     if (presence)
129     {
130         if (!presence->getValue())
131         {
132             markAvailable(false);
133             missing = true;
134             pollTime = sensorFailedPollTimeMs;
135         }
136         itemIface->set_property("Present", !missing);
137     }
138     std::istream responseStream(&readBuf);
139     if (!missing)
140     {
141         if (!err)
142         {
143             std::string response;
144             try
145             {
146                 std::getline(responseStream, response);
147                 rawValue = std::stod(response);
148                 responseStream.clear();
149                 updateValue(rawValue);
150             }
151             catch (const std::invalid_argument&)
152             {
153                 incrementError();
154                 pollTime = sensorFailedPollTimeMs;
155             }
156         }
157         else
158         {
159             incrementError();
160             pollTime = sensorFailedPollTimeMs;
161         }
162     }
163     responseStream.clear();
164     inputDev.close();
165     int fd = open(path.c_str(), O_RDONLY);
166     if (fd < 0)
167     {
168         return; // we're no longer valid
169     }
170     inputDev.assign(fd);
171     waitTimer.expires_from_now(boost::posix_time::milliseconds(pollTime));
172     waitTimer.async_wait([&](const boost::system::error_code& ec) {
173         if (ec == boost::asio::error::operation_aborted)
174         {
175             return; // we're being canceled
176         }
177         setupRead();
178     });
179 }
180 
181 void TachSensor::checkThresholds(void)
182 {
183     bool status = thresholds::checkThresholds(this);
184 
185     if (redundancy && *redundancy)
186     {
187         (*redundancy)
188             ->update("/xyz/openbmc_project/sensors/fan_tach/" + name, !status);
189     }
190 
191     bool curLed = !status;
192     if (led && ledState != curLed)
193     {
194         ledState = curLed;
195         setLed(dbusConnection, *led, curLed);
196     }
197 }
198 
199 PresenceSensor::PresenceSensor(const std::string& gpioName, bool inverted,
200                                boost::asio::io_service& io,
201                                const std::string& name) :
202     gpioLine(gpiod::find_line(gpioName)),
203     gpioFd(io), name(name)
204 {
205     if (!gpioLine)
206     {
207         std::cerr << "Error requesting gpio: " << gpioName << "\n";
208         status = false;
209         return;
210     }
211 
212     try
213     {
214         gpioLine.request({"FanSensor", gpiod::line_request::EVENT_BOTH_EDGES,
215                           inverted ? gpiod::line_request::FLAG_ACTIVE_LOW : 0});
216         status = gpioLine.get_value();
217 
218         int gpioLineFd = gpioLine.event_get_fd();
219         if (gpioLineFd < 0)
220         {
221             std::cerr << "Failed to get " << gpioName << " fd\n";
222             return;
223         }
224 
225         gpioFd.assign(gpioLineFd);
226     }
227     catch (const std::system_error&)
228     {
229         std::cerr << "Error reading gpio: " << gpioName << "\n";
230         status = false;
231         return;
232     }
233 
234     monitorPresence();
235 }
236 
237 PresenceSensor::~PresenceSensor()
238 {
239     gpioFd.close();
240     gpioLine.release();
241 }
242 
243 void PresenceSensor::monitorPresence(void)
244 {
245     gpioFd.async_wait(boost::asio::posix::stream_descriptor::wait_read,
246                       [this](const boost::system::error_code& ec) {
247                           if (ec == boost::system::errc::bad_file_descriptor)
248                           {
249                               return; // we're being destroyed
250                           }
251                           if (ec)
252                           {
253                               std::cerr << "Error on presence sensor " << name
254                                         << " \n";
255                               ;
256                           }
257                           else
258                           {
259                               read();
260                           }
261                           monitorPresence();
262                       });
263 }
264 
265 void PresenceSensor::read(void)
266 {
267     gpioLine.event_read();
268     status = gpioLine.get_value();
269     // Read is invoked when an edge event is detected by monitorPresence
270     if (status)
271     {
272         logFanInserted(name);
273     }
274     else
275     {
276         logFanRemoved(name);
277     }
278 }
279 
280 bool PresenceSensor::getValue(void)
281 {
282     return status;
283 }
284 
285 RedundancySensor::RedundancySensor(size_t count,
286                                    const std::vector<std::string>& children,
287                                    sdbusplus::asio::object_server& objectServer,
288                                    const std::string& sensorConfiguration) :
289     count(count),
290     iface(objectServer.add_interface(
291         "/xyz/openbmc_project/control/FanRedundancy/Tach",
292         "xyz.openbmc_project.Control.FanRedundancy")),
293     association(objectServer.add_interface(
294         "/xyz/openbmc_project/control/FanRedundancy/Tach",
295         association::interface)),
296     objectServer(objectServer)
297 {
298     createAssociation(association, sensorConfiguration);
299     iface->register_property("Collection", children);
300     iface->register_property("Status", std::string("Full"));
301     iface->register_property("AllowedFailures", static_cast<uint8_t>(count));
302     iface->initialize();
303 }
304 RedundancySensor::~RedundancySensor()
305 {
306     objectServer.remove_interface(association);
307     objectServer.remove_interface(iface);
308 }
309 void RedundancySensor::update(const std::string& name, bool failed)
310 {
311     statuses[name] = failed;
312     size_t failedCount = 0;
313 
314     std::string newState = redundancy::full;
315     for (const auto& status : statuses)
316     {
317         if (status.second)
318         {
319             failedCount++;
320         }
321         if (failedCount > count)
322         {
323             newState = redundancy::failed;
324             break;
325         }
326         if (failedCount)
327         {
328             newState = redundancy::degraded;
329         }
330     }
331     if (state != newState)
332     {
333         if (state == redundancy::full)
334         {
335             logFanRedundancyLost();
336         }
337         else if (newState == redundancy::full)
338         {
339             logFanRedundancyRestored();
340         }
341         state = newState;
342         iface->set_property("Status", state);
343     }
344 }
345