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