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 "PresenceGpio.hpp" 20 #include "SensorPaths.hpp" 21 #include "Thresholds.hpp" 22 #include "Utils.hpp" 23 #include "sensor.hpp" 24 25 #include <boost/asio/buffer.hpp> 26 #include <boost/asio/error.hpp> 27 #include <boost/asio/io_context.hpp> 28 #include <boost/asio/random_access_file.hpp> 29 #include <sdbusplus/asio/connection.hpp> 30 #include <sdbusplus/asio/object_server.hpp> 31 32 #include <charconv> 33 #include <chrono> 34 #include <cstddef> 35 #include <cstdint> 36 #include <iostream> 37 #include <memory> 38 #include <optional> 39 #include <string> 40 #include <system_error> 41 #include <utility> 42 #include <vector> 43 44 static constexpr unsigned int pwmPollMs = 500; 45 46 TachSensor::TachSensor( 47 const std::string& path, const std::string& objectType, 48 sdbusplus::asio::object_server& objectServer, 49 std::shared_ptr<sdbusplus::asio::connection>& conn, 50 std::shared_ptr<PresenceGpio>& presenceGpio, 51 std::optional<RedundancySensor>* redundancy, boost::asio::io_context& io, 52 const std::string& fanName, 53 std::vector<thresholds::Threshold>&& thresholdsIn, 54 const std::string& sensorConfiguration, 55 const std::pair<double, double>& limits, const PowerState& powerState, 56 const std::optional<std::string>& ledIn) : 57 Sensor(escapeName(fanName), std::move(thresholdsIn), sensorConfiguration, 58 objectType, false, false, limits.second, limits.first, conn, 59 powerState), 60 objServer(objectServer), redundancy(redundancy), presence(presenceGpio), 61 inputDev(io, path, boost::asio::random_access_file::read_only), 62 waitTimer(io), path(path), led(ledIn) 63 { 64 sensorInterface = objectServer.add_interface( 65 "/xyz/openbmc_project/sensors/fan_tach/" + name, 66 "xyz.openbmc_project.Sensor.Value"); 67 68 for (const auto& threshold : thresholds) 69 { 70 std::string interface = thresholds::getInterface(threshold.level); 71 thresholdInterfaces[static_cast<size_t>(threshold.level)] = 72 objectServer.add_interface( 73 "/xyz/openbmc_project/sensors/fan_tach/" + name, interface); 74 } 75 association = objectServer.add_interface( 76 "/xyz/openbmc_project/sensors/fan_tach/" + name, 77 association::interface); 78 79 if (presence) 80 { 81 itemIface = 82 objectServer.add_interface("/xyz/openbmc_project/inventory/" + name, 83 "xyz.openbmc_project.Inventory.Item"); 84 itemIface->register_property("PrettyName", 85 std::string()); // unused property 86 itemIface->register_property("Present", true); 87 itemIface->initialize(); 88 itemAssoc = objectServer.add_interface( 89 "/xyz/openbmc_project/inventory/" + name, association::interface); 90 itemAssoc->register_property( 91 "Associations", 92 std::vector<Association>{ 93 {"sensors", "inventory", 94 "/xyz/openbmc_project/sensors/fan_tach/" + name}}); 95 itemAssoc->initialize(); 96 } 97 setInitialProperties(sensor_paths::unitRPMs); 98 } 99 100 TachSensor::~TachSensor() 101 { 102 // close the input dev to cancel async operations 103 inputDev.close(); 104 waitTimer.cancel(); 105 for (const auto& iface : thresholdInterfaces) 106 { 107 objServer.remove_interface(iface); 108 } 109 objServer.remove_interface(sensorInterface); 110 objServer.remove_interface(association); 111 objServer.remove_interface(itemIface); 112 objServer.remove_interface(itemAssoc); 113 } 114 115 void TachSensor::setupRead() 116 { 117 std::weak_ptr<TachSensor> weakRef = weak_from_this(); 118 inputDev.async_read_some_at( 119 0, boost::asio::buffer(readBuf), 120 [weakRef](const boost::system::error_code& ec, std::size_t bytesRead) { 121 std::shared_ptr<TachSensor> self = weakRef.lock(); 122 if (self) 123 { 124 self->handleResponse(ec, bytesRead); 125 } 126 }); 127 } 128 129 void TachSensor::restartRead(size_t pollTime) 130 { 131 std::weak_ptr<TachSensor> weakRef = weak_from_this(); 132 waitTimer.expires_after(std::chrono::milliseconds(pollTime)); 133 waitTimer.async_wait([weakRef](const boost::system::error_code& ec) { 134 if (ec == boost::asio::error::operation_aborted) 135 { 136 return; // we're being canceled 137 } 138 std::shared_ptr<TachSensor> self = weakRef.lock(); 139 if (!self) 140 { 141 return; 142 } 143 self->setupRead(); 144 }); 145 } 146 147 void TachSensor::handleResponse(const boost::system::error_code& err, 148 size_t bytesRead) 149 { 150 if ((err == boost::system::errc::bad_file_descriptor) || 151 (err == boost::asio::error::misc_errors::not_found)) 152 { 153 std::cerr << "TachSensor " << name << " removed " << path << "\n"; 154 return; // we're being destroyed 155 } 156 bool missing = false; 157 size_t pollTime = pwmPollMs; 158 if (presence) 159 { 160 if (!presence->isPresent()) 161 { 162 markAvailable(false); 163 missing = true; 164 pollTime = sensorFailedPollTimeMs; 165 } 166 itemIface->set_property("Present", !missing); 167 } 168 169 if (!missing) 170 { 171 if (!err) 172 { 173 const char* bufEnd = readBuf.data() + bytesRead; 174 int nvalue = 0; 175 std::from_chars_result ret = 176 std::from_chars(readBuf.data(), bufEnd, nvalue); 177 if (ret.ec != std::errc()) 178 { 179 incrementError(); 180 pollTime = sensorFailedPollTimeMs; 181 } 182 else 183 { 184 updateValue(nvalue); 185 } 186 } 187 else 188 { 189 incrementError(); 190 pollTime = sensorFailedPollTimeMs; 191 } 192 } 193 194 restartRead(pollTime); 195 } 196 197 void TachSensor::checkThresholds() 198 { 199 bool status = thresholds::checkThresholds(this); 200 201 if ((redundancy != nullptr) && *redundancy) 202 { 203 (*redundancy) 204 ->update("/xyz/openbmc_project/sensors/fan_tach/" + name, !status); 205 } 206 207 bool curLed = !status; 208 if (led && ledState != curLed) 209 { 210 ledState = curLed; 211 setLed(dbusConnection, *led, curLed); 212 } 213 } 214 215 RedundancySensor::RedundancySensor(size_t count, 216 const std::vector<std::string>& children, 217 sdbusplus::asio::object_server& objectServer, 218 const std::string& sensorConfiguration) : 219 count(count), 220 iface(objectServer.add_interface( 221 "/xyz/openbmc_project/control/FanRedundancy/Tach", 222 "xyz.openbmc_project.Control.FanRedundancy")), 223 association(objectServer.add_interface( 224 "/xyz/openbmc_project/control/FanRedundancy/Tach", 225 association::interface)), 226 objectServer(objectServer) 227 { 228 createAssociation(association, sensorConfiguration); 229 iface->register_property("Collection", children); 230 iface->register_property("Status", std::string("Full")); 231 iface->register_property("AllowedFailures", static_cast<uint8_t>(count)); 232 iface->initialize(); 233 } 234 RedundancySensor::~RedundancySensor() 235 { 236 objectServer.remove_interface(association); 237 objectServer.remove_interface(iface); 238 } 239 void RedundancySensor::update(const std::string& name, bool failed) 240 { 241 statuses[name] = failed; 242 size_t failedCount = 0; 243 244 std::string newState = redundancy::full; 245 for (const auto& [name, status] : statuses) 246 { 247 if (status) 248 { 249 failedCount++; 250 } 251 if (failedCount > count) 252 { 253 newState = redundancy::failed; 254 break; 255 } 256 if (failedCount != 0U) 257 { 258 newState = redundancy::degraded; 259 } 260 } 261 if (state != newState) 262 { 263 if (state == redundancy::full) 264 { 265 logFanRedundancyLost(); 266 } 267 else if (newState == redundancy::full) 268 { 269 logFanRedundancyRestored(); 270 } 271 state = newState; 272 iface->set_property("Status", state); 273 } 274 } 275