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 * epoch_ms,setpt,fan1,fan2,fanN,sensor1,sensor2,sensorN,failsafe 163 */ 164 165 _log << "epoch_ms,setpt"; 166 167 for (auto& f : _fanInputs) 168 { 169 _log << "," << f; 170 } 171 for (auto& t : _thermalInputs) 172 { 173 _log << "," << t; 174 } 175 _log << ",failsafe"; 176 _log << std::endl; 177 178 return; 179 } 180 181 std::ofstream& PIDZone::getLogHandle(void) 182 { 183 return _log; 184 } 185 #endif 186 187 /* 188 * TODO(venture) This is effectively updating the cache and should check if the 189 * values they're using to update it are new or old, or whatnot. For instance, 190 * if we haven't heard from the host in X time we need to detect this failure. 191 * 192 * I haven't decided if the Sensor should have a lastUpdated method or whether 193 * that should be for the ReadInterface or etc... 194 */ 195 196 /** 197 * We want the PID loop to run with values cached, so this will get all the 198 * fan tachs for the loop. 199 */ 200 void PIDZone::updateFanTelemetry(void) 201 { 202 /* TODO(venture): Should I just make _log point to /dev/null when logging 203 * is disabled? I think it's a waste to try and log things even if the 204 * data is just being dropped though. 205 */ 206 #ifdef __TUNING_LOGGING__ 207 tstamp now = std::chrono::high_resolution_clock::now(); 208 _log << std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()).count(); 209 _log << "," << _maximumRPMSetPt; 210 #endif 211 212 for (auto& f : _fanInputs) 213 { 214 auto& sensor = _mgr.getSensor(f); 215 ReadReturn r = sensor->read(); 216 _cachedValuesByName[f] = r.value; 217 218 /* 219 * TODO(venture): We should check when these were last read. 220 * However, these are the fans, so if I'm not getting updated values 221 * for them... what should I do? 222 */ 223 #ifdef __TUNING_LOGGING__ 224 _log << "," << r.value; 225 #endif 226 } 227 228 #ifdef __TUNING_LOGGING__ 229 for (auto& t : _thermalInputs) 230 { 231 _log << "," << _cachedValuesByName[t]; 232 } 233 #endif 234 235 return; 236 } 237 238 void PIDZone::updateSensors(void) 239 { 240 using namespace std::chrono; 241 /* margin and temp are stored as temp */ 242 tstamp now = high_resolution_clock::now(); 243 244 for (auto& t : _thermalInputs) 245 { 246 auto& sensor = _mgr.getSensor(t); 247 ReadReturn r = sensor->read(); 248 int64_t timeout = sensor->GetTimeout(); 249 250 _cachedValuesByName[t] = r.value; 251 tstamp then = r.updated; 252 253 /* Only go into failsafe if the timeout is set for 254 * the sensor. 255 */ 256 if (timeout > 0) 257 { 258 auto duration = duration_cast<std::chrono::seconds> 259 (now - then).count(); 260 auto period = std::chrono::seconds(timeout).count(); 261 if (duration >= period) 262 { 263 //std::cerr << "Entering fail safe mode.\n"; 264 _failSafeSensors.insert(t); 265 } 266 else 267 { 268 // Check if it's in there: remove it. 269 auto kt = _failSafeSensors.find(t); 270 if (kt != _failSafeSensors.end()) 271 { 272 _failSafeSensors.erase(kt); 273 } 274 } 275 } 276 } 277 278 return; 279 } 280 281 void PIDZone::initializeCache(void) 282 { 283 for (auto& f : _fanInputs) 284 { 285 _cachedValuesByName[f] = 0; 286 } 287 288 for (auto& t : _thermalInputs) 289 { 290 _cachedValuesByName[t] = 0; 291 292 // Start all sensors in fail-safe mode. 293 _failSafeSensors.insert(t); 294 } 295 } 296 297 void PIDZone::dumpCache(void) 298 { 299 std::cerr << "Cache values now: \n"; 300 for (auto& k : _cachedValuesByName) 301 { 302 std::cerr << k.first << ": " << k.second << "\n"; 303 } 304 } 305 306 void PIDZone::process_fans(void) 307 { 308 for (auto& p : _fans) 309 { 310 p->pid_process(); 311 } 312 } 313 314 void PIDZone::process_thermals(void) 315 { 316 for (auto& p : _thermals) 317 { 318 p->pid_process(); 319 } 320 } 321 322 const std::unique_ptr<Sensor>& PIDZone::getSensor(std::string name) 323 { 324 return _mgr.getSensor(name); 325 } 326 327 bool PIDZone::manual(bool value) 328 { 329 std::cerr << "manual: " << value << std::endl; 330 setManualMode(value); 331 return ModeObject::manual(value); 332 } 333 334 bool PIDZone::failSafe() const 335 { 336 return getFailSafeMode(); 337 } 338 339 static std::string GetControlPath(int64_t zone) 340 { 341 return std::string(objectPath) + std::to_string(zone); 342 } 343 344 std::map<int64_t, std::shared_ptr<PIDZone>> BuildZones( 345 std::map<int64_t, PIDConf>& ZonePids, 346 std::map<int64_t, struct zone>& ZoneConfigs, 347 SensorManager& mgr, 348 sdbusplus::bus::bus& ModeControlBus) 349 { 350 std::map<int64_t, std::shared_ptr<PIDZone>> zones; 351 352 for (auto& zi : ZonePids) 353 { 354 auto zoneId = static_cast<int64_t>(zi.first); 355 /* The above shouldn't be necessary but is, and I am having trouble 356 * locating my notes on why. If I recall correctly it was casting it 357 * down to a byte in at least some cases causing weird behaviors. 358 */ 359 360 auto zoneConf = ZoneConfigs.find(zoneId); 361 if (zoneConf == ZoneConfigs.end()) 362 { 363 /* The Zone doesn't have a configuration, bail. */ 364 static constexpr auto err = 365 "Bailing during load, missing Zone Configuration"; 366 std::cerr << err << std::endl; 367 throw std::runtime_error(err); 368 } 369 370 PIDConf& PIDConfig = zi.second; 371 372 auto zone = std::make_shared<PIDZone>( 373 zoneId, 374 zoneConf->second.minthermalrpm, 375 zoneConf->second.failsafepercent, 376 mgr, 377 ModeControlBus, 378 GetControlPath(zi.first).c_str(), 379 deferSignals); 380 381 zones[zoneId] = zone; 382 383 std::cerr << "Zone Id: " << zone->getZoneId() << "\n"; 384 385 // For each PID create a Controller and a Sensor. 386 PIDConf::iterator pit = PIDConfig.begin(); 387 while (pit != PIDConfig.end()) 388 { 389 std::vector<std::string> inputs; 390 std::string name = pit->first; 391 struct controller_info* info = &pit->second; 392 393 std::cerr << "PID name: " << name << "\n"; 394 395 /* 396 * TODO(venture): Need to check if input is known to the 397 * SensorManager. 398 */ 399 if (info->type == "fan") 400 { 401 for (auto i : info->inputs) 402 { 403 inputs.push_back(i); 404 zone->addFanInput(i); 405 } 406 407 auto pid = FanController::CreateFanPid( 408 zone, 409 name, 410 inputs, 411 info->info); 412 zone->addFanPID(std::move(pid)); 413 } 414 else if (info->type == "temp" || info->type == "margin") 415 { 416 for (auto i : info->inputs) 417 { 418 inputs.push_back(i); 419 zone->addThermalInput(i); 420 } 421 422 auto pid = ThermalController::CreateThermalPid( 423 zone, 424 name, 425 inputs, 426 info->setpoint, 427 info->info); 428 zone->addThermalPID(std::move(pid)); 429 } 430 431 std::cerr << "inputs: "; 432 for (auto& i : inputs) 433 { 434 std::cerr << i << ", "; 435 } 436 std::cerr << "\n"; 437 438 ++pit; 439 } 440 441 zone->emit_object_added(); 442 } 443 444 return zones; 445 } 446 447 std::map<int64_t, std::shared_ptr<PIDZone>> BuildZonesFromConfig( 448 std::string& path, 449 SensorManager& mgr, 450 sdbusplus::bus::bus& ModeControlBus) 451 { 452 using namespace libconfig; 453 // zone -> pids 454 std::map<int64_t, PIDConf> pidConfig; 455 // zone -> configs 456 std::map<int64_t, struct zone> zoneConfig; 457 458 std::cerr << "entered BuildZonesFromConfig\n"; 459 460 Config cfg; 461 462 /* The load was modeled after the example source provided. */ 463 try 464 { 465 cfg.readFile(path.c_str()); 466 } 467 catch (const FileIOException& fioex) 468 { 469 std::cerr << "I/O error while reading file: " << fioex.what() << std::endl; 470 throw; 471 } 472 catch (const ParseException& pex) 473 { 474 std::cerr << "Parse error at " << pex.getFile() << ":" << pex.getLine() 475 << " - " << pex.getError() << std::endl; 476 throw; 477 } 478 479 try 480 { 481 const Setting& root = cfg.getRoot(); 482 const Setting& zones = root["zones"]; 483 int count = zones.getLength(); 484 485 /* For each zone. */ 486 for (int i = 0; i < count; ++i) 487 { 488 const Setting& zoneSettings = zones[i]; 489 490 int id; 491 PIDConf thisZone; 492 struct zone thisZoneConfig; 493 494 zoneSettings.lookupValue("id", id); 495 496 thisZoneConfig.minthermalrpm = 497 zoneSettings.lookup("minthermalrpm"); 498 thisZoneConfig.failsafepercent = 499 zoneSettings.lookup("failsafepercent"); 500 501 const Setting& pids = zoneSettings["pids"]; 502 int pidCount = pids.getLength(); 503 504 for (int j = 0; j < pidCount; ++j) 505 { 506 const Setting& pid = pids[j]; 507 508 std::string name; 509 controller_info info; 510 511 /* 512 * Mysteriously if you use lookupValue on these, and the type 513 * is float. It won't work right. 514 * 515 * If the configuration file value doesn't look explicitly like 516 * a float it won't let you assign it to one. 517 */ 518 name = pid.lookup("name").c_str(); 519 info.type = pid.lookup("type").c_str(); 520 /* set-point is only required to be set for thermal. */ 521 /* TODO(venture): Verify this works optionally here. */ 522 info.setpoint = pid.lookup("set-point"); 523 info.info.ts = pid.lookup("pid.sampleperiod"); 524 info.info.p_c = pid.lookup("pid.p_coefficient"); 525 info.info.i_c = pid.lookup("pid.i_coefficient"); 526 info.info.ff_off = pid.lookup("pid.ff_off_coefficient"); 527 info.info.ff_gain = pid.lookup("pid.ff_gain_coefficient"); 528 info.info.i_lim.min = pid.lookup("pid.i_limit.min"); 529 info.info.i_lim.max = pid.lookup("pid.i_limit.max"); 530 info.info.out_lim.min = pid.lookup("pid.out_limit.min"); 531 info.info.out_lim.max = pid.lookup("pid.out_limit.max"); 532 info.info.slew_neg = pid.lookup("pid.slew_neg"); 533 info.info.slew_pos = pid.lookup("pid.slew_pos"); 534 535 std::cerr << "out_lim.min: " << info.info.out_lim.min << "\n"; 536 std::cerr << "out_lim.max: " << info.info.out_lim.max << "\n"; 537 538 const Setting& inputs = pid["inputs"]; 539 int icount = inputs.getLength(); 540 541 for (int z = 0; z < icount; ++z) 542 { 543 std::string v; 544 v = pid["inputs"][z].c_str(); 545 info.inputs.push_back(v); 546 } 547 548 thisZone[name] = info; 549 } 550 551 pidConfig[static_cast<int64_t>(id)] = thisZone; 552 zoneConfig[static_cast<int64_t>(id)] = thisZoneConfig; 553 } 554 } 555 catch (const SettingTypeException &setex) 556 { 557 std::cerr << "Setting '" << setex.getPath() << "' type exception!" << std::endl; 558 throw; 559 } 560 catch (const SettingNotFoundException& snex) 561 { 562 std::cerr << "Setting '" << snex.getPath() << "' not found!" << std::endl; 563 throw; 564 } 565 566 return BuildZones(pidConfig, zoneConfig, mgr, ModeControlBus); 567 } 568