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 "conf.hpp" 19 20 #include "zone.hpp" 21 22 #include <algorithm> 23 #include <chrono> 24 #include <cstring> 25 #include <fstream> 26 #include <iostream> 27 #include <libconfig.h++> 28 #include <memory> 29 30 #include "pid/controller.hpp" 31 #include "pid/fancontroller.hpp" 32 #include "pid/thermalcontroller.hpp" 33 #include "pid/ec/pid.hpp" 34 35 36 using tstamp = std::chrono::high_resolution_clock::time_point; 37 using namespace std::literals::chrono_literals; 38 39 static constexpr bool deferSignals = true; 40 static constexpr auto objectPath = "/xyz/openbmc_project/settings/fanctrl/zone"; 41 42 float PIDZone::getMaxRPMRequest(void) const 43 { 44 return _maximumRPMSetPt; 45 } 46 47 bool PIDZone::getManualMode(void) const 48 { 49 return _manualMode; 50 } 51 52 void PIDZone::setManualMode(bool mode) 53 { 54 _manualMode = mode; 55 } 56 57 bool PIDZone::getFailSafeMode(void) const 58 { 59 // If any keys are present at least one sensor is in fail safe mode. 60 return !_failSafeSensors.empty(); 61 } 62 63 int64_t PIDZone::getZoneId(void) const 64 { 65 return _zoneId; 66 } 67 68 void PIDZone::addRPMSetPoint(float setpoint) 69 { 70 _RPMSetPoints.push_back(setpoint); 71 } 72 73 void PIDZone::clearRPMSetPoints(void) 74 { 75 _RPMSetPoints.clear(); 76 } 77 78 float PIDZone::getFailSafePercent(void) const 79 { 80 return _failSafePercent; 81 } 82 83 float PIDZone::getMinThermalRpmSetPt(void) const 84 { 85 return _minThermalRpmSetPt; 86 } 87 88 void PIDZone::addFanPID(std::unique_ptr<PIDController> pid) 89 { 90 _fans.push_back(std::move(pid)); 91 } 92 93 void PIDZone::addThermalPID(std::unique_ptr<PIDController> pid) 94 { 95 _thermals.push_back(std::move(pid)); 96 } 97 98 double PIDZone::getCachedValue(const std::string& name) 99 { 100 return _cachedValuesByName.at(name); 101 } 102 103 void PIDZone::addFanInput(std::string fan) 104 { 105 _fanInputs.push_back(fan); 106 } 107 108 void PIDZone::addThermalInput(std::string therm) 109 { 110 _thermalInputs.push_back(therm); 111 } 112 113 void PIDZone::determineMaxRPMRequest(void) 114 { 115 float max = 0; 116 std::vector<float>::iterator result; 117 118 if (_RPMSetPoints.size() > 0) 119 { 120 result = std::max_element(_RPMSetPoints.begin(), _RPMSetPoints.end()); 121 max = *result; 122 } 123 124 /* 125 * If the maximum RPM set-point output is below the minimum RPM 126 * set-point, set it to the minimum. 127 */ 128 max = std::max(getMinThermalRpmSetPt(), max); 129 130 #ifdef __TUNING_LOGGING__ 131 /* 132 * We received no set-points from thermal sensors. 133 * This is a case experienced during tuning where they only specify 134 * fan sensors and one large fan PID for all the fans. 135 */ 136 static constexpr auto setpointpath = "/etc/thermal.d/set-point"; 137 try 138 { 139 int value; 140 std::ifstream ifs; 141 ifs.open(setpointpath); 142 if (ifs.good()) { 143 ifs >> value; 144 max = value; // expecting RPM set-point, not pwm% 145 } 146 } 147 catch (const std::exception& e) 148 { 149 /* This exception is uninteresting. */ 150 std::cerr << "Unable to read from '" << setpointpath << "'\n"; 151 } 152 #endif 153 154 _maximumRPMSetPt = max; 155 return; 156 } 157 158 #ifdef __TUNING_LOGGING__ 159 void PIDZone::initializeLog(void) 160 { 161 /* Print header for log file. */ 162 163 _log << "epoch_ms,setpt"; 164 165 for (auto& f : _fanInputs) 166 { 167 _log << "," << f; 168 } 169 _log << std::endl; 170 171 return; 172 } 173 174 std::ofstream& PIDZone::getLogHandle(void) 175 { 176 return _log; 177 } 178 #endif 179 180 /* 181 * TODO(venture) This is effectively updating the cache and should check if the 182 * values they're using to update it are new or old, or whatnot. For instance, 183 * if we haven't heard from the host in X time we need to detect this failure. 184 * 185 * I haven't decided if the Sensor should have a lastUpdated method or whether 186 * that should be for the ReadInterface or etc... 187 */ 188 189 /** 190 * We want the PID loop to run with values cached, so this will get all the 191 * fan tachs for the loop. 192 */ 193 void PIDZone::updateFanTelemetry(void) 194 { 195 /* TODO(venture): Should I just make _log point to /dev/null when logging 196 * is disabled? I think it's a waste to try and log things even if the 197 * data is just being dropped though. 198 */ 199 #ifdef __TUNING_LOGGING__ 200 tstamp now = std::chrono::high_resolution_clock::now(); 201 _log << std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()).count(); 202 _log << "," << _maximumRPMSetPt; 203 #endif 204 205 for (auto& f : _fanInputs) 206 { 207 auto& sensor = _mgr->getSensor(f); 208 ReadReturn r = sensor->read(); 209 _cachedValuesByName[f] = r.value; 210 211 /* 212 * TODO(venture): We should check when these were last read. 213 * However, these are the fans, so if I'm not getting updated values 214 * for them... what should I do? 215 */ 216 #ifdef __TUNING_LOGGING__ 217 _log << "," << r.value; 218 #endif 219 } 220 221 return; 222 } 223 224 void PIDZone::updateSensors(void) 225 { 226 using namespace std::chrono; 227 /* margin and temp are stored as temp */ 228 tstamp now = high_resolution_clock::now(); 229 230 for (auto& t : _thermalInputs) 231 { 232 auto& sensor = _mgr->getSensor(t); 233 ReadReturn r = sensor->read(); 234 int64_t timeout = sensor->GetTimeout(); 235 236 _cachedValuesByName[t] = r.value; 237 tstamp then = r.updated; 238 239 /* Only go into failsafe if the timeout is set for 240 * the sensor. 241 */ 242 if (timeout > 0) 243 { 244 auto duration = duration_cast<std::chrono::seconds> 245 (now - then).count(); 246 auto period = std::chrono::seconds(timeout).count(); 247 if (duration >= period) 248 { 249 //std::cerr << "Entering fail safe mode.\n"; 250 _failSafeSensors.insert(t); 251 } 252 else 253 { 254 // Check if it's in there: remove it. 255 auto kt = _failSafeSensors.find(t); 256 if (kt != _failSafeSensors.end()) 257 { 258 _failSafeSensors.erase(kt); 259 } 260 } 261 } 262 } 263 264 return; 265 } 266 267 void PIDZone::initializeCache(void) 268 { 269 for (auto& f : _fanInputs) 270 { 271 _cachedValuesByName[f] = 0; 272 } 273 274 for (auto& t : _thermalInputs) 275 { 276 _cachedValuesByName[t] = 0; 277 278 // Start all sensors in fail-safe mode. 279 _failSafeSensors.insert(t); 280 } 281 } 282 283 void PIDZone::dumpCache(void) 284 { 285 std::cerr << "Cache values now: \n"; 286 for (auto& k : _cachedValuesByName) 287 { 288 std::cerr << k.first << ": " << k.second << "\n"; 289 } 290 } 291 292 void PIDZone::process_fans(void) 293 { 294 for (auto& p : _fans) 295 { 296 p->pid_process(); 297 } 298 } 299 300 void PIDZone::process_thermals(void) 301 { 302 for (auto& p : _thermals) 303 { 304 p->pid_process(); 305 } 306 } 307 308 std::unique_ptr<Sensor>& PIDZone::getSensor(std::string name) 309 { 310 return _mgr->getSensor(name); 311 } 312 313 bool PIDZone::manual(bool value) 314 { 315 std::cerr << "manual: " << value << std::endl; 316 setManualMode(value); 317 return ModeObject::manual(value); 318 } 319 320 bool PIDZone::failSafe() const 321 { 322 return getFailSafeMode(); 323 } 324 325 static std::string GetControlPath(int64_t zone) 326 { 327 return std::string(objectPath) + std::to_string(zone); 328 } 329 330 std::map<int64_t, std::shared_ptr<PIDZone>> BuildZones( 331 std::map<int64_t, PIDConf>& ZonePids, 332 std::map<int64_t, struct zone>& ZoneConfigs, 333 std::shared_ptr<SensorManager> mgr, 334 sdbusplus::bus::bus& ModeControlBus) 335 { 336 std::map<int64_t, std::shared_ptr<PIDZone>> zones; 337 338 for (auto& zi : ZonePids) 339 { 340 auto zoneId = static_cast<int64_t>(zi.first); 341 /* The above shouldn't be necessary but is, and I am having trouble 342 * locating my notes on why. If I recall correctly it was casting it 343 * down to a byte in at least some cases causing weird behaviors. 344 */ 345 346 auto zoneConf = ZoneConfigs.find(zoneId); 347 if (zoneConf == ZoneConfigs.end()) 348 { 349 /* The Zone doesn't have a configuration, bail. */ 350 static constexpr auto err = 351 "Bailing during load, missing Zone Configuration"; 352 std::cerr << err << std::endl; 353 throw std::runtime_error(err); 354 } 355 356 PIDConf& PIDConfig = zi.second; 357 358 auto zone = std::make_shared<PIDZone>( 359 zoneId, 360 zoneConf->second.minthermalrpm, 361 zoneConf->second.failsafepercent, 362 mgr, 363 ModeControlBus, 364 GetControlPath(zi.first).c_str(), 365 deferSignals); 366 367 zones[zoneId] = zone; 368 369 std::cerr << "Zone Id: " << zone->getZoneId() << "\n"; 370 371 // For each PID create a Controller and a Sensor. 372 PIDConf::iterator pit = PIDConfig.begin(); 373 while (pit != PIDConfig.end()) 374 { 375 std::vector<std::string> inputs; 376 std::string name = pit->first; 377 struct controller_info* info = &pit->second; 378 379 std::cerr << "PID name: " << name << "\n"; 380 381 /* 382 * TODO(venture): Need to check if input is known to the 383 * SensorManager. 384 */ 385 if (info->type == "fan") 386 { 387 for (auto i : info->inputs) 388 { 389 inputs.push_back(i); 390 zone->addFanInput(i); 391 } 392 393 auto pid = FanController::CreateFanPid( 394 zone, 395 name, 396 inputs, 397 info->info); 398 zone->addFanPID(std::move(pid)); 399 } 400 else if (info->type == "temp" || info->type == "margin") 401 { 402 for (auto i : info->inputs) 403 { 404 inputs.push_back(i); 405 zone->addThermalInput(i); 406 } 407 408 auto pid = ThermalController::CreateThermalPid( 409 zone, 410 name, 411 inputs, 412 info->setpoint, 413 info->info); 414 zone->addThermalPID(std::move(pid)); 415 } 416 417 std::cerr << "inputs: "; 418 for (auto& i : inputs) 419 { 420 std::cerr << i << ", "; 421 } 422 std::cerr << "\n"; 423 424 ++pit; 425 } 426 427 zone->emit_object_added(); 428 } 429 430 return zones; 431 } 432 433 std::map<int64_t, std::shared_ptr<PIDZone>> BuildZonesFromConfig( 434 std::string& path, 435 std::shared_ptr<SensorManager> mgr, 436 sdbusplus::bus::bus& ModeControlBus) 437 { 438 using namespace libconfig; 439 // zone -> pids 440 std::map<int64_t, PIDConf> pidConfig; 441 // zone -> configs 442 std::map<int64_t, struct zone> zoneConfig; 443 444 std::cerr << "entered BuildZonesFromConfig\n"; 445 446 Config cfg; 447 448 /* The load was modeled after the example source provided. */ 449 try 450 { 451 cfg.readFile(path.c_str()); 452 } 453 catch (const FileIOException& fioex) 454 { 455 std::cerr << "I/O error while reading file: " << fioex.what() << std::endl; 456 throw; 457 } 458 catch (const ParseException& pex) 459 { 460 std::cerr << "Parse error at " << pex.getFile() << ":" << pex.getLine() 461 << " - " << pex.getError() << std::endl; 462 throw; 463 } 464 465 try 466 { 467 const Setting& root = cfg.getRoot(); 468 const Setting& zones = root["zones"]; 469 int count = zones.getLength(); 470 471 /* For each zone. */ 472 for (int i = 0; i < count; ++i) 473 { 474 const Setting& zoneSettings = zones[i]; 475 476 int id; 477 PIDConf thisZone; 478 struct zone thisZoneConfig; 479 480 zoneSettings.lookupValue("id", id); 481 482 thisZoneConfig.minthermalrpm = 483 zoneSettings.lookup("minthermalrpm"); 484 thisZoneConfig.failsafepercent = 485 zoneSettings.lookup("failsafepercent"); 486 487 const Setting& pids = zoneSettings["pids"]; 488 int pidCount = pids.getLength(); 489 490 for (int j = 0; j < pidCount; ++j) 491 { 492 const Setting& pid = pids[j]; 493 494 std::string name; 495 controller_info info; 496 497 /* 498 * Mysteriously if you use lookupValue on these, and the type 499 * is float. It won't work right. 500 * 501 * If the configuration file value doesn't look explicitly like 502 * a float it won't let you assign it to one. 503 */ 504 name = pid.lookup("name").c_str(); 505 info.type = pid.lookup("type").c_str(); 506 /* set-point is only required to be set for thermal. */ 507 /* TODO(venture): Verify this works optionally here. */ 508 info.setpoint = pid.lookup("set-point"); 509 info.info.ts = pid.lookup("pid.sampleperiod"); 510 info.info.p_c = pid.lookup("pid.p_coefficient"); 511 info.info.i_c = pid.lookup("pid.i_coefficient"); 512 info.info.ff_off = pid.lookup("pid.ff_off_coefficient"); 513 info.info.ff_gain = pid.lookup("pid.ff_gain_coefficient"); 514 info.info.i_lim.min = pid.lookup("pid.i_limit.min"); 515 info.info.i_lim.max = pid.lookup("pid.i_limit.max"); 516 info.info.out_lim.min = pid.lookup("pid.out_limit.min"); 517 info.info.out_lim.max = pid.lookup("pid.out_limit.max"); 518 info.info.slew_neg = pid.lookup("pid.slew_neg"); 519 info.info.slew_pos = pid.lookup("pid.slew_pos"); 520 521 std::cerr << "out_lim.min: " << info.info.out_lim.min << "\n"; 522 std::cerr << "out_lim.max: " << info.info.out_lim.max << "\n"; 523 524 const Setting& inputs = pid["inputs"]; 525 int icount = inputs.getLength(); 526 527 for (int z = 0; z < icount; ++z) 528 { 529 std::string v; 530 v = pid["inputs"][z].c_str(); 531 info.inputs.push_back(v); 532 } 533 534 thisZone[name] = info; 535 } 536 537 pidConfig[static_cast<int64_t>(id)] = thisZone; 538 zoneConfig[static_cast<int64_t>(id)] = thisZoneConfig; 539 } 540 } 541 catch (const SettingTypeException &setex) 542 { 543 std::cerr << "Setting '" << setex.getPath() << "' type exception!" << std::endl; 544 throw; 545 } 546 catch (const SettingNotFoundException& snex) 547 { 548 std::cerr << "Setting '" << snex.getPath() << "' not found!" << std::endl; 549 throw; 550 } 551 552 return BuildZones(pidConfig, zoneConfig, mgr, ModeControlBus); 553 } 554