blob: 86b548fa3fd6b3d98ad2b4d32d804ef4bfa3f751 [file] [log] [blame]
James Feist6714a252018-09-10 15:26:18 -07001/*
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
James Feist58295ad2019-05-30 15:01:41 -070017#include "TachSensor.hpp"
18
19#include "Utils.hpp"
20
James Feist6714a252018-09-10 15:26:18 -070021#include <unistd.h>
22
James Feist6714a252018-09-10 15:26:18 -070023#include <boost/algorithm/string/predicate.hpp>
24#include <boost/algorithm/string/replace.hpp>
25#include <boost/date_time/posix_time/posix_time.hpp>
James Feist7bc2bab2018-10-26 14:09:45 -070026#include <fstream>
James Feist6714a252018-09-10 15:26:18 -070027#include <iostream>
Patrick Venture96e97db2019-10-31 13:44:38 -070028#include <istream>
James Feist6714a252018-09-10 15:26:18 -070029#include <limits>
Patrick Venture96e97db2019-10-31 13:44:38 -070030#include <memory>
31#include <optional>
James Feist6714a252018-09-10 15:26:18 -070032#include <sdbusplus/asio/connection.hpp>
33#include <sdbusplus/asio/object_server.hpp>
Patrick Venture96e97db2019-10-31 13:44:38 -070034#include <stdexcept>
James Feist6714a252018-09-10 15:26:18 -070035#include <string>
Patrick Venture96e97db2019-10-31 13:44:38 -070036#include <utility>
37#include <vector>
James Feist6714a252018-09-10 15:26:18 -070038
Jae Hyun Yoo9ced0a32018-10-25 10:42:39 -070039static constexpr unsigned int pwmPollMs = 500;
40static constexpr size_t warnAfterErrorCount = 10;
James Feist6714a252018-09-10 15:26:18 -070041
James Feistd8705872019-02-08 13:26:09 -080042TachSensor::TachSensor(const std::string& path, const std::string& objectType,
43 sdbusplus::asio::object_server& objectServer,
44 std::shared_ptr<sdbusplus::asio::connection>& conn,
James Feist60c0ec72019-03-18 15:08:43 -070045 std::unique_ptr<PresenceSensor>&& presenceSensor,
James Feist7b18b1e2019-05-14 13:42:09 -070046 std::optional<RedundancySensor>* redundancy,
James Feistd8705872019-02-08 13:26:09 -080047 boost::asio::io_service& io, const std::string& fanName,
48 std::vector<thresholds::Threshold>&& _thresholds,
49 const std::string& sensorConfiguration,
50 const std::pair<size_t, size_t>& limits) :
James Feist930fcde2019-05-28 12:58:43 -070051 Sensor(boost::replace_all_copy(fanName, " ", "_"), std::move(_thresholds),
52 sensorConfiguration, objectType, limits.second, limits.first),
53 path(path), objServer(objectServer), presence(std::move(presenceSensor)),
James Feist71d31b22019-01-02 16:57:54 -080054 redundancy(redundancy), inputDev(io, open(path.c_str(), O_RDONLY)),
55 waitTimer(io), errCount(0)
James Feist6714a252018-09-10 15:26:18 -070056{
James Feist251c7822018-09-12 12:54:15 -070057 sensorInterface = objectServer.add_interface(
58 "/xyz/openbmc_project/sensors/fan_tach/" + name,
59 "xyz.openbmc_project.Sensor.Value");
60
Jae Hyun Yoo9ced0a32018-10-25 10:42:39 -070061 if (thresholds::hasWarningInterface(thresholds))
James Feist6714a252018-09-10 15:26:18 -070062 {
James Feist251c7822018-09-12 12:54:15 -070063 thresholdInterfaceWarning = objectServer.add_interface(
James Feist6714a252018-09-10 15:26:18 -070064 "/xyz/openbmc_project/sensors/fan_tach/" + name,
65 "xyz.openbmc_project.Sensor.Threshold.Warning");
66 }
Jae Hyun Yoo9ced0a32018-10-25 10:42:39 -070067 if (thresholds::hasCriticalInterface(thresholds))
James Feist6714a252018-09-10 15:26:18 -070068 {
James Feist251c7822018-09-12 12:54:15 -070069 thresholdInterfaceCritical = objectServer.add_interface(
James Feist6714a252018-09-10 15:26:18 -070070 "/xyz/openbmc_project/sensors/fan_tach/" + name,
71 "xyz.openbmc_project.Sensor.Threshold.Critical");
72 }
James Feist078f2322019-03-08 11:09:05 -080073 association = objectServer.add_interface(
74 "/xyz/openbmc_project/sensors/fan_tach/" + name,
James Feist2adc95c2019-09-30 14:55:28 -070075 association::interface);
James Feist60c0ec72019-03-18 15:08:43 -070076
77 if (presence)
78 {
79 itemIface =
James Feistd8bd5622019-06-26 12:09:05 -070080 objectServer.add_interface("/xyz/openbmc_project/inventory/" + name,
James Feist60c0ec72019-03-18 15:08:43 -070081 "xyz.openbmc_project.Inventory.Item");
82 itemIface->register_property("PrettyName",
83 std::string()); // unused property
84 itemIface->register_property("Present", true);
85 itemIface->initialize();
James Feist2adc95c2019-09-30 14:55:28 -070086 itemAssoc = objectServer.add_interface(
87 "/xyz/openbmc_project/inventory/" + name, association::interface);
James Feistd8bd5622019-06-26 12:09:05 -070088 itemAssoc->register_property(
89 "associations",
90 std::vector<Association>{
91 {"sensors", "inventory",
92 "/xyz/openbmc_project/sensors/fan_tach/" + name}});
93 itemAssoc->initialize();
James Feist60c0ec72019-03-18 15:08:43 -070094 }
Jae Hyun Yoo9ced0a32018-10-25 10:42:39 -070095 setInitialProperties(conn);
James Feist6ef20402019-01-07 16:45:08 -080096 setupPowerMatch(conn);
Jae Hyun Yoo9ced0a32018-10-25 10:42:39 -070097 setupRead();
James Feist6714a252018-09-10 15:26:18 -070098}
99
100TachSensor::~TachSensor()
101{
102 // close the input dev to cancel async operations
Jae Hyun Yoo9ced0a32018-10-25 10:42:39 -0700103 inputDev.close();
104 waitTimer.cancel();
James Feist251c7822018-09-12 12:54:15 -0700105 objServer.remove_interface(thresholdInterfaceWarning);
106 objServer.remove_interface(thresholdInterfaceCritical);
107 objServer.remove_interface(sensorInterface);
James Feist078f2322019-03-08 11:09:05 -0800108 objServer.remove_interface(association);
James Feist60c0ec72019-03-18 15:08:43 -0700109 objServer.remove_interface(itemIface);
James Feistd8bd5622019-06-26 12:09:05 -0700110 objServer.remove_interface(itemAssoc);
James Feist6714a252018-09-10 15:26:18 -0700111}
112
Jae Hyun Yoo9ced0a32018-10-25 10:42:39 -0700113void TachSensor::setupRead(void)
James Feist6714a252018-09-10 15:26:18 -0700114{
115 boost::asio::async_read_until(
Jae Hyun Yoo9ced0a32018-10-25 10:42:39 -0700116 inputDev, readBuf, '\n',
James Feistd8705872019-02-08 13:26:09 -0800117 [&](const boost::system::error_code& ec,
Jae Hyun Yoo9ced0a32018-10-25 10:42:39 -0700118 std::size_t /*bytes_transfered*/) { handleResponse(ec); });
James Feist6714a252018-09-10 15:26:18 -0700119}
120
James Feistd8705872019-02-08 13:26:09 -0800121void TachSensor::handleResponse(const boost::system::error_code& err)
James Feist6714a252018-09-10 15:26:18 -0700122{
123 if (err == boost::system::errc::bad_file_descriptor)
124 {
125 return; // we're being destroyed
126 }
James Feist7bc2bab2018-10-26 14:09:45 -0700127 bool missing = false;
James Feist1169eb42018-10-31 10:08:47 -0700128 size_t pollTime = pwmPollMs;
James Feist7bc2bab2018-10-26 14:09:45 -0700129 if (presence)
130 {
131 if (!presence->getValue())
132 {
James Feist1169eb42018-10-31 10:08:47 -0700133 updateValue(std::numeric_limits<double>::quiet_NaN());
James Feist7bc2bab2018-10-26 14:09:45 -0700134 missing = true;
James Feist1169eb42018-10-31 10:08:47 -0700135 pollTime = sensorFailedPollTimeMs;
James Feist7bc2bab2018-10-26 14:09:45 -0700136 }
James Feist60c0ec72019-03-18 15:08:43 -0700137 itemIface->set_property("Present", !missing);
James Feist7bc2bab2018-10-26 14:09:45 -0700138 }
Jae Hyun Yoo9ced0a32018-10-25 10:42:39 -0700139 std::istream responseStream(&readBuf);
James Feist7bc2bab2018-10-26 14:09:45 -0700140 if (!missing)
James Feist6714a252018-09-10 15:26:18 -0700141 {
James Feist7bc2bab2018-10-26 14:09:45 -0700142 if (!err)
James Feist6714a252018-09-10 15:26:18 -0700143 {
James Feist7bc2bab2018-10-26 14:09:45 -0700144 std::string response;
145 try
James Feist6714a252018-09-10 15:26:18 -0700146 {
James Feist7bc2bab2018-10-26 14:09:45 -0700147 std::getline(responseStream, response);
148 float nvalue = std::stof(response);
149 responseStream.clear();
James Feistb6c0b912019-07-09 12:21:44 -0700150 if (static_cast<double>(nvalue) != value)
James Feist7bc2bab2018-10-26 14:09:45 -0700151 {
152 updateValue(nvalue);
153 }
154 errCount = 0;
James Feist6714a252018-09-10 15:26:18 -0700155 }
James Feistd8705872019-02-08 13:26:09 -0800156 catch (const std::invalid_argument&)
James Feist7bc2bab2018-10-26 14:09:45 -0700157 {
158 errCount++;
159 }
James Feist6714a252018-09-10 15:26:18 -0700160 }
161 else
162 {
James Feist71d31b22019-01-02 16:57:54 -0800163 if (!isPowerOn())
James Feist7bc2bab2018-10-26 14:09:45 -0700164 {
James Feist71d31b22019-01-02 16:57:54 -0800165 errCount = 0;
166 updateValue(std::numeric_limits<double>::quiet_NaN());
James Feist7bc2bab2018-10-26 14:09:45 -0700167 }
168 else
169 {
James Feist71d31b22019-01-02 16:57:54 -0800170 pollTime = sensorFailedPollTimeMs;
171 errCount++;
James Feist7bc2bab2018-10-26 14:09:45 -0700172 }
James Feist6714a252018-09-10 15:26:18 -0700173 }
James Feist71d31b22019-01-02 16:57:54 -0800174 if (errCount >= warnAfterErrorCount)
175 {
176 // only print once
177 if (errCount == warnAfterErrorCount)
178 {
179 std::cerr << "Failure to read sensor " << name << " at " << path
180 << " ec:" << err << "\n";
181 }
182 updateValue(0);
183 }
James Feist6714a252018-09-10 15:26:18 -0700184 }
Jae Hyun Yoo9ced0a32018-10-25 10:42:39 -0700185 responseStream.clear();
186 inputDev.close();
James Feist6714a252018-09-10 15:26:18 -0700187 int fd = open(path.c_str(), O_RDONLY);
Jae Hyun Yooef9665a2019-10-10 10:26:39 -0700188 if (fd < 0)
James Feist6714a252018-09-10 15:26:18 -0700189 {
190 return; // we're no longer valid
191 }
Jae Hyun Yoo9ced0a32018-10-25 10:42:39 -0700192 inputDev.assign(fd);
James Feist7bc2bab2018-10-26 14:09:45 -0700193 waitTimer.expires_from_now(boost::posix_time::milliseconds(pollTime));
James Feistd8705872019-02-08 13:26:09 -0800194 waitTimer.async_wait([&](const boost::system::error_code& ec) {
James Feist6714a252018-09-10 15:26:18 -0700195 if (ec == boost::asio::error::operation_aborted)
196 {
197 return; // we're being canceled
198 }
Jae Hyun Yoo9ced0a32018-10-25 10:42:39 -0700199 setupRead();
James Feist6714a252018-09-10 15:26:18 -0700200 });
201}
202
Jae Hyun Yoo9ced0a32018-10-25 10:42:39 -0700203void TachSensor::checkThresholds(void)
James Feist6714a252018-09-10 15:26:18 -0700204{
James Feist95e54902019-09-04 15:13:36 -0700205 if (!isPowerOn())
206 {
207 return;
208 }
209
James Feistdc6c55f2018-10-31 12:53:20 -0700210 bool status = thresholds::checkThresholds(this);
James Feist95e54902019-09-04 15:13:36 -0700211
James Feist7b18b1e2019-05-14 13:42:09 -0700212 if (redundancy && *redundancy)
James Feistdc6c55f2018-10-31 12:53:20 -0700213 {
James Feist7b18b1e2019-05-14 13:42:09 -0700214 (*redundancy)
215 ->update("/xyz/openbmc_project/sensors/fan_tach/" + name, !status);
James Feistdc6c55f2018-10-31 12:53:20 -0700216 }
James Feist6714a252018-09-10 15:26:18 -0700217}
218
James Feist7bc2bab2018-10-26 14:09:45 -0700219PresenceSensor::PresenceSensor(const size_t index, bool inverted,
James Feist7b18b1e2019-05-14 13:42:09 -0700220 boost::asio::io_service& io,
221 const std::string& name) :
James Feist7bc2bab2018-10-26 14:09:45 -0700222 inverted(inverted),
James Feist7b18b1e2019-05-14 13:42:09 -0700223 inputDev(io), name(name)
James Feist7bc2bab2018-10-26 14:09:45 -0700224{
225 // todo: use gpiodaemon
226 std::string device = gpioPath + std::string("gpio") + std::to_string(index);
227 fd = open((device + "/value").c_str(), O_RDONLY);
228 if (fd < 0)
229 {
230 std::cerr << "Error opening gpio " << index << "\n";
231 return;
232 }
233
234 std::ofstream deviceFile(device + "/edge");
235 if (!deviceFile.good())
236 {
237 std::cerr << "Error setting edge " << device << "\n";
238 return;
239 }
240 deviceFile << "both";
241 deviceFile.close();
242
243 inputDev.assign(boost::asio::ip::tcp::v4(), fd);
244 monitorPresence();
245 read();
246}
247
248PresenceSensor::~PresenceSensor()
249{
250 inputDev.close();
251 close(fd);
252}
253
254void PresenceSensor::monitorPresence(void)
255{
256 inputDev.async_wait(boost::asio::ip::tcp::socket::wait_error,
James Feistd8705872019-02-08 13:26:09 -0800257 [this](const boost::system::error_code& ec) {
James Feist7bc2bab2018-10-26 14:09:45 -0700258 if (ec == boost::system::errc::bad_file_descriptor)
259 {
260 return; // we're being destroyed
261 }
262 else if (ec)
263 {
264 std::cerr
265 << "Error on presence sensor socket\n";
266 }
267 else
268 {
269 read();
270 }
271 monitorPresence();
272 });
273}
274
275void PresenceSensor::read(void)
276{
277 constexpr size_t readSize = sizeof("0");
278 std::string readBuf;
279 readBuf.resize(readSize);
280 lseek(fd, 0, SEEK_SET);
281 size_t r = ::read(fd, readBuf.data(), readSize);
James Feist95b079b2018-11-21 09:28:00 -0800282 if (r != readSize)
James Feist7bc2bab2018-10-26 14:09:45 -0700283 {
284 std::cerr << "Error reading gpio\n";
285 }
286 else
287 {
288 bool value = std::stoi(readBuf);
289 if (inverted)
290 {
291 value = !value;
292 }
James Feist7b18b1e2019-05-14 13:42:09 -0700293 if (value != status)
294 {
295 status = value;
296 if (status)
297 {
298 logFanInserted(name);
299 }
300 else
301 {
302 logFanRemoved(name);
303 }
304 }
James Feist7bc2bab2018-10-26 14:09:45 -0700305 }
306}
307
308bool PresenceSensor::getValue(void)
309{
310 return status;
James Feistdc6c55f2018-10-31 12:53:20 -0700311}
312
James Feist9bb67462019-03-15 15:09:50 -0700313RedundancySensor::RedundancySensor(size_t count,
314 const std::vector<std::string>& children,
315 sdbusplus::asio::object_server& objectServer,
316 const std::string& sensorConfiguration) :
James Feistdc6c55f2018-10-31 12:53:20 -0700317 count(count),
318 iface(objectServer.add_interface(
319 "/xyz/openbmc_project/control/FanRedundancy/Tach",
James Feist9bb67462019-03-15 15:09:50 -0700320 "xyz.openbmc_project.Control.FanRedundancy")),
321 association(objectServer.add_interface(
322 "/xyz/openbmc_project/control/FanRedundancy/Tach",
James Feist2adc95c2019-09-30 14:55:28 -0700323 association::interface)),
James Feistdc6c55f2018-10-31 12:53:20 -0700324 objectServer(objectServer)
325{
James Feist9bb67462019-03-15 15:09:50 -0700326 createAssociation(association, sensorConfiguration);
James Feistdc6c55f2018-10-31 12:53:20 -0700327 iface->register_property("Collection", children);
328 iface->register_property("Status", std::string("Full"));
329 iface->register_property("AllowedFailures", static_cast<uint8_t>(count));
330 iface->initialize();
331}
332RedundancySensor::~RedundancySensor()
333{
James Feist9bb67462019-03-15 15:09:50 -0700334 objectServer.remove_interface(association);
James Feistdc6c55f2018-10-31 12:53:20 -0700335 objectServer.remove_interface(iface);
336}
James Feistd8705872019-02-08 13:26:09 -0800337void RedundancySensor::update(const std::string& name, bool failed)
James Feistdc6c55f2018-10-31 12:53:20 -0700338{
339 statuses[name] = failed;
340 size_t failedCount = 0;
341
James Feist7b18b1e2019-05-14 13:42:09 -0700342 std::string newState = redundancy::full;
James Feistd8705872019-02-08 13:26:09 -0800343 for (const auto& status : statuses)
James Feistdc6c55f2018-10-31 12:53:20 -0700344 {
345 if (status.second)
346 {
347 failedCount++;
348 }
349 if (failedCount > count)
350 {
James Feist7b18b1e2019-05-14 13:42:09 -0700351 newState = redundancy::failed;
James Feistdc6c55f2018-10-31 12:53:20 -0700352 break;
353 }
354 else if (failedCount)
355 {
James Feist7b18b1e2019-05-14 13:42:09 -0700356 newState = redundancy::degraded;
James Feistdc6c55f2018-10-31 12:53:20 -0700357 }
358 }
James Feist7b18b1e2019-05-14 13:42:09 -0700359 if (state != newState)
360 {
361 if (state == redundancy::full)
362 {
363 logFanRedundancyLost();
364 }
365 else if (newState == redundancy::full)
366 {
367 logFanRedundancyRestored();
368 }
369 state = newState;
370 iface->set_property("Status", state);
371 }
James Feistdc6c55f2018-10-31 12:53:20 -0700372}