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