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, 35 sdbusplus::asio::object_server &objectServer, 36 std::shared_ptr<sdbusplus::asio::connection> &conn, 37 std::unique_ptr<PresenceSensor> &&presence, 38 boost::asio::io_service &io, const std::string &fanName, 39 std::vector<thresholds::Threshold> &&_thresholds, 40 const std::string &sensorConfiguration) : 41 Sensor(), 42 path(path), objServer(objectServer), dbusConnection(conn), 43 presence(std::move(presence)), 44 name(boost::replace_all_copy(fanName, " ", "_")), 45 configuration(sensorConfiguration), 46 inputDev(io, open(path.c_str(), O_RDONLY)), waitTimer(io), errCount(0), 47 // todo, get these from config 48 maxValue(25000), minValue(0) 49 { 50 thresholds = std::move(_thresholds); 51 sensorInterface = objectServer.add_interface( 52 "/xyz/openbmc_project/sensors/fan_tach/" + name, 53 "xyz.openbmc_project.Sensor.Value"); 54 55 if (thresholds::hasWarningInterface(thresholds)) 56 { 57 thresholdInterfaceWarning = objectServer.add_interface( 58 "/xyz/openbmc_project/sensors/fan_tach/" + name, 59 "xyz.openbmc_project.Sensor.Threshold.Warning"); 60 } 61 if (thresholds::hasCriticalInterface(thresholds)) 62 { 63 thresholdInterfaceCritical = objectServer.add_interface( 64 "/xyz/openbmc_project/sensors/fan_tach/" + name, 65 "xyz.openbmc_project.Sensor.Threshold.Critical"); 66 } 67 setInitialProperties(conn); 68 isPowerOn(dbusConnection); // first call initializes 69 setupRead(); 70 } 71 72 TachSensor::~TachSensor() 73 { 74 // close the input dev to cancel async operations 75 inputDev.close(); 76 waitTimer.cancel(); 77 objServer.remove_interface(thresholdInterfaceWarning); 78 objServer.remove_interface(thresholdInterfaceCritical); 79 objServer.remove_interface(sensorInterface); 80 } 81 82 void TachSensor::setupRead(void) 83 { 84 boost::asio::async_read_until( 85 inputDev, readBuf, '\n', 86 [&](const boost::system::error_code &ec, 87 std::size_t /*bytes_transfered*/) { handleResponse(ec); }); 88 } 89 90 void TachSensor::handleResponse(const boost::system::error_code &err) 91 { 92 if (err == boost::system::errc::bad_file_descriptor) 93 { 94 return; // we're being destroyed 95 } 96 bool missing = false; 97 if (presence) 98 { 99 if (!presence->getValue()) 100 { 101 sensorInterface->set_property( 102 "Value", std::numeric_limits<double>::quiet_NaN()); 103 missing = true; 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 (nvalue != value) 118 { 119 updateValue(nvalue); 120 } 121 errCount = 0; 122 } 123 catch (const std::invalid_argument &) 124 { 125 errCount++; 126 } 127 } 128 else 129 { 130 131 errCount++; 132 } 133 // only send value update once 134 if (errCount == warnAfterErrorCount) 135 { 136 // only an error if power is on 137 if (isPowerOn(dbusConnection)) 138 { 139 std::cerr << "Failure to read sensor " << name << " at " << path 140 << "\n"; 141 updateValue(0); 142 } 143 else 144 { 145 errCount = 0; // check power again in 10 cycles 146 sensorInterface->set_property( 147 "Value", std::numeric_limits<double>::quiet_NaN()); 148 } 149 } 150 } 151 responseStream.clear(); 152 inputDev.close(); 153 int fd = open(path.c_str(), O_RDONLY); 154 if (fd <= 0) 155 { 156 return; // we're no longer valid 157 } 158 inputDev.assign(fd); 159 size_t pollTime = pwmPollMs; 160 if (missing) 161 { 162 pollTime *= 10; 163 } 164 waitTimer.expires_from_now(boost::posix_time::milliseconds(pollTime)); 165 waitTimer.async_wait([&](const boost::system::error_code &ec) { 166 if (ec == boost::asio::error::operation_aborted) 167 { 168 return; // we're being canceled 169 } 170 setupRead(); 171 }); 172 } 173 174 void TachSensor::checkThresholds(void) 175 { 176 thresholds::checkThresholds(this); 177 } 178 179 void TachSensor::updateValue(const double &newValue) 180 { 181 sensorInterface->set_property("Value", newValue); 182 value = newValue; 183 checkThresholds(); 184 } 185 186 void TachSensor::setInitialProperties( 187 std::shared_ptr<sdbusplus::asio::connection> &conn) 188 { 189 // todo, get max and min from configuration 190 sensorInterface->register_property("MaxValue", maxValue); 191 sensorInterface->register_property("MinValue", minValue); 192 sensorInterface->register_property("Value", value); 193 194 for (auto &threshold : thresholds) 195 { 196 std::shared_ptr<sdbusplus::asio::dbus_interface> iface; 197 std::string level; 198 std::string alarm; 199 if (threshold.level == thresholds::Level::CRITICAL) 200 { 201 iface = thresholdInterfaceCritical; 202 if (threshold.direction == thresholds::Direction::HIGH) 203 { 204 level = "CriticalHigh"; 205 alarm = "CriticalAlarmHigh"; 206 } 207 else 208 { 209 level = "CriticalLow"; 210 alarm = "CriticalAlarmLow"; 211 } 212 } 213 else if (threshold.level == thresholds::Level::WARNING) 214 { 215 iface = thresholdInterfaceWarning; 216 if (threshold.direction == thresholds::Direction::HIGH) 217 { 218 level = "WarningHigh"; 219 alarm = "WarningAlarmHigh"; 220 } 221 else 222 { 223 level = "WarningLow"; 224 alarm = "WarningAlarmLow"; 225 } 226 } 227 else 228 { 229 std::cerr << "Unknown threshold level" << threshold.level << "\n"; 230 continue; 231 } 232 if (!iface) 233 { 234 std::cout << "trying to set uninitialized interface\n"; 235 continue; 236 } 237 iface->register_property( 238 level, threshold.value, 239 [&](const double &request, double &oldValue) { 240 oldValue = request; // todo, just let the config do this? 241 threshold.value = request; 242 thresholds::persistThreshold( 243 configuration, 244 "xyz.openbmc_project.Configuration.AspeedFan", threshold, 245 conn); 246 return 1; 247 }); 248 iface->register_property(alarm, false); 249 } 250 if (!sensorInterface->initialize()) 251 { 252 std::cerr << "error initializing value interface\n"; 253 } 254 if (thresholdInterfaceWarning && !thresholdInterfaceWarning->initialize()) 255 { 256 std::cerr << "error initializing warning threshold interface\n"; 257 } 258 259 if (thresholdInterfaceCritical && !thresholdInterfaceCritical->initialize()) 260 { 261 std::cerr << "error initializing critical threshold interface\n"; 262 } 263 } 264 265 PresenceSensor::PresenceSensor(const size_t index, bool inverted, 266 boost::asio::io_service &io) : 267 inverted(inverted), 268 inputDev(io) 269 { 270 // todo: use gpiodaemon 271 std::string device = gpioPath + std::string("gpio") + std::to_string(index); 272 fd = open((device + "/value").c_str(), O_RDONLY); 273 if (fd < 0) 274 { 275 std::cerr << "Error opening gpio " << index << "\n"; 276 return; 277 } 278 279 std::ofstream deviceFile(device + "/edge"); 280 if (!deviceFile.good()) 281 { 282 std::cerr << "Error setting edge " << device << "\n"; 283 return; 284 } 285 deviceFile << "both"; 286 deviceFile.close(); 287 288 inputDev.assign(boost::asio::ip::tcp::v4(), fd); 289 monitorPresence(); 290 read(); 291 } 292 293 PresenceSensor::~PresenceSensor() 294 { 295 inputDev.close(); 296 close(fd); 297 } 298 299 void PresenceSensor::monitorPresence(void) 300 { 301 inputDev.async_wait(boost::asio::ip::tcp::socket::wait_error, 302 [this](const boost::system::error_code &ec) { 303 if (ec == boost::system::errc::bad_file_descriptor) 304 { 305 return; // we're being destroyed 306 } 307 else if (ec) 308 { 309 std::cerr 310 << "Error on presence sensor socket\n"; 311 } 312 else 313 { 314 read(); 315 } 316 monitorPresence(); 317 }); 318 } 319 320 void PresenceSensor::read(void) 321 { 322 constexpr size_t readSize = sizeof("0"); 323 std::string readBuf; 324 readBuf.resize(readSize); 325 lseek(fd, 0, SEEK_SET); 326 size_t r = ::read(fd, readBuf.data(), readSize); 327 if (r != 1) 328 { 329 std::cerr << "Error reading gpio\n"; 330 } 331 else 332 { 333 bool value = std::stoi(readBuf); 334 if (inverted) 335 { 336 value = !value; 337 } 338 status = value; 339 } 340 } 341 342 bool PresenceSensor::getValue(void) 343 { 344 return status; 345 }