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/asio/read_until.hpp> 26 #include <boost/date_time/posix_time/posix_time.hpp> 27 #include <gpiod.hpp> 28 #include <sdbusplus/asio/connection.hpp> 29 #include <sdbusplus/asio/object_server.hpp> 30 31 #include <fstream> 32 #include <iostream> 33 #include <istream> 34 #include <limits> 35 #include <memory> 36 #include <optional> 37 #include <stdexcept> 38 #include <string> 39 #include <utility> 40 #include <vector> 41 42 static constexpr unsigned int pwmPollMs = 500; 43 static constexpr size_t warnAfterErrorCount = 10; 44 45 TachSensor::TachSensor(const std::string& path, const std::string& objectType, 46 sdbusplus::asio::object_server& objectServer, 47 std::shared_ptr<sdbusplus::asio::connection>& conn, 48 std::unique_ptr<PresenceSensor>&& presenceSensor, 49 std::optional<RedundancySensor>* redundancy, 50 boost::asio::io_service& io, const std::string& fanName, 51 std::vector<thresholds::Threshold>&& _thresholds, 52 const std::string& sensorConfiguration, 53 const std::pair<size_t, size_t>& limits, 54 const PowerState& powerState, 55 const std::optional<std::string>& ledIn) : 56 Sensor(boost::replace_all_copy(fanName, " ", "_"), std::move(_thresholds), 57 sensorConfiguration, objectType, limits.second, limits.first, conn, 58 powerState), 59 objServer(objectServer), redundancy(redundancy), 60 presence(std::move(presenceSensor)), 61 inputDev(io, open(path.c_str(), O_RDONLY)), waitTimer(io), path(path), 62 led(ledIn) 63 { 64 sensorInterface = objectServer.add_interface( 65 "/xyz/openbmc_project/sensors/fan_tach/" + name, 66 "xyz.openbmc_project.Sensor.Value"); 67 68 if (thresholds::hasWarningInterface(thresholds)) 69 { 70 thresholdInterfaceWarning = objectServer.add_interface( 71 "/xyz/openbmc_project/sensors/fan_tach/" + name, 72 "xyz.openbmc_project.Sensor.Threshold.Warning"); 73 } 74 if (thresholds::hasCriticalInterface(thresholds)) 75 { 76 thresholdInterfaceCritical = objectServer.add_interface( 77 "/xyz/openbmc_project/sensors/fan_tach/" + name, 78 "xyz.openbmc_project.Sensor.Threshold.Critical"); 79 } 80 association = objectServer.add_interface( 81 "/xyz/openbmc_project/sensors/fan_tach/" + name, 82 association::interface); 83 84 if (presence) 85 { 86 itemIface = 87 objectServer.add_interface("/xyz/openbmc_project/inventory/" + name, 88 "xyz.openbmc_project.Inventory.Item"); 89 itemIface->register_property("PrettyName", 90 std::string()); // unused property 91 itemIface->register_property("Present", true); 92 itemIface->initialize(); 93 itemAssoc = objectServer.add_interface( 94 "/xyz/openbmc_project/inventory/" + name, association::interface); 95 itemAssoc->register_property( 96 "associations", 97 std::vector<Association>{ 98 {"sensors", "inventory", 99 "/xyz/openbmc_project/sensors/fan_tach/" + name}}); 100 itemAssoc->initialize(); 101 } 102 setInitialProperties(conn); 103 setupRead(); 104 } 105 106 TachSensor::~TachSensor() 107 { 108 // close the input dev to cancel async operations 109 inputDev.close(); 110 waitTimer.cancel(); 111 objServer.remove_interface(thresholdInterfaceWarning); 112 objServer.remove_interface(thresholdInterfaceCritical); 113 objServer.remove_interface(sensorInterface); 114 objServer.remove_interface(association); 115 objServer.remove_interface(itemIface); 116 objServer.remove_interface(itemAssoc); 117 } 118 119 void TachSensor::setupRead(void) 120 { 121 boost::asio::async_read_until( 122 inputDev, readBuf, '\n', 123 [&](const boost::system::error_code& ec, 124 std::size_t /*bytes_transfered*/) { handleResponse(ec); }); 125 } 126 127 void TachSensor::handleResponse(const boost::system::error_code& err) 128 { 129 if (err == boost::system::errc::bad_file_descriptor) 130 { 131 return; // we're being destroyed 132 } 133 bool missing = false; 134 size_t pollTime = pwmPollMs; 135 if (presence) 136 { 137 if (!presence->getValue()) 138 { 139 markAvailable(false); 140 missing = true; 141 pollTime = sensorFailedPollTimeMs; 142 } 143 itemIface->set_property("Present", !missing); 144 } 145 std::istream responseStream(&readBuf); 146 if (!missing) 147 { 148 if (!err) 149 { 150 std::string response; 151 try 152 { 153 std::getline(responseStream, response); 154 rawValue = std::stod(response); 155 responseStream.clear(); 156 updateValue(rawValue); 157 } 158 catch (const std::invalid_argument&) 159 { 160 incrementError(); 161 pollTime = sensorFailedPollTimeMs; 162 } 163 } 164 else 165 { 166 incrementError(); 167 pollTime = sensorFailedPollTimeMs; 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 192 if (redundancy && *redundancy) 193 { 194 (*redundancy) 195 ->update("/xyz/openbmc_project/sensors/fan_tach/" + name, !status); 196 } 197 198 bool curLed = !status; 199 if (led && ledState != curLed) 200 { 201 ledState = curLed; 202 setLed(dbusConnection, *led, curLed); 203 } 204 } 205 206 PresenceSensor::PresenceSensor(const std::string& gpioName, bool inverted, 207 boost::asio::io_service& io, 208 const std::string& name) : 209 inverted(inverted), 210 gpioLine(gpiod::find_line(gpioName)), gpioFd(io), name(name) 211 { 212 if (!gpioLine) 213 { 214 std::cerr << "Error requesting gpio: " << gpioName << "\n"; 215 status = false; 216 return; 217 } 218 219 try 220 { 221 gpioLine.request({"FanSensor", gpiod::line_request::EVENT_BOTH_EDGES, 222 inverted ? gpiod::line_request::FLAG_ACTIVE_LOW : 0}); 223 status = gpioLine.get_value(); 224 225 int gpioLineFd = gpioLine.event_get_fd(); 226 if (gpioLineFd < 0) 227 { 228 std::cerr << "Failed to get " << gpioName << " fd\n"; 229 return; 230 } 231 232 gpioFd.assign(gpioLineFd); 233 } 234 catch (std::system_error&) 235 { 236 std::cerr << "Error reading gpio: " << gpioName << "\n"; 237 status = false; 238 return; 239 } 240 241 monitorPresence(); 242 } 243 244 PresenceSensor::~PresenceSensor() 245 { 246 gpioFd.close(); 247 gpioLine.release(); 248 } 249 250 void PresenceSensor::monitorPresence(void) 251 { 252 gpioFd.async_wait(boost::asio::posix::stream_descriptor::wait_read, 253 [this](const boost::system::error_code& ec) { 254 if (ec == boost::system::errc::bad_file_descriptor) 255 { 256 return; // we're being destroyed 257 } 258 else if (ec) 259 { 260 std::cerr << "Error on presence sensor " << name 261 << " \n"; 262 ; 263 } 264 else 265 { 266 read(); 267 } 268 monitorPresence(); 269 }); 270 } 271 272 void PresenceSensor::read(void) 273 { 274 gpioLine.event_read(); 275 status = gpioLine.get_value(); 276 // Read is invoked when an edge event is detected by monitorPresence 277 if (status) 278 { 279 logFanInserted(name); 280 } 281 else 282 { 283 logFanRemoved(name); 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 association::interface)), 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