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