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