xref: /openbmc/dbus-sensors/src/TachSensor.cpp (revision 9bb67462)
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>&& presence,
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(presence)),
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     setInitialProperties(conn);
70     setupPowerMatch(conn);
71     setupRead();
72 }
73 
74 TachSensor::~TachSensor()
75 {
76     // close the input dev to cancel async operations
77     inputDev.close();
78     waitTimer.cancel();
79     objServer.remove_interface(thresholdInterfaceWarning);
80     objServer.remove_interface(thresholdInterfaceCritical);
81     objServer.remove_interface(sensorInterface);
82     objServer.remove_interface(association);
83 }
84 
85 void TachSensor::setupRead(void)
86 {
87     boost::asio::async_read_until(
88         inputDev, readBuf, '\n',
89         [&](const boost::system::error_code& ec,
90             std::size_t /*bytes_transfered*/) { handleResponse(ec); });
91 }
92 
93 void TachSensor::handleResponse(const boost::system::error_code& err)
94 {
95     if (err == boost::system::errc::bad_file_descriptor)
96     {
97         return; // we're being destroyed
98     }
99     bool missing = false;
100     size_t pollTime = pwmPollMs;
101     if (presence)
102     {
103         if (!presence->getValue())
104         {
105             updateValue(std::numeric_limits<double>::quiet_NaN());
106             missing = true;
107             pollTime = sensorFailedPollTimeMs;
108         }
109     }
110     std::istream responseStream(&readBuf);
111     if (!missing)
112     {
113         if (!err)
114         {
115             std::string response;
116             try
117             {
118                 std::getline(responseStream, response);
119                 float nvalue = std::stof(response);
120                 responseStream.clear();
121                 if (overridenState)
122                 {
123                     nvalue = overriddenValue;
124                 }
125                 if (nvalue != value)
126                 {
127                     updateValue(nvalue);
128                 }
129                 errCount = 0;
130             }
131             catch (const std::invalid_argument&)
132             {
133                 errCount++;
134             }
135         }
136         else
137         {
138             if (!isPowerOn())
139             {
140                 errCount = 0;
141                 updateValue(std::numeric_limits<double>::quiet_NaN());
142             }
143             else
144             {
145                 pollTime = sensorFailedPollTimeMs;
146                 errCount++;
147             }
148         }
149         if (errCount >= warnAfterErrorCount)
150         {
151             // only print once
152             if (errCount == warnAfterErrorCount)
153             {
154                 std::cerr << "Failure to read sensor " << name << " at " << path
155                           << " ec:" << err << "\n";
156             }
157             updateValue(0);
158         }
159     }
160     responseStream.clear();
161     inputDev.close();
162     int fd = open(path.c_str(), O_RDONLY);
163     if (fd <= 0)
164     {
165         return; // we're no longer valid
166     }
167     inputDev.assign(fd);
168     waitTimer.expires_from_now(boost::posix_time::milliseconds(pollTime));
169     waitTimer.async_wait([&](const boost::system::error_code& ec) {
170         if (ec == boost::asio::error::operation_aborted)
171         {
172             return; // we're being canceled
173         }
174         setupRead();
175     });
176 }
177 
178 void TachSensor::checkThresholds(void)
179 {
180     bool status = thresholds::checkThresholds(this);
181     if (redundancy)
182     {
183         redundancy->update("/xyz/openbmc_project/sensors/fan_tach/" + name,
184                            !status);
185     }
186 }
187 
188 PresenceSensor::PresenceSensor(const size_t index, bool inverted,
189                                boost::asio::io_service& io) :
190     inverted(inverted),
191     inputDev(io)
192 {
193     // todo: use gpiodaemon
194     std::string device = gpioPath + std::string("gpio") + std::to_string(index);
195     fd = open((device + "/value").c_str(), O_RDONLY);
196     if (fd < 0)
197     {
198         std::cerr << "Error opening gpio " << index << "\n";
199         return;
200     }
201 
202     std::ofstream deviceFile(device + "/edge");
203     if (!deviceFile.good())
204     {
205         std::cerr << "Error setting edge " << device << "\n";
206         return;
207     }
208     deviceFile << "both";
209     deviceFile.close();
210 
211     inputDev.assign(boost::asio::ip::tcp::v4(), fd);
212     monitorPresence();
213     read();
214 }
215 
216 PresenceSensor::~PresenceSensor()
217 {
218     inputDev.close();
219     close(fd);
220 }
221 
222 void PresenceSensor::monitorPresence(void)
223 {
224     inputDev.async_wait(boost::asio::ip::tcp::socket::wait_error,
225                         [this](const boost::system::error_code& ec) {
226                             if (ec == boost::system::errc::bad_file_descriptor)
227                             {
228                                 return; // we're being destroyed
229                             }
230                             else if (ec)
231                             {
232                                 std::cerr
233                                     << "Error on presence sensor socket\n";
234                             }
235                             else
236                             {
237                                 read();
238                             }
239                             monitorPresence();
240                         });
241 }
242 
243 void PresenceSensor::read(void)
244 {
245     constexpr size_t readSize = sizeof("0");
246     std::string readBuf;
247     readBuf.resize(readSize);
248     lseek(fd, 0, SEEK_SET);
249     size_t r = ::read(fd, readBuf.data(), readSize);
250     if (r != readSize)
251     {
252         std::cerr << "Error reading gpio\n";
253     }
254     else
255     {
256         bool value = std::stoi(readBuf);
257         if (inverted)
258         {
259             value = !value;
260         }
261         status = value;
262     }
263 }
264 
265 bool PresenceSensor::getValue(void)
266 {
267     return status;
268 }
269 
270 RedundancySensor::RedundancySensor(size_t count,
271                                    const std::vector<std::string>& children,
272                                    sdbusplus::asio::object_server& objectServer,
273                                    const std::string& sensorConfiguration) :
274     count(count),
275     iface(objectServer.add_interface(
276         "/xyz/openbmc_project/control/FanRedundancy/Tach",
277         "xyz.openbmc_project.Control.FanRedundancy")),
278     association(objectServer.add_interface(
279         "/xyz/openbmc_project/control/FanRedundancy/Tach",
280         "org.openbmc.Associations")),
281     objectServer(objectServer)
282 {
283     createAssociation(association, sensorConfiguration);
284     iface->register_property("Collection", children);
285     iface->register_property("Status", std::string("Full"));
286     iface->register_property("AllowedFailures", static_cast<uint8_t>(count));
287     iface->initialize();
288 }
289 RedundancySensor::~RedundancySensor()
290 {
291     objectServer.remove_interface(association);
292     objectServer.remove_interface(iface);
293 }
294 void RedundancySensor::update(const std::string& name, bool failed)
295 {
296     statuses[name] = failed;
297     size_t failedCount = 0;
298 
299     std::string state = "Full";
300     for (const auto& status : statuses)
301     {
302         if (status.second)
303         {
304             failedCount++;
305         }
306         if (failedCount > count)
307         {
308             state = "Failed";
309             break;
310         }
311         else if (failedCount)
312         {
313             state = "Degraded";
314         }
315     }
316     iface->set_property("Status", state);
317 }
318