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 "TachSensor.hpp" 18 19 #include "Utils.hpp" 20 21 #include <unistd.h> 22 23 #include <boost/algorithm/string/predicate.hpp> 24 #include <boost/algorithm/string/replace.hpp> 25 #include <boost/date_time/posix_time/posix_time.hpp> 26 #include <fstream> 27 #include <iostream> 28 #include <limits> 29 #include <sdbusplus/asio/connection.hpp> 30 #include <sdbusplus/asio/object_server.hpp> 31 #include <string> 32 33 static constexpr unsigned int pwmPollMs = 500; 34 static constexpr size_t warnAfterErrorCount = 10; 35 36 TachSensor::TachSensor(const std::string& path, const std::string& objectType, 37 sdbusplus::asio::object_server& objectServer, 38 std::shared_ptr<sdbusplus::asio::connection>& conn, 39 std::unique_ptr<PresenceSensor>&& presenceSensor, 40 std::optional<RedundancySensor>* redundancy, 41 boost::asio::io_service& io, const std::string& fanName, 42 std::vector<thresholds::Threshold>&& _thresholds, 43 const std::string& sensorConfiguration, 44 const std::pair<size_t, size_t>& limits) : 45 Sensor(boost::replace_all_copy(fanName, " ", "_"), std::move(_thresholds), 46 sensorConfiguration, objectType, limits.second, limits.first), 47 path(path), objServer(objectServer), presence(std::move(presenceSensor)), 48 redundancy(redundancy), inputDev(io, open(path.c_str(), O_RDONLY)), 49 waitTimer(io), errCount(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 association = objectServer.add_interface( 68 "/xyz/openbmc_project/sensors/fan_tach/" + name, 69 association::interface); 70 71 if (presence) 72 { 73 itemIface = 74 objectServer.add_interface("/xyz/openbmc_project/inventory/" + name, 75 "xyz.openbmc_project.Inventory.Item"); 76 itemIface->register_property("PrettyName", 77 std::string()); // unused property 78 itemIface->register_property("Present", true); 79 itemIface->initialize(); 80 itemAssoc = objectServer.add_interface( 81 "/xyz/openbmc_project/inventory/" + name, association::interface); 82 itemAssoc->register_property( 83 "associations", 84 std::vector<Association>{ 85 {"sensors", "inventory", 86 "/xyz/openbmc_project/sensors/fan_tach/" + name}}); 87 itemAssoc->initialize(); 88 } 89 setInitialProperties(conn); 90 setupPowerMatch(conn); 91 setupRead(); 92 } 93 94 TachSensor::~TachSensor() 95 { 96 // close the input dev to cancel async operations 97 inputDev.close(); 98 waitTimer.cancel(); 99 objServer.remove_interface(thresholdInterfaceWarning); 100 objServer.remove_interface(thresholdInterfaceCritical); 101 objServer.remove_interface(sensorInterface); 102 objServer.remove_interface(association); 103 objServer.remove_interface(itemIface); 104 objServer.remove_interface(itemAssoc); 105 } 106 107 void TachSensor::setupRead(void) 108 { 109 boost::asio::async_read_until( 110 inputDev, readBuf, '\n', 111 [&](const boost::system::error_code& ec, 112 std::size_t /*bytes_transfered*/) { handleResponse(ec); }); 113 } 114 115 void TachSensor::handleResponse(const boost::system::error_code& err) 116 { 117 if (err == boost::system::errc::bad_file_descriptor) 118 { 119 return; // we're being destroyed 120 } 121 bool missing = false; 122 size_t pollTime = pwmPollMs; 123 if (presence) 124 { 125 if (!presence->getValue()) 126 { 127 updateValue(std::numeric_limits<double>::quiet_NaN()); 128 missing = true; 129 pollTime = sensorFailedPollTimeMs; 130 } 131 itemIface->set_property("Present", !missing); 132 } 133 std::istream responseStream(&readBuf); 134 if (!missing) 135 { 136 if (!err) 137 { 138 std::string response; 139 try 140 { 141 std::getline(responseStream, response); 142 float nvalue = std::stof(response); 143 responseStream.clear(); 144 if (static_cast<double>(nvalue) != value) 145 { 146 updateValue(nvalue); 147 } 148 errCount = 0; 149 } 150 catch (const std::invalid_argument&) 151 { 152 errCount++; 153 } 154 } 155 else 156 { 157 if (!isPowerOn()) 158 { 159 errCount = 0; 160 updateValue(std::numeric_limits<double>::quiet_NaN()); 161 } 162 else 163 { 164 pollTime = sensorFailedPollTimeMs; 165 errCount++; 166 } 167 } 168 if (errCount >= warnAfterErrorCount) 169 { 170 // only print once 171 if (errCount == warnAfterErrorCount) 172 { 173 std::cerr << "Failure to read sensor " << name << " at " << path 174 << " ec:" << err << "\n"; 175 } 176 updateValue(0); 177 } 178 } 179 responseStream.clear(); 180 inputDev.close(); 181 int fd = open(path.c_str(), O_RDONLY); 182 if (fd <= 0) 183 { 184 return; // we're no longer valid 185 } 186 inputDev.assign(fd); 187 waitTimer.expires_from_now(boost::posix_time::milliseconds(pollTime)); 188 waitTimer.async_wait([&](const boost::system::error_code& ec) { 189 if (ec == boost::asio::error::operation_aborted) 190 { 191 return; // we're being canceled 192 } 193 setupRead(); 194 }); 195 } 196 197 void TachSensor::checkThresholds(void) 198 { 199 if (!isPowerOn()) 200 { 201 return; 202 } 203 204 bool status = thresholds::checkThresholds(this); 205 206 if (redundancy && *redundancy) 207 { 208 (*redundancy) 209 ->update("/xyz/openbmc_project/sensors/fan_tach/" + name, !status); 210 } 211 } 212 213 PresenceSensor::PresenceSensor(const size_t index, bool inverted, 214 boost::asio::io_service& io, 215 const std::string& name) : 216 inverted(inverted), 217 inputDev(io), name(name) 218 { 219 // todo: use gpiodaemon 220 std::string device = gpioPath + std::string("gpio") + std::to_string(index); 221 fd = open((device + "/value").c_str(), O_RDONLY); 222 if (fd < 0) 223 { 224 std::cerr << "Error opening gpio " << index << "\n"; 225 return; 226 } 227 228 std::ofstream deviceFile(device + "/edge"); 229 if (!deviceFile.good()) 230 { 231 std::cerr << "Error setting edge " << device << "\n"; 232 return; 233 } 234 deviceFile << "both"; 235 deviceFile.close(); 236 237 inputDev.assign(boost::asio::ip::tcp::v4(), fd); 238 monitorPresence(); 239 read(); 240 } 241 242 PresenceSensor::~PresenceSensor() 243 { 244 inputDev.close(); 245 close(fd); 246 } 247 248 void PresenceSensor::monitorPresence(void) 249 { 250 inputDev.async_wait(boost::asio::ip::tcp::socket::wait_error, 251 [this](const boost::system::error_code& ec) { 252 if (ec == boost::system::errc::bad_file_descriptor) 253 { 254 return; // we're being destroyed 255 } 256 else if (ec) 257 { 258 std::cerr 259 << "Error on presence sensor socket\n"; 260 } 261 else 262 { 263 read(); 264 } 265 monitorPresence(); 266 }); 267 } 268 269 void PresenceSensor::read(void) 270 { 271 constexpr size_t readSize = sizeof("0"); 272 std::string readBuf; 273 readBuf.resize(readSize); 274 lseek(fd, 0, SEEK_SET); 275 size_t r = ::read(fd, readBuf.data(), readSize); 276 if (r != readSize) 277 { 278 std::cerr << "Error reading gpio\n"; 279 } 280 else 281 { 282 bool value = std::stoi(readBuf); 283 if (inverted) 284 { 285 value = !value; 286 } 287 if (value != status) 288 { 289 status = value; 290 if (status) 291 { 292 logFanInserted(name); 293 } 294 else 295 { 296 logFanRemoved(name); 297 } 298 } 299 } 300 } 301 302 bool PresenceSensor::getValue(void) 303 { 304 return status; 305 } 306 307 RedundancySensor::RedundancySensor(size_t count, 308 const std::vector<std::string>& children, 309 sdbusplus::asio::object_server& objectServer, 310 const std::string& sensorConfiguration) : 311 count(count), 312 iface(objectServer.add_interface( 313 "/xyz/openbmc_project/control/FanRedundancy/Tach", 314 "xyz.openbmc_project.Control.FanRedundancy")), 315 association(objectServer.add_interface( 316 "/xyz/openbmc_project/control/FanRedundancy/Tach", 317 association::interface)), 318 objectServer(objectServer) 319 { 320 createAssociation(association, sensorConfiguration); 321 iface->register_property("Collection", children); 322 iface->register_property("Status", std::string("Full")); 323 iface->register_property("AllowedFailures", static_cast<uint8_t>(count)); 324 iface->initialize(); 325 } 326 RedundancySensor::~RedundancySensor() 327 { 328 objectServer.remove_interface(association); 329 objectServer.remove_interface(iface); 330 } 331 void RedundancySensor::update(const std::string& name, bool failed) 332 { 333 statuses[name] = failed; 334 size_t failedCount = 0; 335 336 std::string newState = redundancy::full; 337 for (const auto& status : statuses) 338 { 339 if (status.second) 340 { 341 failedCount++; 342 } 343 if (failedCount > count) 344 { 345 newState = redundancy::failed; 346 break; 347 } 348 else if (failedCount) 349 { 350 newState = redundancy::degraded; 351 } 352 } 353 if (state != newState) 354 { 355 if (state == redundancy::full) 356 { 357 logFanRedundancyLost(); 358 } 359 else if (newState == redundancy::full) 360 { 361 logFanRedundancyRestored(); 362 } 363 state = newState; 364 iface->set_property("Status", state); 365 } 366 } 367