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 "failsafeloggers/failsafe_logger_utility.hpp" 22 #include "interfaces.hpp" 23 #include "pid/controller.hpp" 24 #include "pid/tuning.hpp" 25 26 #include <sdbusplus/bus.hpp> 27 28 #include <algorithm> 29 #include <chrono> 30 #include <cstdint> 31 #include <cstring> 32 #include <exception> 33 #include <fstream> 34 #include <iostream> 35 #include <limits> 36 #include <memory> 37 #include <sstream> 38 #include <string> 39 #include <string_view> 40 #include <utility> 41 #include <vector> 42 43 using tstamp = std::chrono::high_resolution_clock::time_point; 44 using namespace std::literals::chrono_literals; 45 46 // Enforces minimum duration between events 47 // Rreturns true if event should be allowed, false if disallowed 48 bool allowThrottle(const tstamp& now, const std::chrono::seconds& pace) 49 { 50 static tstamp then; 51 static bool first = true; 52 53 if (first) 54 { 55 // Special case initialization 56 then = now; 57 first = false; 58 59 // Initialization, always allow 60 return true; 61 } 62 63 auto elapsed = now - then; 64 if (elapsed < pace) 65 { 66 // Too soon since last time, disallow 67 return false; 68 } 69 70 // It has been long enough, allow 71 then = now; 72 return true; 73 } 74 75 namespace pid_control 76 { 77 78 double DbusPidZone::getMaxSetPointRequest(void) const 79 { 80 return _maximumSetPoint; 81 } 82 83 bool DbusPidZone::getManualMode(void) const 84 { 85 return _manualMode; 86 } 87 88 void DbusPidZone::setManualMode(bool mode) 89 { 90 _manualMode = mode; 91 92 // If returning to automatic mode, need to restore PWM from PID loop 93 if (!mode) 94 { 95 _redundantWrite = true; 96 } 97 } 98 99 bool DbusPidZone::getFailSafeMode(void) const 100 { 101 // If any keys are present at least one sensor is in fail safe mode. 102 return !_failSafeSensors.empty(); 103 } 104 105 FailSafeSensorsMap DbusPidZone::getFailSafeSensors(void) const 106 { 107 return _failSafeSensors; 108 } 109 110 void DbusPidZone::markSensorMissing(const std::string& name, 111 const std::string& failReason) 112 { 113 if (_missingAcceptable.find(name) != _missingAcceptable.end()) 114 { 115 // Disallow sensors in MissingIsAcceptable list from causing failsafe 116 outputFailsafeLogWithZone(_zoneId, this->getFailSafeMode(), name, 117 "The sensor is missing but is acceptable."); 118 return; 119 } 120 121 if (_sensorFailSafePercent[name] == 0) 122 { 123 _failSafeSensors[name] = std::pair(failReason, _zoneFailSafePercent); 124 } 125 else 126 { 127 _failSafeSensors[name] = 128 std::pair(failReason, _sensorFailSafePercent[name]); 129 } 130 131 if (debugEnabled) 132 { 133 std::cerr << "Sensor " << name << " marked missing\n"; 134 } 135 } 136 137 int64_t DbusPidZone::getZoneID(void) const 138 { 139 return _zoneId; 140 } 141 142 void DbusPidZone::addSetPoint(double setPoint, const std::string& name) 143 { 144 /* exclude disabled pidloop from _maximumSetPoint calculation*/ 145 if (!isPidProcessEnabled(name)) 146 { 147 return; 148 } 149 150 auto profileName = name; 151 if (getAccSetPoint()) 152 { 153 /* 154 * If the name of controller is Linear_Temp_CPU0. 155 * The profile name will be Temp_CPU0. 156 */ 157 profileName = name.substr(name.find('_') + 1); 158 setPoints[profileName] += setPoint; 159 } 160 else 161 { 162 if (setPoints[profileName] < setPoint) 163 { 164 setPoints[profileName] = setPoint; 165 } 166 } 167 168 /* 169 * if there are multiple thermal controllers with the same 170 * value, pick the first one in the iterator 171 */ 172 if (_maximumSetPoint < setPoints[profileName]) 173 { 174 _maximumSetPoint = setPoints[profileName]; 175 _maximumSetPointName = profileName; 176 } 177 } 178 179 void DbusPidZone::addRPMCeiling(double ceiling) 180 { 181 rpmCeilings.push_back(ceiling); 182 } 183 184 void DbusPidZone::clearRPMCeilings(void) 185 { 186 rpmCeilings.clear(); 187 } 188 189 void DbusPidZone::clearSetPoints(void) 190 { 191 setPoints.clear(); 192 _maximumSetPoint = 0; 193 _maximumSetPointName.clear(); 194 } 195 196 double DbusPidZone::getFailSafePercent(void) 197 { 198 if (_failSafeSensors.empty()) 199 { 200 return _zoneFailSafePercent; 201 } 202 203 FailSafeSensorsMap::iterator maxData = std::max_element( 204 _failSafeSensors.begin(), _failSafeSensors.end(), 205 [](const FailSafeSensorPair& firstData, 206 const FailSafeSensorPair& secondData) { 207 return firstData.second.second < secondData.second.second; 208 }); 209 210 // In dbus/dbusconfiguration.cpp, the default sensor failsafepercent is 0 if 211 // there is no setting in json. 212 // Therfore, if the max failsafe duty in _failSafeSensors is 0, set final 213 // failsafe duty to _zoneFailSafePercent. 214 if ((*maxData).second.second == 0) 215 { 216 return _zoneFailSafePercent; 217 } 218 219 return (*maxData).second.second; 220 } 221 222 double DbusPidZone::getMinThermalSetPoint(void) const 223 { 224 return _minThermalOutputSetPt; 225 } 226 227 uint64_t DbusPidZone::getCycleIntervalTime(void) const 228 { 229 return _cycleTime.cycleIntervalTimeMS; 230 } 231 232 uint64_t DbusPidZone::getUpdateThermalsCycle(void) const 233 { 234 return _cycleTime.updateThermalsTimeMS; 235 } 236 237 void DbusPidZone::addFanPID(std::unique_ptr<Controller> pid) 238 { 239 _fans.push_back(std::move(pid)); 240 } 241 242 void DbusPidZone::addThermalPID(std::unique_ptr<Controller> pid) 243 { 244 _thermals.push_back(std::move(pid)); 245 } 246 247 double DbusPidZone::getCachedValue(const std::string& name) 248 { 249 return _cachedValuesByName.at(name).scaled; 250 } 251 252 ValueCacheEntry DbusPidZone::getCachedValues(const std::string& name) 253 { 254 return _cachedValuesByName.at(name); 255 } 256 257 void DbusPidZone::setOutputCache(std::string_view name, 258 const ValueCacheEntry& values) 259 { 260 _cachedFanOutputs[std::string{name}] = values; 261 } 262 263 void DbusPidZone::addFanInput(const std::string& fan, bool missingAcceptable) 264 { 265 _fanInputs.push_back(fan); 266 267 if (missingAcceptable) 268 { 269 _missingAcceptable.emplace(fan); 270 } 271 } 272 273 void DbusPidZone::addThermalInput(const std::string& therm, 274 bool missingAcceptable) 275 { 276 /* 277 * One sensor may have stepwise and PID at the same time. 278 * Searching the sensor name before inserting it to avoid duplicated sensor 279 * names. 280 */ 281 if (std::find(_thermalInputs.begin(), _thermalInputs.end(), therm) == 282 _thermalInputs.end()) 283 { 284 _thermalInputs.push_back(therm); 285 } 286 287 if (missingAcceptable) 288 { 289 _missingAcceptable.emplace(therm); 290 } 291 } 292 293 // Updates desired RPM setpoint from optional text file 294 // Returns true if rpmValue updated, false if left unchanged 295 static bool fileParseRpm(const std::string& fileName, double& rpmValue) 296 { 297 static constexpr std::chrono::seconds throttlePace{3}; 298 299 std::string errText; 300 301 try 302 { 303 std::ifstream ifs; 304 ifs.open(fileName); 305 if (ifs) 306 { 307 int value; 308 ifs >> value; 309 310 if (value <= 0) 311 { 312 errText = "File content could not be parsed to a number"; 313 } 314 else if (value <= 100) 315 { 316 errText = "File must contain RPM value, not PWM value"; 317 } 318 else 319 { 320 rpmValue = static_cast<double>(value); 321 return true; 322 } 323 } 324 } 325 catch (const std::exception& e) 326 { 327 errText = "Exception: "; 328 errText += e.what(); 329 } 330 331 // The file is optional, intentionally not an error if file not found 332 if (!(errText.empty())) 333 { 334 tstamp now = std::chrono::high_resolution_clock::now(); 335 if (allowThrottle(now, throttlePace)) 336 { 337 std::cerr << "Unable to read from '" << fileName << "': " << errText 338 << "\n"; 339 } 340 } 341 342 return false; 343 } 344 345 void DbusPidZone::determineMaxSetPointRequest(void) 346 { 347 std::vector<double>::iterator result; 348 double minThermalThreshold = getMinThermalSetPoint(); 349 350 if (rpmCeilings.size() > 0) 351 { 352 result = std::min_element(rpmCeilings.begin(), rpmCeilings.end()); 353 // if Max set point is larger than the lowest ceiling, reset to lowest 354 // ceiling. 355 if (*result < _maximumSetPoint) 356 { 357 _maximumSetPoint = *result; 358 // When using lowest ceiling, controller name is ceiling. 359 _maximumSetPointName = "Ceiling"; 360 } 361 } 362 363 /* 364 * Combine the maximum SetPoint Name if the controllers have same profile 365 * name. e.g., PID_BB_INLET_TEMP_C + Stepwise_BB_INLET_TEMP_C. 366 */ 367 if (getAccSetPoint()) 368 { 369 auto profileName = _maximumSetPointName; 370 _maximumSetPointName = ""; 371 372 for (auto& p : _thermals) 373 { 374 auto controllerID = p->getID(); 375 auto found = controllerID.find(profileName); 376 if (found != std::string::npos) 377 { 378 if (_maximumSetPointName.empty()) 379 { 380 _maximumSetPointName = controllerID; 381 } 382 else 383 { 384 _maximumSetPointName += " + " + controllerID; 385 } 386 } 387 } 388 } 389 390 /* 391 * If the maximum RPM setpoint output is below the minimum RPM 392 * setpoint, set it to the minimum. 393 */ 394 if (minThermalThreshold >= _maximumSetPoint) 395 { 396 _maximumSetPoint = minThermalThreshold; 397 _maximumSetPointName = "Minimum"; 398 } 399 else if (_maximumSetPointName.compare(_maximumSetPointNamePrev)) 400 { 401 std::cerr << "PID Zone " << _zoneId << " max SetPoint " 402 << _maximumSetPoint << " requested by " 403 << _maximumSetPointName; 404 for (const auto& sensor : _failSafeSensors) 405 { 406 if (sensor.first.find("Fan") == std::string::npos) 407 { 408 std::cerr << " " << sensor.first; 409 } 410 } 411 std::cerr << "\n"; 412 _maximumSetPointNamePrev.assign(_maximumSetPointName); 413 } 414 if (tuningEnabled) 415 { 416 /* 417 * We received no setpoints from thermal sensors. 418 * This is a case experienced during tuning where they only specify 419 * fan sensors and one large fan PID for all the fans. 420 */ 421 static constexpr auto setpointpath = "/etc/thermal.d/setpoint"; 422 423 fileParseRpm(setpointpath, _maximumSetPoint); 424 425 // Allow per-zone setpoint files to override overall setpoint file 426 std::ostringstream zoneSuffix; 427 zoneSuffix << ".zone" << _zoneId; 428 std::string zoneSetpointPath = setpointpath + zoneSuffix.str(); 429 430 fileParseRpm(zoneSetpointPath, _maximumSetPoint); 431 } 432 return; 433 } 434 435 void DbusPidZone::initializeLog(void) 436 { 437 /* Print header for log file: 438 * 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 439 */ 440 441 _log << "epoch_ms,setpt,requester"; 442 443 for (const auto& f : _fanInputs) 444 { 445 _log << "," << f << "," << f << "_raw"; 446 _log << "," << f << "_pwm," << f << "_pwm_raw"; 447 } 448 for (const auto& t : _thermalInputs) 449 { 450 _log << "," << t << "," << t << "_raw"; 451 } 452 453 _log << ",failsafe"; 454 _log << std::endl; 455 } 456 457 void DbusPidZone::writeLog(const std::string& value) 458 { 459 _log << value; 460 } 461 462 /* 463 * TODO(venture) This is effectively updating the cache and should check if the 464 * values they're using to update it are new or old, or whatnot. For instance, 465 * if we haven't heard from the host in X time we need to detect this failure. 466 * 467 * I haven't decided if the Sensor should have a lastUpdated method or whether 468 * that should be for the ReadInterface or etc... 469 */ 470 471 /** 472 * We want the PID loop to run with values cached, so this will get all the 473 * fan tachs for the loop. 474 */ 475 void DbusPidZone::updateFanTelemetry(void) 476 { 477 /* TODO(venture): Should I just make _log point to /dev/null when logging 478 * is disabled? I think it's a waste to try and log things even if the 479 * data is just being dropped though. 480 */ 481 const auto now = std::chrono::high_resolution_clock::now(); 482 if (loggingEnabled) 483 { 484 _log << std::chrono::duration_cast<std::chrono::milliseconds>( 485 now.time_since_epoch()) 486 .count(); 487 _log << "," << _maximumSetPoint; 488 _log << "," << _maximumSetPointName; 489 } 490 491 processSensorInputs</* fanSensorLogging */ true>(_fanInputs, now); 492 493 if (loggingEnabled) 494 { 495 for (const auto& t : _thermalInputs) 496 { 497 const auto& v = _cachedValuesByName[t]; 498 _log << "," << v.scaled << "," << v.unscaled; 499 } 500 } 501 502 return; 503 } 504 505 void DbusPidZone::updateSensors(void) 506 { 507 processSensorInputs</* fanSensorLogging */ false>( 508 _thermalInputs, std::chrono::high_resolution_clock::now()); 509 510 return; 511 } 512 513 void DbusPidZone::initializeCache(void) 514 { 515 auto nan = std::numeric_limits<double>::quiet_NaN(); 516 517 for (const auto& f : _fanInputs) 518 { 519 _cachedValuesByName[f] = {nan, nan}; 520 _cachedFanOutputs[f] = {nan, nan}; 521 522 // Start all fans in fail-safe mode. 523 markSensorMissing(f, ""); 524 } 525 526 for (const auto& t : _thermalInputs) 527 { 528 _cachedValuesByName[t] = {nan, nan}; 529 530 // Start all sensors in fail-safe mode. 531 markSensorMissing(t, ""); 532 } 533 } 534 535 void DbusPidZone::dumpCache(void) 536 { 537 std::cerr << "Cache values now: \n"; 538 for (const auto& [name, value] : _cachedValuesByName) 539 { 540 std::cerr << name << ": " << value.scaled << " " << value.unscaled 541 << "\n"; 542 } 543 544 std::cerr << "Fan outputs now: \n"; 545 for (const auto& [name, value] : _cachedFanOutputs) 546 { 547 std::cerr << name << ": " << value.scaled << " " << value.unscaled 548 << "\n"; 549 } 550 } 551 552 void DbusPidZone::processFans(void) 553 { 554 for (auto& p : _fans) 555 { 556 p->process(); 557 } 558 559 if (_redundantWrite) 560 { 561 // This is only needed once 562 _redundantWrite = false; 563 } 564 } 565 566 void DbusPidZone::processThermals(void) 567 { 568 for (auto& p : _thermals) 569 { 570 p->process(); 571 } 572 } 573 574 Sensor* DbusPidZone::getSensor(const std::string& name) 575 { 576 return _mgr.getSensor(name); 577 } 578 579 std::vector<std::string> DbusPidZone::getSensorNames(void) 580 { 581 return _thermalInputs; 582 } 583 584 bool DbusPidZone::getRedundantWrite(void) const 585 { 586 return _redundantWrite; 587 } 588 589 bool DbusPidZone::manual(bool value) 590 { 591 std::cerr << "manual: " << value << std::endl; 592 setManualMode(value); 593 return ModeObject::manual(value); 594 } 595 596 bool DbusPidZone::failSafe() const 597 { 598 return getFailSafeMode(); 599 } 600 601 void DbusPidZone::addPidControlProcess( 602 const std::string& name, const std::string& type, double setpoint, 603 sdbusplus::bus_t& bus, const std::string& objPath, bool defer) 604 { 605 _pidsControlProcess[name] = std::make_unique<ProcessObject>( 606 bus, objPath.c_str(), 607 defer ? ProcessObject::action::defer_emit 608 : ProcessObject::action::emit_object_added); 609 // Default enable setting = true 610 _pidsControlProcess[name]->enabled(true); 611 _pidsControlProcess[name]->setpoint(setpoint); 612 613 if (type == "temp") 614 { 615 _pidsControlProcess[name]->classType("Temperature"); 616 } 617 else if (type == "margin") 618 { 619 _pidsControlProcess[name]->classType("Margin"); 620 } 621 else if (type == "power") 622 { 623 _pidsControlProcess[name]->classType("Power"); 624 } 625 else if (type == "powersum") 626 { 627 _pidsControlProcess[name]->classType("PowerSum"); 628 } 629 } 630 631 bool DbusPidZone::isPidProcessEnabled(const std::string& name) 632 { 633 return _pidsControlProcess[name]->enabled(); 634 } 635 636 void DbusPidZone::addPidFailSafePercent(const std::vector<std::string>& inputs, 637 double percent) 638 { 639 for (const auto& sensorName : inputs) 640 { 641 if (_sensorFailSafePercent.find(sensorName) != 642 _sensorFailSafePercent.end()) 643 { 644 _sensorFailSafePercent[sensorName] = 645 std::max(_sensorFailSafePercent[sensorName], percent); 646 if (debugEnabled) 647 { 648 std::cerr << "Sensor " << sensorName 649 << " failsafe percent updated to " 650 << _sensorFailSafePercent[sensorName] << "\n"; 651 } 652 } 653 else 654 { 655 _sensorFailSafePercent[sensorName] = percent; 656 if (debugEnabled) 657 { 658 std::cerr << "Sensor " << sensorName 659 << " failsafe percent set to " << percent << "\n"; 660 } 661 } 662 } 663 } 664 665 std::string DbusPidZone::leader() const 666 { 667 return _maximumSetPointName; 668 } 669 670 void DbusPidZone::updateThermalPowerDebugInterface( 671 std::string pidName, std::string leader, double input, double output) 672 { 673 if (leader.empty()) 674 { 675 _pidsControlProcess[pidName]->output(output); 676 } 677 else 678 { 679 _pidsControlProcess[pidName]->leader(leader); 680 _pidsControlProcess[pidName]->input(input); 681 } 682 } 683 684 bool DbusPidZone::getAccSetPoint(void) const 685 { 686 return _accumulateSetPoint; 687 } 688 689 } // namespace pid_control 690