xref: /openbmc/dbus-sensors/src/TachSensor.cpp (revision 60c0ec78)
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                        const std::shared_ptr<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 (overridenState)
135                 {
136                     nvalue = overriddenValue;
137                 }
138                 if (nvalue != value)
139                 {
140                     updateValue(nvalue);
141                 }
142                 errCount = 0;
143             }
144             catch (const std::invalid_argument&)
145             {
146                 errCount++;
147             }
148         }
149         else
150         {
151             if (!isPowerOn())
152             {
153                 errCount = 0;
154                 updateValue(std::numeric_limits<double>::quiet_NaN());
155             }
156             else
157             {
158                 pollTime = sensorFailedPollTimeMs;
159                 errCount++;
160             }
161         }
162         if (errCount >= warnAfterErrorCount)
163         {
164             // only print once
165             if (errCount == warnAfterErrorCount)
166             {
167                 std::cerr << "Failure to read sensor " << name << " at " << path
168                           << " ec:" << err << "\n";
169             }
170             updateValue(0);
171         }
172     }
173     responseStream.clear();
174     inputDev.close();
175     int fd = open(path.c_str(), O_RDONLY);
176     if (fd <= 0)
177     {
178         return; // we're no longer valid
179     }
180     inputDev.assign(fd);
181     waitTimer.expires_from_now(boost::posix_time::milliseconds(pollTime));
182     waitTimer.async_wait([&](const boost::system::error_code& ec) {
183         if (ec == boost::asio::error::operation_aborted)
184         {
185             return; // we're being canceled
186         }
187         setupRead();
188     });
189 }
190 
191 void TachSensor::checkThresholds(void)
192 {
193     bool status = thresholds::checkThresholds(this);
194     if (redundancy)
195     {
196         redundancy->update("/xyz/openbmc_project/sensors/fan_tach/" + name,
197                            !status);
198     }
199 }
200 
201 PresenceSensor::PresenceSensor(const size_t index, bool inverted,
202                                boost::asio::io_service& io) :
203     inverted(inverted),
204     inputDev(io)
205 {
206     // todo: use gpiodaemon
207     std::string device = gpioPath + std::string("gpio") + std::to_string(index);
208     fd = open((device + "/value").c_str(), O_RDONLY);
209     if (fd < 0)
210     {
211         std::cerr << "Error opening gpio " << index << "\n";
212         return;
213     }
214 
215     std::ofstream deviceFile(device + "/edge");
216     if (!deviceFile.good())
217     {
218         std::cerr << "Error setting edge " << device << "\n";
219         return;
220     }
221     deviceFile << "both";
222     deviceFile.close();
223 
224     inputDev.assign(boost::asio::ip::tcp::v4(), fd);
225     monitorPresence();
226     read();
227 }
228 
229 PresenceSensor::~PresenceSensor()
230 {
231     inputDev.close();
232     close(fd);
233 }
234 
235 void PresenceSensor::monitorPresence(void)
236 {
237     inputDev.async_wait(boost::asio::ip::tcp::socket::wait_error,
238                         [this](const boost::system::error_code& ec) {
239                             if (ec == boost::system::errc::bad_file_descriptor)
240                             {
241                                 return; // we're being destroyed
242                             }
243                             else if (ec)
244                             {
245                                 std::cerr
246                                     << "Error on presence sensor socket\n";
247                             }
248                             else
249                             {
250                                 read();
251                             }
252                             monitorPresence();
253                         });
254 }
255 
256 void PresenceSensor::read(void)
257 {
258     constexpr size_t readSize = sizeof("0");
259     std::string readBuf;
260     readBuf.resize(readSize);
261     lseek(fd, 0, SEEK_SET);
262     size_t r = ::read(fd, readBuf.data(), readSize);
263     if (r != readSize)
264     {
265         std::cerr << "Error reading gpio\n";
266     }
267     else
268     {
269         bool value = std::stoi(readBuf);
270         if (inverted)
271         {
272             value = !value;
273         }
274         status = value;
275     }
276 }
277 
278 bool PresenceSensor::getValue(void)
279 {
280     return status;
281 }
282 
283 RedundancySensor::RedundancySensor(size_t count,
284                                    const std::vector<std::string>& children,
285                                    sdbusplus::asio::object_server& objectServer,
286                                    const std::string& sensorConfiguration) :
287     count(count),
288     iface(objectServer.add_interface(
289         "/xyz/openbmc_project/control/FanRedundancy/Tach",
290         "xyz.openbmc_project.Control.FanRedundancy")),
291     association(objectServer.add_interface(
292         "/xyz/openbmc_project/control/FanRedundancy/Tach",
293         "org.openbmc.Associations")),
294     objectServer(objectServer)
295 {
296     createAssociation(association, sensorConfiguration);
297     iface->register_property("Collection", children);
298     iface->register_property("Status", std::string("Full"));
299     iface->register_property("AllowedFailures", static_cast<uint8_t>(count));
300     iface->initialize();
301 }
302 RedundancySensor::~RedundancySensor()
303 {
304     objectServer.remove_interface(association);
305     objectServer.remove_interface(iface);
306 }
307 void RedundancySensor::update(const std::string& name, bool failed)
308 {
309     statuses[name] = failed;
310     size_t failedCount = 0;
311 
312     std::string state = "Full";
313     for (const auto& status : statuses)
314     {
315         if (status.second)
316         {
317             failedCount++;
318         }
319         if (failedCount > count)
320         {
321             state = "Failed";
322             break;
323         }
324         else if (failedCount)
325         {
326             state = "Degraded";
327         }
328     }
329     iface->set_property("Status", state);
330 }
331