1 /** 2 * Copyright 2017 Google Inc. 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 /* Configuration. */ 18 #include "zone.hpp" 19 20 #include "conf.hpp" 21 #include "pid/controller.hpp" 22 #include "pid/ec/pid.hpp" 23 #include "pid/fancontroller.hpp" 24 #include "pid/stepwisecontroller.hpp" 25 #include "pid/thermalcontroller.hpp" 26 #include "pid/tuning.hpp" 27 28 #include <algorithm> 29 #include <chrono> 30 #include <cstring> 31 #include <fstream> 32 #include <iostream> 33 #include <memory> 34 #include <sstream> 35 #include <string> 36 37 using tstamp = std::chrono::high_resolution_clock::time_point; 38 using namespace std::literals::chrono_literals; 39 40 // Enforces minimum duration between events 41 // Rreturns true if event should be allowed, false if disallowed 42 bool allowThrottle(const tstamp& now, const std::chrono::seconds& pace) 43 { 44 static tstamp then; 45 static bool first = true; 46 47 if (first) 48 { 49 // Special case initialization 50 then = now; 51 first = false; 52 53 // Initialization, always allow 54 return true; 55 } 56 57 auto elapsed = now - then; 58 if (elapsed < pace) 59 { 60 // Too soon since last time, disallow 61 return false; 62 } 63 64 // It has been long enough, allow 65 then = now; 66 return true; 67 } 68 69 namespace pid_control 70 { 71 72 double DbusPidZone::getMaxSetPointRequest(void) const 73 { 74 return _maximumSetPoint; 75 } 76 77 bool DbusPidZone::getManualMode(void) const 78 { 79 return _manualMode; 80 } 81 82 void DbusPidZone::setManualMode(bool mode) 83 { 84 _manualMode = mode; 85 86 // If returning to automatic mode, need to restore PWM from PID loop 87 if (!mode) 88 { 89 _redundantWrite = true; 90 } 91 } 92 93 bool DbusPidZone::getFailSafeMode(void) const 94 { 95 // If any keys are present at least one sensor is in fail safe mode. 96 return !_failSafeSensors.empty(); 97 } 98 99 int64_t DbusPidZone::getZoneID(void) const 100 { 101 return _zoneId; 102 } 103 104 void DbusPidZone::addSetPoint(double setpoint) 105 { 106 _SetPoints.push_back(setpoint); 107 } 108 109 void DbusPidZone::addRPMCeiling(double ceiling) 110 { 111 _RPMCeilings.push_back(ceiling); 112 } 113 114 void DbusPidZone::clearRPMCeilings(void) 115 { 116 _RPMCeilings.clear(); 117 } 118 119 void DbusPidZone::clearSetPoints(void) 120 { 121 _SetPoints.clear(); 122 } 123 124 double DbusPidZone::getFailSafePercent(void) const 125 { 126 return _failSafePercent; 127 } 128 129 double DbusPidZone::getMinThermalSetpoint(void) const 130 { 131 return _minThermalOutputSetPt; 132 } 133 134 void DbusPidZone::addFanPID(std::unique_ptr<Controller> pid) 135 { 136 _fans.push_back(std::move(pid)); 137 } 138 139 void DbusPidZone::addThermalPID(std::unique_ptr<Controller> pid) 140 { 141 _thermals.push_back(std::move(pid)); 142 } 143 144 double DbusPidZone::getCachedValue(const std::string& name) 145 { 146 return _cachedValuesByName.at(name); 147 } 148 149 void DbusPidZone::addFanInput(const std::string& fan) 150 { 151 _fanInputs.push_back(fan); 152 } 153 154 void DbusPidZone::addThermalInput(const std::string& therm) 155 { 156 _thermalInputs.push_back(therm); 157 } 158 159 // Updates desired RPM setpoint from optional text file 160 // Returns true if rpmValue updated, false if left unchanged 161 static bool fileParseRpm(const std::string& fileName, double& rpmValue) 162 { 163 static constexpr std::chrono::seconds throttlePace{3}; 164 165 std::string errText; 166 167 try 168 { 169 std::ifstream ifs; 170 ifs.open(fileName); 171 if (ifs) 172 { 173 int value; 174 ifs >> value; 175 176 if (value <= 0) 177 { 178 errText = "File content could not be parsed to a number"; 179 } 180 else if (value <= 100) 181 { 182 errText = "File must contain RPM value, not PWM value"; 183 } 184 else 185 { 186 rpmValue = static_cast<double>(value); 187 return true; 188 } 189 } 190 } 191 catch (const std::exception& e) 192 { 193 errText = "Exception: "; 194 errText += e.what(); 195 } 196 197 // The file is optional, intentionally not an error if file not found 198 if (!(errText.empty())) 199 { 200 tstamp now = std::chrono::high_resolution_clock::now(); 201 if (allowThrottle(now, throttlePace)) 202 { 203 std::cerr << "Unable to read from '" << fileName << "': " << errText 204 << "\n"; 205 } 206 } 207 208 return false; 209 } 210 211 void DbusPidZone::determineMaxSetPointRequest(void) 212 { 213 double max = 0; 214 std::vector<double>::iterator result; 215 216 if (_SetPoints.size() > 0) 217 { 218 result = std::max_element(_SetPoints.begin(), _SetPoints.end()); 219 max = *result; 220 } 221 222 if (_RPMCeilings.size() > 0) 223 { 224 result = std::min_element(_RPMCeilings.begin(), _RPMCeilings.end()); 225 max = std::min(max, *result); 226 } 227 228 /* 229 * If the maximum RPM setpoint output is below the minimum RPM 230 * setpoint, set it to the minimum. 231 */ 232 max = std::max(getMinThermalSetpoint(), max); 233 234 if (tuningEnabled) 235 { 236 /* 237 * We received no setpoints from thermal sensors. 238 * This is a case experienced during tuning where they only specify 239 * fan sensors and one large fan PID for all the fans. 240 */ 241 static constexpr auto setpointpath = "/etc/thermal.d/setpoint"; 242 243 fileParseRpm(setpointpath, max); 244 245 // Allow per-zone setpoint files to override overall setpoint file 246 std::ostringstream zoneSuffix; 247 zoneSuffix << ".zone" << _zoneId; 248 std::string zoneSetpointPath = setpointpath + zoneSuffix.str(); 249 250 fileParseRpm(zoneSetpointPath, max); 251 } 252 253 _maximumSetPoint = max; 254 return; 255 } 256 257 void DbusPidZone::initializeLog(void) 258 { 259 /* Print header for log file: 260 * epoch_ms,setpt,fan1,fan2,fanN,sensor1,sensor2,sensorN,failsafe 261 */ 262 263 _log << "epoch_ms,setpt"; 264 265 for (const auto& f : _fanInputs) 266 { 267 _log << "," << f; 268 } 269 for (const auto& t : _thermalInputs) 270 { 271 _log << "," << t; 272 } 273 _log << ",failsafe"; 274 _log << std::endl; 275 276 return; 277 } 278 279 void DbusPidZone::writeLog(const std::string& value) 280 { 281 _log << value; 282 return; 283 } 284 285 /* 286 * TODO(venture) This is effectively updating the cache and should check if the 287 * values they're using to update it are new or old, or whatnot. For instance, 288 * if we haven't heard from the host in X time we need to detect this failure. 289 * 290 * I haven't decided if the Sensor should have a lastUpdated method or whether 291 * that should be for the ReadInterface or etc... 292 */ 293 294 /** 295 * We want the PID loop to run with values cached, so this will get all the 296 * fan tachs for the loop. 297 */ 298 void DbusPidZone::updateFanTelemetry(void) 299 { 300 /* TODO(venture): Should I just make _log point to /dev/null when logging 301 * is disabled? I think it's a waste to try and log things even if the 302 * data is just being dropped though. 303 */ 304 tstamp now = std::chrono::high_resolution_clock::now(); 305 if (loggingEnabled) 306 { 307 _log << std::chrono::duration_cast<std::chrono::milliseconds>( 308 now.time_since_epoch()) 309 .count(); 310 _log << "," << _maximumSetPoint; 311 } 312 313 for (const auto& f : _fanInputs) 314 { 315 auto sensor = _mgr.getSensor(f); 316 ReadReturn r = sensor->read(); 317 _cachedValuesByName[f] = r.value; 318 int64_t timeout = sensor->getTimeout(); 319 tstamp then = r.updated; 320 321 auto duration = 322 std::chrono::duration_cast<std::chrono::seconds>(now - then) 323 .count(); 324 auto period = std::chrono::seconds(timeout).count(); 325 /* 326 * TODO(venture): We should check when these were last read. 327 * However, these are the fans, so if I'm not getting updated values 328 * for them... what should I do? 329 */ 330 if (loggingEnabled) 331 { 332 _log << "," << r.value; 333 } 334 335 // check if fan fail. 336 if (sensor->getFailed()) 337 { 338 _failSafeSensors.insert(f); 339 } 340 else if (timeout != 0 && duration >= period) 341 { 342 _failSafeSensors.insert(f); 343 } 344 else 345 { 346 // Check if it's in there: remove it. 347 auto kt = _failSafeSensors.find(f); 348 if (kt != _failSafeSensors.end()) 349 { 350 _failSafeSensors.erase(kt); 351 } 352 } 353 } 354 355 if (loggingEnabled) 356 { 357 for (const auto& t : _thermalInputs) 358 { 359 _log << "," << _cachedValuesByName[t]; 360 } 361 } 362 363 return; 364 } 365 366 void DbusPidZone::updateSensors(void) 367 { 368 using namespace std::chrono; 369 /* margin and temp are stored as temp */ 370 tstamp now = high_resolution_clock::now(); 371 372 for (const auto& t : _thermalInputs) 373 { 374 auto sensor = _mgr.getSensor(t); 375 ReadReturn r = sensor->read(); 376 int64_t timeout = sensor->getTimeout(); 377 378 _cachedValuesByName[t] = r.value; 379 tstamp then = r.updated; 380 381 auto duration = duration_cast<std::chrono::seconds>(now - then).count(); 382 auto period = std::chrono::seconds(timeout).count(); 383 384 if (sensor->getFailed()) 385 { 386 _failSafeSensors.insert(t); 387 } 388 else if (timeout != 0 && duration >= period) 389 { 390 // std::cerr << "Entering fail safe mode.\n"; 391 _failSafeSensors.insert(t); 392 } 393 else 394 { 395 // Check if it's in there: remove it. 396 auto kt = _failSafeSensors.find(t); 397 if (kt != _failSafeSensors.end()) 398 { 399 _failSafeSensors.erase(kt); 400 } 401 } 402 } 403 404 return; 405 } 406 407 void DbusPidZone::initializeCache(void) 408 { 409 for (const auto& f : _fanInputs) 410 { 411 _cachedValuesByName[f] = 0; 412 413 // Start all fans in fail-safe mode. 414 _failSafeSensors.insert(f); 415 } 416 417 for (const auto& t : _thermalInputs) 418 { 419 _cachedValuesByName[t] = 0; 420 421 // Start all sensors in fail-safe mode. 422 _failSafeSensors.insert(t); 423 } 424 } 425 426 void DbusPidZone::dumpCache(void) 427 { 428 std::cerr << "Cache values now: \n"; 429 for (const auto& [name, value] : _cachedValuesByName) 430 { 431 std::cerr << name << ": " << value << "\n"; 432 } 433 } 434 435 void DbusPidZone::processFans(void) 436 { 437 for (auto& p : _fans) 438 { 439 p->process(); 440 } 441 442 if (_redundantWrite) 443 { 444 // This is only needed once 445 _redundantWrite = false; 446 } 447 } 448 449 void DbusPidZone::processThermals(void) 450 { 451 for (auto& p : _thermals) 452 { 453 p->process(); 454 } 455 } 456 457 Sensor* DbusPidZone::getSensor(const std::string& name) 458 { 459 return _mgr.getSensor(name); 460 } 461 462 bool DbusPidZone::getRedundantWrite(void) const 463 { 464 return _redundantWrite; 465 } 466 467 bool DbusPidZone::manual(bool value) 468 { 469 std::cerr << "manual: " << value << std::endl; 470 setManualMode(value); 471 return ModeObject::manual(value); 472 } 473 474 bool DbusPidZone::failSafe() const 475 { 476 return getFailSafeMode(); 477 } 478 479 } // namespace pid_control 480