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