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