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