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