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, 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 Sensor(boost::replace_all_copy(fanName, " ", "_"), path, 43 std::move(_thresholds)), 44 objServer(objectServer), dbusConnection(conn), 45 presence(std::move(presence)), redundancy(redundancy), 46 configuration(sensorConfiguration), 47 inputDev(io, open(path.c_str(), O_RDONLY)), waitTimer(io), errCount(0), 48 // todo, get these from config 49 maxValue(25000), minValue(0) 50 { 51 sensorInterface = objectServer.add_interface( 52 "/xyz/openbmc_project/sensors/fan_tach/" + name, 53 "xyz.openbmc_project.Sensor.Value"); 54 55 if (thresholds::hasWarningInterface(thresholds)) 56 { 57 thresholdInterfaceWarning = objectServer.add_interface( 58 "/xyz/openbmc_project/sensors/fan_tach/" + name, 59 "xyz.openbmc_project.Sensor.Threshold.Warning"); 60 } 61 if (thresholds::hasCriticalInterface(thresholds)) 62 { 63 thresholdInterfaceCritical = objectServer.add_interface( 64 "/xyz/openbmc_project/sensors/fan_tach/" + name, 65 "xyz.openbmc_project.Sensor.Threshold.Critical"); 66 } 67 setInitialProperties(conn); 68 isPowerOn(dbusConnection); // first call initializes 69 setupRead(); 70 } 71 72 TachSensor::~TachSensor() 73 { 74 // close the input dev to cancel async operations 75 inputDev.close(); 76 waitTimer.cancel(); 77 objServer.remove_interface(thresholdInterfaceWarning); 78 objServer.remove_interface(thresholdInterfaceCritical); 79 objServer.remove_interface(sensorInterface); 80 } 81 82 void TachSensor::setupRead(void) 83 { 84 boost::asio::async_read_until( 85 inputDev, readBuf, '\n', 86 [&](const boost::system::error_code &ec, 87 std::size_t /*bytes_transfered*/) { handleResponse(ec); }); 88 } 89 90 void TachSensor::handleResponse(const boost::system::error_code &err) 91 { 92 if (err == boost::system::errc::bad_file_descriptor) 93 { 94 return; // we're being destroyed 95 } 96 bool missing = false; 97 size_t pollTime = pwmPollMs; 98 if (presence) 99 { 100 if (!presence->getValue()) 101 { 102 updateValue(std::numeric_limits<double>::quiet_NaN()); 103 missing = true; 104 pollTime = sensorFailedPollTimeMs; 105 } 106 } 107 std::istream responseStream(&readBuf); 108 if (!missing) 109 { 110 if (!err) 111 { 112 std::string response; 113 try 114 { 115 std::getline(responseStream, response); 116 float nvalue = std::stof(response); 117 responseStream.clear(); 118 if (!isnan(overriddenValue)) 119 { 120 nvalue = overriddenValue; 121 } 122 if (nvalue != value) 123 { 124 updateValue(nvalue); 125 } 126 errCount = 0; 127 } 128 catch (const std::invalid_argument &) 129 { 130 errCount++; 131 } 132 } 133 else 134 { 135 pollTime = sensorFailedPollTimeMs; 136 errCount++; 137 } 138 if (errCount >= warnAfterErrorCount) 139 { 140 // only an error if power is on 141 if (isPowerOn(dbusConnection)) 142 { 143 // only print once 144 if (errCount == warnAfterErrorCount) 145 { 146 std::cerr << "Failure to read sensor " << name << " at " 147 << path << " ec:" << err << "\n"; 148 } 149 updateValue(0); 150 } 151 else 152 { 153 errCount = 0; // check power again in 10 cycles 154 updateValue(std::numeric_limits<double>::quiet_NaN()); 155 } 156 } 157 } 158 responseStream.clear(); 159 inputDev.close(); 160 int fd = open(path.c_str(), O_RDONLY); 161 if (fd <= 0) 162 { 163 return; // we're no longer valid 164 } 165 inputDev.assign(fd); 166 waitTimer.expires_from_now(boost::posix_time::milliseconds(pollTime)); 167 waitTimer.async_wait([&](const boost::system::error_code &ec) { 168 if (ec == boost::asio::error::operation_aborted) 169 { 170 return; // we're being canceled 171 } 172 setupRead(); 173 }); 174 } 175 176 void TachSensor::checkThresholds(void) 177 { 178 bool status = thresholds::checkThresholds(this); 179 if (redundancy) 180 { 181 redundancy->update("/xyz/openbmc_project/sensors/fan_tach/" + name, 182 !status); 183 } 184 } 185 186 void TachSensor::updateValue(const double &newValue) 187 { 188 // Indicate that it is internal set call 189 internalSet = true; 190 sensorInterface->set_property("Value", newValue); 191 internalSet = false; 192 value = newValue; 193 checkThresholds(); 194 } 195 196 void TachSensor::setInitialProperties( 197 std::shared_ptr<sdbusplus::asio::connection> &conn) 198 { 199 // todo, get max and min from configuration 200 sensorInterface->register_property("MaxValue", maxValue); 201 sensorInterface->register_property("MinValue", minValue); 202 sensorInterface->register_property( 203 "Value", value, [&](const double &newValue, double &oldValue) { 204 return setSensorValue(newValue, oldValue); 205 }); 206 207 for (auto &threshold : thresholds) 208 { 209 std::shared_ptr<sdbusplus::asio::dbus_interface> iface; 210 std::string level; 211 std::string alarm; 212 if (threshold.level == thresholds::Level::CRITICAL) 213 { 214 iface = thresholdInterfaceCritical; 215 if (threshold.direction == thresholds::Direction::HIGH) 216 { 217 level = "CriticalHigh"; 218 alarm = "CriticalAlarmHigh"; 219 } 220 else 221 { 222 level = "CriticalLow"; 223 alarm = "CriticalAlarmLow"; 224 } 225 } 226 else if (threshold.level == thresholds::Level::WARNING) 227 { 228 iface = thresholdInterfaceWarning; 229 if (threshold.direction == thresholds::Direction::HIGH) 230 { 231 level = "WarningHigh"; 232 alarm = "WarningAlarmHigh"; 233 } 234 else 235 { 236 level = "WarningLow"; 237 alarm = "WarningAlarmLow"; 238 } 239 } 240 else 241 { 242 std::cerr << "Unknown threshold level" << threshold.level << "\n"; 243 continue; 244 } 245 if (!iface) 246 { 247 std::cout << "trying to set uninitialized interface\n"; 248 continue; 249 } 250 iface->register_property( 251 level, threshold.value, 252 [&](const double &request, double &oldValue) { 253 oldValue = request; // todo, just let the config do this? 254 threshold.value = request; 255 thresholds::persistThreshold( 256 configuration, 257 "xyz.openbmc_project.Configuration.AspeedFan", threshold, 258 conn); 259 return 1; 260 }); 261 iface->register_property(alarm, false); 262 } 263 if (!sensorInterface->initialize()) 264 { 265 std::cerr << "error initializing value interface\n"; 266 } 267 if (thresholdInterfaceWarning && !thresholdInterfaceWarning->initialize()) 268 { 269 std::cerr << "error initializing warning threshold interface\n"; 270 } 271 272 if (thresholdInterfaceCritical && !thresholdInterfaceCritical->initialize()) 273 { 274 std::cerr << "error initializing critical threshold interface\n"; 275 } 276 } 277 278 PresenceSensor::PresenceSensor(const size_t index, bool inverted, 279 boost::asio::io_service &io) : 280 inverted(inverted), 281 inputDev(io) 282 { 283 // todo: use gpiodaemon 284 std::string device = gpioPath + std::string("gpio") + std::to_string(index); 285 fd = open((device + "/value").c_str(), O_RDONLY); 286 if (fd < 0) 287 { 288 std::cerr << "Error opening gpio " << index << "\n"; 289 return; 290 } 291 292 std::ofstream deviceFile(device + "/edge"); 293 if (!deviceFile.good()) 294 { 295 std::cerr << "Error setting edge " << device << "\n"; 296 return; 297 } 298 deviceFile << "both"; 299 deviceFile.close(); 300 301 inputDev.assign(boost::asio::ip::tcp::v4(), fd); 302 monitorPresence(); 303 read(); 304 } 305 306 PresenceSensor::~PresenceSensor() 307 { 308 inputDev.close(); 309 close(fd); 310 } 311 312 void PresenceSensor::monitorPresence(void) 313 { 314 inputDev.async_wait(boost::asio::ip::tcp::socket::wait_error, 315 [this](const boost::system::error_code &ec) { 316 if (ec == boost::system::errc::bad_file_descriptor) 317 { 318 return; // we're being destroyed 319 } 320 else if (ec) 321 { 322 std::cerr 323 << "Error on presence sensor socket\n"; 324 } 325 else 326 { 327 read(); 328 } 329 monitorPresence(); 330 }); 331 } 332 333 void PresenceSensor::read(void) 334 { 335 constexpr size_t readSize = sizeof("0"); 336 std::string readBuf; 337 readBuf.resize(readSize); 338 lseek(fd, 0, SEEK_SET); 339 size_t r = ::read(fd, readBuf.data(), readSize); 340 if (r != readSize) 341 { 342 std::cerr << "Error reading gpio\n"; 343 } 344 else 345 { 346 bool value = std::stoi(readBuf); 347 if (inverted) 348 { 349 value = !value; 350 } 351 status = value; 352 } 353 } 354 355 bool PresenceSensor::getValue(void) 356 { 357 return status; 358 } 359 360 RedundancySensor::RedundancySensor( 361 size_t count, const std::vector<std::string> &children, 362 sdbusplus::asio::object_server &objectServer) : 363 count(count), 364 iface(objectServer.add_interface( 365 "/xyz/openbmc_project/control/FanRedundancy/Tach", 366 "xyz.openbmc_project.control.FanRedundancy")), 367 objectServer(objectServer) 368 { 369 iface->register_property("Collection", children); 370 iface->register_property("Status", std::string("Full")); 371 iface->register_property("AllowedFailures", static_cast<uint8_t>(count)); 372 iface->initialize(); 373 } 374 RedundancySensor::~RedundancySensor() 375 { 376 objectServer.remove_interface(iface); 377 } 378 void RedundancySensor::update(const std::string &name, bool failed) 379 { 380 statuses[name] = failed; 381 size_t failedCount = 0; 382 383 std::string state = "Full"; 384 for (const auto &status : statuses) 385 { 386 if (status.second) 387 { 388 failedCount++; 389 } 390 if (failedCount > count) 391 { 392 state = "Failed"; 393 break; 394 } 395 else if (failedCount) 396 { 397 state = "Degraded"; 398 } 399 } 400 iface->set_property("Status", state); 401 } 402