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