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