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