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 void DbusPidZone::markSensorMissing(const std::string& name) 100 { 101 if (_missingAcceptable.find(name) != _missingAcceptable.end()) 102 { 103 // Disallow sensors in MissingIsAcceptable list from causing failsafe 104 return; 105 } 106 107 _failSafeSensors.emplace(name); 108 } 109 110 int64_t DbusPidZone::getZoneID(void) const 111 { 112 return _zoneId; 113 } 114 115 void DbusPidZone::addSetPoint(double setPoint, const std::string& name) 116 { 117 /* exclude disabled pidloop from _maximumSetPoint calculation*/ 118 if (!isPidProcessEnabled(name)) 119 { 120 return; 121 } 122 123 _SetPoints.push_back(setPoint); 124 /* 125 * if there are multiple thermal controllers with the same 126 * value, pick the first one in the iterator 127 */ 128 if (_maximumSetPoint < setPoint) 129 { 130 _maximumSetPoint = setPoint; 131 _maximumSetPointName = name; 132 } 133 } 134 135 void DbusPidZone::addRPMCeiling(double ceiling) 136 { 137 _RPMCeilings.push_back(ceiling); 138 } 139 140 void DbusPidZone::clearRPMCeilings(void) 141 { 142 _RPMCeilings.clear(); 143 } 144 145 void DbusPidZone::clearSetPoints(void) 146 { 147 _SetPoints.clear(); 148 _maximumSetPoint = 0; 149 _maximumSetPointName.clear(); 150 } 151 152 double DbusPidZone::getFailSafePercent(void) const 153 { 154 return _failSafePercent; 155 } 156 157 double DbusPidZone::getMinThermalSetPoint(void) const 158 { 159 return _minThermalOutputSetPt; 160 } 161 162 uint64_t DbusPidZone::getCycleIntervalTime(void) const 163 { 164 return _cycleTime.cycleIntervalTimeMS; 165 } 166 167 uint64_t DbusPidZone::getUpdateThermalsCycle(void) const 168 { 169 return _cycleTime.updateThermalsTimeMS; 170 } 171 172 void DbusPidZone::addFanPID(std::unique_ptr<Controller> pid) 173 { 174 _fans.push_back(std::move(pid)); 175 } 176 177 void DbusPidZone::addThermalPID(std::unique_ptr<Controller> pid) 178 { 179 _thermals.push_back(std::move(pid)); 180 } 181 182 double DbusPidZone::getCachedValue(const std::string& name) 183 { 184 return _cachedValuesByName.at(name).scaled; 185 } 186 187 ValueCacheEntry DbusPidZone::getCachedValues(const std::string& name) 188 { 189 return _cachedValuesByName.at(name); 190 } 191 192 void DbusPidZone::setOutputCache(std::string_view name, 193 const ValueCacheEntry& values) 194 { 195 _cachedFanOutputs[std::string{name}] = values; 196 } 197 198 void DbusPidZone::addFanInput(const std::string& fan, bool missingAcceptable) 199 { 200 _fanInputs.push_back(fan); 201 202 if (missingAcceptable) 203 { 204 _missingAcceptable.emplace(fan); 205 } 206 } 207 208 void DbusPidZone::addThermalInput(const std::string& therm, 209 bool missingAcceptable) 210 { 211 _thermalInputs.push_back(therm); 212 213 if (missingAcceptable) 214 { 215 _missingAcceptable.emplace(therm); 216 } 217 } 218 219 // Updates desired RPM setpoint from optional text file 220 // Returns true if rpmValue updated, false if left unchanged 221 static bool fileParseRpm(const std::string& fileName, double& rpmValue) 222 { 223 static constexpr std::chrono::seconds throttlePace{3}; 224 225 std::string errText; 226 227 try 228 { 229 std::ifstream ifs; 230 ifs.open(fileName); 231 if (ifs) 232 { 233 int value; 234 ifs >> value; 235 236 if (value <= 0) 237 { 238 errText = "File content could not be parsed to a number"; 239 } 240 else if (value <= 100) 241 { 242 errText = "File must contain RPM value, not PWM value"; 243 } 244 else 245 { 246 rpmValue = static_cast<double>(value); 247 return true; 248 } 249 } 250 } 251 catch (const std::exception& e) 252 { 253 errText = "Exception: "; 254 errText += e.what(); 255 } 256 257 // The file is optional, intentionally not an error if file not found 258 if (!(errText.empty())) 259 { 260 tstamp now = std::chrono::high_resolution_clock::now(); 261 if (allowThrottle(now, throttlePace)) 262 { 263 std::cerr << "Unable to read from '" << fileName << "': " << errText 264 << "\n"; 265 } 266 } 267 268 return false; 269 } 270 271 void DbusPidZone::determineMaxSetPointRequest(void) 272 { 273 std::vector<double>::iterator result; 274 double minThermalThreshold = getMinThermalSetPoint(); 275 276 if (_RPMCeilings.size() > 0) 277 { 278 result = std::min_element(_RPMCeilings.begin(), _RPMCeilings.end()); 279 // if Max set point is larger than the lowest ceiling, reset to lowest 280 // ceiling. 281 if (*result < _maximumSetPoint) 282 { 283 _maximumSetPoint = *result; 284 // When using lowest ceiling, controller name is ceiling. 285 _maximumSetPointName = "Ceiling"; 286 } 287 } 288 289 /* 290 * If the maximum RPM setpoint output is below the minimum RPM 291 * setpoint, set it to the minimum. 292 */ 293 if (minThermalThreshold >= _maximumSetPoint) 294 { 295 _maximumSetPoint = minThermalThreshold; 296 _maximumSetPointName = "Minimum"; 297 } 298 else if (_maximumSetPointName.compare(_maximumSetPointNamePrev)) 299 { 300 std::cerr << "PID Zone " << _zoneId << " max SetPoint " 301 << _maximumSetPoint << " requested by " 302 << _maximumSetPointName; 303 for (const auto& sensor : _failSafeSensors) 304 { 305 if (sensor.find("Fan") == std::string::npos) 306 { 307 std::cerr << " " << sensor; 308 } 309 } 310 std::cerr << "\n"; 311 _maximumSetPointNamePrev.assign(_maximumSetPointName); 312 } 313 if (tuningEnabled) 314 { 315 /* 316 * We received no setpoints from thermal sensors. 317 * This is a case experienced during tuning where they only specify 318 * fan sensors and one large fan PID for all the fans. 319 */ 320 static constexpr auto setpointpath = "/etc/thermal.d/setpoint"; 321 322 fileParseRpm(setpointpath, _maximumSetPoint); 323 324 // Allow per-zone setpoint files to override overall setpoint file 325 std::ostringstream zoneSuffix; 326 zoneSuffix << ".zone" << _zoneId; 327 std::string zoneSetpointPath = setpointpath + zoneSuffix.str(); 328 329 fileParseRpm(zoneSetpointPath, _maximumSetPoint); 330 } 331 return; 332 } 333 334 void DbusPidZone::initializeLog(void) 335 { 336 /* Print header for log file: 337 * epoch_ms,setpt,fan1,fan1_raw,fan1_pwm,fan1_pwm_raw,fan2,fan2_raw,fan2_pwm,fan2_pwm_raw,fanN,fanN_raw,fanN_pwm,fanN_pwm_raw,sensor1,sensor1_raw,sensor2,sensor2_raw,sensorN,sensorN_raw,failsafe 338 */ 339 340 _log << "epoch_ms,setpt,requester"; 341 342 for (const auto& f : _fanInputs) 343 { 344 _log << "," << f << "," << f << "_raw"; 345 _log << "," << f << "_pwm," << f << "_pwm_raw"; 346 } 347 for (const auto& t : _thermalInputs) 348 { 349 _log << "," << t << "," << t << "_raw"; 350 } 351 352 _log << ",failsafe"; 353 _log << std::endl; 354 } 355 356 void DbusPidZone::writeLog(const std::string& value) 357 { 358 _log << value; 359 } 360 361 /* 362 * TODO(venture) This is effectively updating the cache and should check if the 363 * values they're using to update it are new or old, or whatnot. For instance, 364 * if we haven't heard from the host in X time we need to detect this failure. 365 * 366 * I haven't decided if the Sensor should have a lastUpdated method or whether 367 * that should be for the ReadInterface or etc... 368 */ 369 370 /** 371 * We want the PID loop to run with values cached, so this will get all the 372 * fan tachs for the loop. 373 */ 374 void DbusPidZone::updateFanTelemetry(void) 375 { 376 /* TODO(venture): Should I just make _log point to /dev/null when logging 377 * is disabled? I think it's a waste to try and log things even if the 378 * data is just being dropped though. 379 */ 380 const auto now = std::chrono::high_resolution_clock::now(); 381 if (loggingEnabled) 382 { 383 _log << std::chrono::duration_cast<std::chrono::milliseconds>( 384 now.time_since_epoch()) 385 .count(); 386 _log << "," << _maximumSetPoint; 387 _log << "," << _maximumSetPointName; 388 } 389 390 processSensorInputs</* fanSensorLogging */ true>(_fanInputs, now); 391 392 if (loggingEnabled) 393 { 394 for (const auto& t : _thermalInputs) 395 { 396 const auto& v = _cachedValuesByName[t]; 397 _log << "," << v.scaled << "," << v.unscaled; 398 } 399 } 400 401 return; 402 } 403 404 void DbusPidZone::updateSensors(void) 405 { 406 processSensorInputs</* fanSensorLogging */ false>( 407 _thermalInputs, std::chrono::high_resolution_clock::now()); 408 409 return; 410 } 411 412 void DbusPidZone::initializeCache(void) 413 { 414 auto nan = std::numeric_limits<double>::quiet_NaN(); 415 416 for (const auto& f : _fanInputs) 417 { 418 _cachedValuesByName[f] = {nan, nan}; 419 _cachedFanOutputs[f] = {nan, nan}; 420 421 // Start all fans in fail-safe mode. 422 markSensorMissing(f); 423 } 424 425 for (const auto& t : _thermalInputs) 426 { 427 _cachedValuesByName[t] = {nan, nan}; 428 429 // Start all sensors in fail-safe mode. 430 markSensorMissing(t); 431 } 432 // Initialize Pid FailSafePercent 433 initPidFailSafePercent(); 434 } 435 436 void DbusPidZone::dumpCache(void) 437 { 438 std::cerr << "Cache values now: \n"; 439 for (const auto& [name, value] : _cachedValuesByName) 440 { 441 std::cerr << name << ": " << value.scaled << " " << value.unscaled 442 << "\n"; 443 } 444 445 std::cerr << "Fan outputs now: \n"; 446 for (const auto& [name, value] : _cachedFanOutputs) 447 { 448 std::cerr << name << ": " << value.scaled << " " << value.unscaled 449 << "\n"; 450 } 451 } 452 453 void DbusPidZone::processFans(void) 454 { 455 for (auto& p : _fans) 456 { 457 p->process(); 458 } 459 460 if (_redundantWrite) 461 { 462 // This is only needed once 463 _redundantWrite = false; 464 } 465 } 466 467 void DbusPidZone::processThermals(void) 468 { 469 for (auto& p : _thermals) 470 { 471 p->process(); 472 } 473 } 474 475 Sensor* DbusPidZone::getSensor(const std::string& name) 476 { 477 return _mgr.getSensor(name); 478 } 479 480 bool DbusPidZone::getRedundantWrite(void) const 481 { 482 return _redundantWrite; 483 } 484 485 bool DbusPidZone::manual(bool value) 486 { 487 std::cerr << "manual: " << value << std::endl; 488 setManualMode(value); 489 return ModeObject::manual(value); 490 } 491 492 bool DbusPidZone::failSafe() const 493 { 494 return getFailSafeMode(); 495 } 496 497 void DbusPidZone::addPidControlProcess(std::string name, std::string type, 498 double setpoint, sdbusplus::bus_t& bus, 499 std::string objPath, bool defer) 500 { 501 _pidsControlProcess[name] = std::make_unique<ProcessObject>( 502 bus, objPath.c_str(), 503 defer ? ProcessObject::action::defer_emit 504 : ProcessObject::action::emit_object_added); 505 // Default enable setting = true 506 _pidsControlProcess[name]->enabled(true); 507 _pidsControlProcess[name]->setpoint(setpoint); 508 509 if (type == "temp") 510 { 511 _pidsControlProcess[name]->classType("Temperature"); 512 } 513 else if (type == "margin") 514 { 515 _pidsControlProcess[name]->classType("Margin"); 516 } 517 else if (type == "power") 518 { 519 _pidsControlProcess[name]->classType("Power"); 520 } 521 else if (type == "powersum") 522 { 523 _pidsControlProcess[name]->classType("PowerSum"); 524 } 525 } 526 527 bool DbusPidZone::isPidProcessEnabled(std::string name) 528 { 529 return _pidsControlProcess[name]->enabled(); 530 } 531 532 void DbusPidZone::initPidFailSafePercent(void) 533 { 534 // Currently, find the max failsafe percent pwm settings from zone and 535 // controller, and assign it to zone failsafe percent. 536 537 _failSafePercent = _zoneFailSafePercent; 538 std::cerr << "zone: Zone" << _zoneId 539 << " zoneFailSafePercent: " << _zoneFailSafePercent << "\n"; 540 541 for (const auto& [name, value] : _pidsFailSafePercent) 542 { 543 _failSafePercent = std::max(_failSafePercent, value); 544 std::cerr << "pid: " << name << " failSafePercent: " << value << "\n"; 545 } 546 547 // when the final failsafe percent is zero , it indicate no failsafe 548 // percent is configured , set it to 100% as the default setting. 549 if (_failSafePercent == 0) 550 { 551 _failSafePercent = 100; 552 } 553 std::cerr << "Final zone" << _zoneId 554 << " failSafePercent: " << _failSafePercent << "\n"; 555 } 556 557 void DbusPidZone::addPidFailSafePercent(std::string name, double percent) 558 { 559 _pidsFailSafePercent[name] = percent; 560 } 561 562 std::string DbusPidZone::leader() const 563 { 564 return _maximumSetPointName; 565 } 566 567 void DbusPidZone::updateThermalPowerDebugInterface(std::string pidName, 568 std::string leader, 569 double input, double output) 570 { 571 if (leader.empty()) 572 { 573 _pidsControlProcess[pidName]->output(output); 574 } 575 else 576 { 577 _pidsControlProcess[pidName]->leader(leader); 578 _pidsControlProcess[pidName]->input(input); 579 } 580 } 581 582 } // namespace pid_control 583