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