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