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/algorithm/string/predicate.hpp> 22 #include <boost/algorithm/string/replace.hpp> 23 #include <boost/date_time/posix_time/posix_time.hpp> 24 #include <fstream> 25 #include <iostream> 26 #include <limits> 27 #include <sdbusplus/asio/connection.hpp> 28 #include <sdbusplus/asio/object_server.hpp> 29 #include <string> 30 31 static constexpr unsigned int pwmPollMs = 500; 32 static constexpr size_t warnAfterErrorCount = 10; 33 34 TachSensor::TachSensor(const std::string& path, const std::string& objectType, 35 sdbusplus::asio::object_server& objectServer, 36 std::shared_ptr<sdbusplus::asio::connection>& conn, 37 std::unique_ptr<PresenceSensor>&& presence, 38 const std::shared_ptr<RedundancySensor>& redundancy, 39 boost::asio::io_service& io, const std::string& fanName, 40 std::vector<thresholds::Threshold>&& _thresholds, 41 const std::string& sensorConfiguration, 42 const std::pair<size_t, size_t>& limits) : 43 Sensor(boost::replace_all_copy(fanName, " ", "_"), path, 44 std::move(_thresholds), sensorConfiguration, objectType, 45 limits.second, limits.first), 46 objServer(objectServer), presence(std::move(presence)), 47 redundancy(redundancy), inputDev(io, open(path.c_str(), O_RDONLY)), 48 waitTimer(io), errCount(0) 49 { 50 sensorInterface = objectServer.add_interface( 51 "/xyz/openbmc_project/sensors/fan_tach/" + name, 52 "xyz.openbmc_project.Sensor.Value"); 53 54 if (thresholds::hasWarningInterface(thresholds)) 55 { 56 thresholdInterfaceWarning = objectServer.add_interface( 57 "/xyz/openbmc_project/sensors/fan_tach/" + name, 58 "xyz.openbmc_project.Sensor.Threshold.Warning"); 59 } 60 if (thresholds::hasCriticalInterface(thresholds)) 61 { 62 thresholdInterfaceCritical = objectServer.add_interface( 63 "/xyz/openbmc_project/sensors/fan_tach/" + name, 64 "xyz.openbmc_project.Sensor.Threshold.Critical"); 65 } 66 association = objectServer.add_interface( 67 "/xyz/openbmc_project/sensors/fan_tach/" + name, 68 "org.openbmc.Associations"); 69 setInitialProperties(conn); 70 setupPowerMatch(conn); 71 setupRead(); 72 } 73 74 TachSensor::~TachSensor() 75 { 76 // close the input dev to cancel async operations 77 inputDev.close(); 78 waitTimer.cancel(); 79 objServer.remove_interface(thresholdInterfaceWarning); 80 objServer.remove_interface(thresholdInterfaceCritical); 81 objServer.remove_interface(sensorInterface); 82 objServer.remove_interface(association); 83 } 84 85 void TachSensor::setupRead(void) 86 { 87 boost::asio::async_read_until( 88 inputDev, readBuf, '\n', 89 [&](const boost::system::error_code& ec, 90 std::size_t /*bytes_transfered*/) { handleResponse(ec); }); 91 } 92 93 void TachSensor::handleResponse(const boost::system::error_code& err) 94 { 95 if (err == boost::system::errc::bad_file_descriptor) 96 { 97 return; // we're being destroyed 98 } 99 bool missing = false; 100 size_t pollTime = pwmPollMs; 101 if (presence) 102 { 103 if (!presence->getValue()) 104 { 105 updateValue(std::numeric_limits<double>::quiet_NaN()); 106 missing = true; 107 pollTime = sensorFailedPollTimeMs; 108 } 109 } 110 std::istream responseStream(&readBuf); 111 if (!missing) 112 { 113 if (!err) 114 { 115 std::string response; 116 try 117 { 118 std::getline(responseStream, response); 119 float nvalue = std::stof(response); 120 responseStream.clear(); 121 if (overridenState) 122 { 123 nvalue = overriddenValue; 124 } 125 if (nvalue != value) 126 { 127 updateValue(nvalue); 128 } 129 errCount = 0; 130 } 131 catch (const std::invalid_argument&) 132 { 133 errCount++; 134 } 135 } 136 else 137 { 138 if (!isPowerOn()) 139 { 140 errCount = 0; 141 updateValue(std::numeric_limits<double>::quiet_NaN()); 142 } 143 else 144 { 145 pollTime = sensorFailedPollTimeMs; 146 errCount++; 147 } 148 } 149 if (errCount >= warnAfterErrorCount) 150 { 151 // only print once 152 if (errCount == warnAfterErrorCount) 153 { 154 std::cerr << "Failure to read sensor " << name << " at " << path 155 << " ec:" << err << "\n"; 156 } 157 updateValue(0); 158 } 159 } 160 responseStream.clear(); 161 inputDev.close(); 162 int fd = open(path.c_str(), O_RDONLY); 163 if (fd <= 0) 164 { 165 return; // we're no longer valid 166 } 167 inputDev.assign(fd); 168 waitTimer.expires_from_now(boost::posix_time::milliseconds(pollTime)); 169 waitTimer.async_wait([&](const boost::system::error_code& ec) { 170 if (ec == boost::asio::error::operation_aborted) 171 { 172 return; // we're being canceled 173 } 174 setupRead(); 175 }); 176 } 177 178 void TachSensor::checkThresholds(void) 179 { 180 bool status = thresholds::checkThresholds(this); 181 if (redundancy) 182 { 183 redundancy->update("/xyz/openbmc_project/sensors/fan_tach/" + name, 184 !status); 185 } 186 } 187 188 PresenceSensor::PresenceSensor(const size_t index, bool inverted, 189 boost::asio::io_service& io) : 190 inverted(inverted), 191 inputDev(io) 192 { 193 // todo: use gpiodaemon 194 std::string device = gpioPath + std::string("gpio") + std::to_string(index); 195 fd = open((device + "/value").c_str(), O_RDONLY); 196 if (fd < 0) 197 { 198 std::cerr << "Error opening gpio " << index << "\n"; 199 return; 200 } 201 202 std::ofstream deviceFile(device + "/edge"); 203 if (!deviceFile.good()) 204 { 205 std::cerr << "Error setting edge " << device << "\n"; 206 return; 207 } 208 deviceFile << "both"; 209 deviceFile.close(); 210 211 inputDev.assign(boost::asio::ip::tcp::v4(), fd); 212 monitorPresence(); 213 read(); 214 } 215 216 PresenceSensor::~PresenceSensor() 217 { 218 inputDev.close(); 219 close(fd); 220 } 221 222 void PresenceSensor::monitorPresence(void) 223 { 224 inputDev.async_wait(boost::asio::ip::tcp::socket::wait_error, 225 [this](const boost::system::error_code& ec) { 226 if (ec == boost::system::errc::bad_file_descriptor) 227 { 228 return; // we're being destroyed 229 } 230 else if (ec) 231 { 232 std::cerr 233 << "Error on presence sensor socket\n"; 234 } 235 else 236 { 237 read(); 238 } 239 monitorPresence(); 240 }); 241 } 242 243 void PresenceSensor::read(void) 244 { 245 constexpr size_t readSize = sizeof("0"); 246 std::string readBuf; 247 readBuf.resize(readSize); 248 lseek(fd, 0, SEEK_SET); 249 size_t r = ::read(fd, readBuf.data(), readSize); 250 if (r != readSize) 251 { 252 std::cerr << "Error reading gpio\n"; 253 } 254 else 255 { 256 bool value = std::stoi(readBuf); 257 if (inverted) 258 { 259 value = !value; 260 } 261 status = value; 262 } 263 } 264 265 bool PresenceSensor::getValue(void) 266 { 267 return status; 268 } 269 270 RedundancySensor::RedundancySensor(size_t count, 271 const std::vector<std::string>& children, 272 sdbusplus::asio::object_server& objectServer, 273 const std::string& sensorConfiguration) : 274 count(count), 275 iface(objectServer.add_interface( 276 "/xyz/openbmc_project/control/FanRedundancy/Tach", 277 "xyz.openbmc_project.Control.FanRedundancy")), 278 association(objectServer.add_interface( 279 "/xyz/openbmc_project/control/FanRedundancy/Tach", 280 "org.openbmc.Associations")), 281 objectServer(objectServer) 282 { 283 createAssociation(association, sensorConfiguration); 284 iface->register_property("Collection", children); 285 iface->register_property("Status", std::string("Full")); 286 iface->register_property("AllowedFailures", static_cast<uint8_t>(count)); 287 iface->initialize(); 288 } 289 RedundancySensor::~RedundancySensor() 290 { 291 objectServer.remove_interface(association); 292 objectServer.remove_interface(iface); 293 } 294 void RedundancySensor::update(const std::string& name, bool failed) 295 { 296 statuses[name] = failed; 297 size_t failedCount = 0; 298 299 std::string state = "Full"; 300 for (const auto& status : statuses) 301 { 302 if (status.second) 303 { 304 failedCount++; 305 } 306 if (failedCount > count) 307 { 308 state = "Failed"; 309 break; 310 } 311 else if (failedCount) 312 { 313 state = "Degraded"; 314 } 315 } 316 iface->set_property("Status", state); 317 } 318