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