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