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