xref: /openbmc/dbus-sensors/src/TachSensor.cpp (revision 7b18b1e0)
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/algorithm/string/replace.hpp>
23 #include <boost/date_time/posix_time/posix_time.hpp>
24 #include <fstream>
25 #include <iostream>
26 #include <limits>
27 #include <sdbusplus/asio/connection.hpp>
28 #include <sdbusplus/asio/object_server.hpp>
29 #include <string>
30 
31 static constexpr unsigned int pwmPollMs = 500;
32 static constexpr size_t warnAfterErrorCount = 10;
33 
34 TachSensor::TachSensor(const std::string& path, const std::string& objectType,
35                        sdbusplus::asio::object_server& objectServer,
36                        std::shared_ptr<sdbusplus::asio::connection>& conn,
37                        std::unique_ptr<PresenceSensor>&& presenceSensor,
38                        std::optional<RedundancySensor>* redundancy,
39                        boost::asio::io_service& io, const std::string& fanName,
40                        std::vector<thresholds::Threshold>&& _thresholds,
41                        const std::string& sensorConfiguration,
42                        const std::pair<size_t, size_t>& limits) :
43     Sensor(boost::replace_all_copy(fanName, " ", "_"), path,
44            std::move(_thresholds), sensorConfiguration, objectType,
45            limits.second, limits.first),
46     objServer(objectServer), presence(std::move(presenceSensor)),
47     redundancy(redundancy), inputDev(io, open(path.c_str(), O_RDONLY)),
48     waitTimer(io), errCount(0)
49 {
50     sensorInterface = objectServer.add_interface(
51         "/xyz/openbmc_project/sensors/fan_tach/" + name,
52         "xyz.openbmc_project.Sensor.Value");
53 
54     if (thresholds::hasWarningInterface(thresholds))
55     {
56         thresholdInterfaceWarning = objectServer.add_interface(
57             "/xyz/openbmc_project/sensors/fan_tach/" + name,
58             "xyz.openbmc_project.Sensor.Threshold.Warning");
59     }
60     if (thresholds::hasCriticalInterface(thresholds))
61     {
62         thresholdInterfaceCritical = objectServer.add_interface(
63             "/xyz/openbmc_project/sensors/fan_tach/" + name,
64             "xyz.openbmc_project.Sensor.Threshold.Critical");
65     }
66     association = objectServer.add_interface(
67         "/xyz/openbmc_project/sensors/fan_tach/" + name,
68         "org.openbmc.Associations");
69 
70     if (presence)
71     {
72         itemIface =
73             objectServer.add_interface("/xyz/openbmc_project/Inventory/" + name,
74                                        "xyz.openbmc_project.Inventory.Item");
75         itemIface->register_property("PrettyName",
76                                      std::string()); // unused property
77         itemIface->register_property("Present", true);
78         itemIface->initialize();
79     }
80     setInitialProperties(conn);
81     setupPowerMatch(conn);
82     setupRead();
83 }
84 
85 TachSensor::~TachSensor()
86 {
87     // close the input dev to cancel async operations
88     inputDev.close();
89     waitTimer.cancel();
90     objServer.remove_interface(thresholdInterfaceWarning);
91     objServer.remove_interface(thresholdInterfaceCritical);
92     objServer.remove_interface(sensorInterface);
93     objServer.remove_interface(association);
94     objServer.remove_interface(itemIface);
95 }
96 
97 void TachSensor::setupRead(void)
98 {
99     boost::asio::async_read_until(
100         inputDev, readBuf, '\n',
101         [&](const boost::system::error_code& ec,
102             std::size_t /*bytes_transfered*/) { handleResponse(ec); });
103 }
104 
105 void TachSensor::handleResponse(const boost::system::error_code& err)
106 {
107     if (err == boost::system::errc::bad_file_descriptor)
108     {
109         return; // we're being destroyed
110     }
111     bool missing = false;
112     size_t pollTime = pwmPollMs;
113     if (presence)
114     {
115         if (!presence->getValue())
116         {
117             updateValue(std::numeric_limits<double>::quiet_NaN());
118             missing = true;
119             pollTime = sensorFailedPollTimeMs;
120         }
121         itemIface->set_property("Present", !missing);
122     }
123     std::istream responseStream(&readBuf);
124     if (!missing)
125     {
126         if (!err)
127         {
128             std::string response;
129             try
130             {
131                 std::getline(responseStream, response);
132                 float nvalue = std::stof(response);
133                 responseStream.clear();
134                 if (nvalue != value)
135                 {
136                     updateValue(nvalue);
137                 }
138                 errCount = 0;
139             }
140             catch (const std::invalid_argument&)
141             {
142                 errCount++;
143             }
144         }
145         else
146         {
147             if (!isPowerOn())
148             {
149                 errCount = 0;
150                 updateValue(std::numeric_limits<double>::quiet_NaN());
151             }
152             else
153             {
154                 pollTime = sensorFailedPollTimeMs;
155                 errCount++;
156             }
157         }
158         if (errCount >= warnAfterErrorCount)
159         {
160             // only print once
161             if (errCount == warnAfterErrorCount)
162             {
163                 std::cerr << "Failure to read sensor " << name << " at " << path
164                           << " ec:" << err << "\n";
165             }
166             updateValue(0);
167         }
168     }
169     responseStream.clear();
170     inputDev.close();
171     int fd = open(path.c_str(), O_RDONLY);
172     if (fd <= 0)
173     {
174         return; // we're no longer valid
175     }
176     inputDev.assign(fd);
177     waitTimer.expires_from_now(boost::posix_time::milliseconds(pollTime));
178     waitTimer.async_wait([&](const boost::system::error_code& ec) {
179         if (ec == boost::asio::error::operation_aborted)
180         {
181             return; // we're being canceled
182         }
183         setupRead();
184     });
185 }
186 
187 void TachSensor::checkThresholds(void)
188 {
189     bool status = thresholds::checkThresholds(this);
190     if (redundancy && *redundancy)
191     {
192         (*redundancy)
193             ->update("/xyz/openbmc_project/sensors/fan_tach/" + name, !status);
194     }
195 }
196 
197 PresenceSensor::PresenceSensor(const size_t index, bool inverted,
198                                boost::asio::io_service& io,
199                                const std::string& name) :
200     inverted(inverted),
201     inputDev(io), name(name)
202 {
203     // todo: use gpiodaemon
204     std::string device = gpioPath + std::string("gpio") + std::to_string(index);
205     fd = open((device + "/value").c_str(), O_RDONLY);
206     if (fd < 0)
207     {
208         std::cerr << "Error opening gpio " << index << "\n";
209         return;
210     }
211 
212     std::ofstream deviceFile(device + "/edge");
213     if (!deviceFile.good())
214     {
215         std::cerr << "Error setting edge " << device << "\n";
216         return;
217     }
218     deviceFile << "both";
219     deviceFile.close();
220 
221     inputDev.assign(boost::asio::ip::tcp::v4(), fd);
222     monitorPresence();
223     read();
224 }
225 
226 PresenceSensor::~PresenceSensor()
227 {
228     inputDev.close();
229     close(fd);
230 }
231 
232 void PresenceSensor::monitorPresence(void)
233 {
234     inputDev.async_wait(boost::asio::ip::tcp::socket::wait_error,
235                         [this](const boost::system::error_code& ec) {
236                             if (ec == boost::system::errc::bad_file_descriptor)
237                             {
238                                 return; // we're being destroyed
239                             }
240                             else if (ec)
241                             {
242                                 std::cerr
243                                     << "Error on presence sensor socket\n";
244                             }
245                             else
246                             {
247                                 read();
248                             }
249                             monitorPresence();
250                         });
251 }
252 
253 void PresenceSensor::read(void)
254 {
255     constexpr size_t readSize = sizeof("0");
256     std::string readBuf;
257     readBuf.resize(readSize);
258     lseek(fd, 0, SEEK_SET);
259     size_t r = ::read(fd, readBuf.data(), readSize);
260     if (r != readSize)
261     {
262         std::cerr << "Error reading gpio\n";
263     }
264     else
265     {
266         bool value = std::stoi(readBuf);
267         if (inverted)
268         {
269             value = !value;
270         }
271         if (value != status)
272         {
273             status = value;
274             if (status)
275             {
276                 logFanInserted(name);
277             }
278             else
279             {
280                 logFanRemoved(name);
281             }
282         }
283     }
284 }
285 
286 bool PresenceSensor::getValue(void)
287 {
288     return status;
289 }
290 
291 RedundancySensor::RedundancySensor(size_t count,
292                                    const std::vector<std::string>& children,
293                                    sdbusplus::asio::object_server& objectServer,
294                                    const std::string& sensorConfiguration) :
295     count(count),
296     iface(objectServer.add_interface(
297         "/xyz/openbmc_project/control/FanRedundancy/Tach",
298         "xyz.openbmc_project.Control.FanRedundancy")),
299     association(objectServer.add_interface(
300         "/xyz/openbmc_project/control/FanRedundancy/Tach",
301         "org.openbmc.Associations")),
302     objectServer(objectServer)
303 {
304     createAssociation(association, sensorConfiguration);
305     iface->register_property("Collection", children);
306     iface->register_property("Status", std::string("Full"));
307     iface->register_property("AllowedFailures", static_cast<uint8_t>(count));
308     iface->initialize();
309 }
310 RedundancySensor::~RedundancySensor()
311 {
312     objectServer.remove_interface(association);
313     objectServer.remove_interface(iface);
314 }
315 void RedundancySensor::update(const std::string& name, bool failed)
316 {
317     statuses[name] = failed;
318     size_t failedCount = 0;
319 
320     std::string newState = redundancy::full;
321     for (const auto& status : statuses)
322     {
323         if (status.second)
324         {
325             failedCount++;
326         }
327         if (failedCount > count)
328         {
329             newState = redundancy::failed;
330             break;
331         }
332         else if (failedCount)
333         {
334             newState = redundancy::degraded;
335         }
336     }
337     if (state != newState)
338     {
339         if (state == redundancy::full)
340         {
341             logFanRedundancyLost();
342         }
343         else if (newState == redundancy::full)
344         {
345             logFanRedundancyRestored();
346         }
347         state = newState;
348         iface->set_property("Status", state);
349     }
350 }
351