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 27 #include <algorithm> 28 #include <chrono> 29 #include <cstring> 30 #include <fstream> 31 #include <iostream> 32 #include <libconfig.h++> 33 #include <memory> 34 35 using tstamp = std::chrono::high_resolution_clock::time_point; 36 using namespace std::literals::chrono_literals; 37 38 double PIDZone::getMaxRPMRequest(void) const 39 { 40 return _maximumRPMSetPt; 41 } 42 43 bool PIDZone::getManualMode(void) const 44 { 45 return _manualMode; 46 } 47 48 void PIDZone::setManualMode(bool mode) 49 { 50 _manualMode = mode; 51 } 52 53 bool PIDZone::getFailSafeMode(void) const 54 { 55 // If any keys are present at least one sensor is in fail safe mode. 56 return !_failSafeSensors.empty(); 57 } 58 59 int64_t PIDZone::getZoneID(void) const 60 { 61 return _zoneId; 62 } 63 64 void PIDZone::addRPMSetPoint(double setpoint) 65 { 66 _RPMSetPoints.push_back(setpoint); 67 } 68 69 void PIDZone::clearRPMSetPoints(void) 70 { 71 _RPMSetPoints.clear(); 72 } 73 74 double PIDZone::getFailSafePercent(void) const 75 { 76 return _failSafePercent; 77 } 78 79 double PIDZone::getMinThermalRPMSetpoint(void) const 80 { 81 return _minThermalRpmSetPt; 82 } 83 84 void PIDZone::addFanPID(std::unique_ptr<Controller> pid) 85 { 86 _fans.push_back(std::move(pid)); 87 } 88 89 void PIDZone::addThermalPID(std::unique_ptr<Controller> pid) 90 { 91 _thermals.push_back(std::move(pid)); 92 } 93 94 double PIDZone::getCachedValue(const std::string& name) 95 { 96 return _cachedValuesByName.at(name); 97 } 98 99 void PIDZone::addFanInput(const std::string& fan) 100 { 101 _fanInputs.push_back(fan); 102 } 103 104 void PIDZone::addThermalInput(const std::string& therm) 105 { 106 _thermalInputs.push_back(therm); 107 } 108 109 void PIDZone::determineMaxRPMRequest(void) 110 { 111 double max = 0; 112 std::vector<double>::iterator result; 113 114 if (_RPMSetPoints.size() > 0) 115 { 116 result = std::max_element(_RPMSetPoints.begin(), _RPMSetPoints.end()); 117 max = *result; 118 } 119 120 /* 121 * If the maximum RPM setpoint output is below the minimum RPM 122 * setpoint, set it to the minimum. 123 */ 124 max = std::max(getMinThermalRPMSetpoint(), max); 125 126 #ifdef __TUNING_LOGGING__ 127 /* 128 * We received no setpoints from thermal sensors. 129 * This is a case experienced during tuning where they only specify 130 * fan sensors and one large fan PID for all the fans. 131 */ 132 static constexpr auto setpointpath = "/etc/thermal.d/setpoint"; 133 try 134 { 135 std::ifstream ifs; 136 ifs.open(setpointpath); 137 if (ifs.good()) 138 { 139 int value; 140 ifs >> value; 141 142 /* expecting RPM setpoint, not pwm% */ 143 max = static_cast<double>(value); 144 } 145 } 146 catch (const std::exception& e) 147 { 148 /* This exception is uninteresting. */ 149 std::cerr << "Unable to read from '" << setpointpath << "'\n"; 150 } 151 #endif 152 153 _maximumRPMSetPt = max; 154 return; 155 } 156 157 #ifdef __TUNING_LOGGING__ 158 void PIDZone::initializeLog(void) 159 { 160 /* Print header for log file: 161 * epoch_ms,setpt,fan1,fan2,fanN,sensor1,sensor2,sensorN,failsafe 162 */ 163 164 _log << "epoch_ms,setpt"; 165 166 for (const auto& f : _fanInputs) 167 { 168 _log << "," << f; 169 } 170 for (const auto& t : _thermalInputs) 171 { 172 _log << "," << t; 173 } 174 _log << ",failsafe"; 175 _log << std::endl; 176 177 return; 178 } 179 180 std::ofstream& PIDZone::getLogHandle(void) 181 { 182 return _log; 183 } 184 #endif 185 186 /* 187 * TODO(venture) This is effectively updating the cache and should check if the 188 * values they're using to update it are new or old, or whatnot. For instance, 189 * if we haven't heard from the host in X time we need to detect this failure. 190 * 191 * I haven't decided if the Sensor should have a lastUpdated method or whether 192 * that should be for the ReadInterface or etc... 193 */ 194 195 /** 196 * We want the PID loop to run with values cached, so this will get all the 197 * fan tachs for the loop. 198 */ 199 void PIDZone::updateFanTelemetry(void) 200 { 201 /* TODO(venture): Should I just make _log point to /dev/null when logging 202 * is disabled? I think it's a waste to try and log things even if the 203 * data is just being dropped though. 204 */ 205 #ifdef __TUNING_LOGGING__ 206 tstamp now = std::chrono::high_resolution_clock::now(); 207 _log << std::chrono::duration_cast<std::chrono::milliseconds>( 208 now.time_since_epoch()) 209 .count(); 210 _log << "," << _maximumRPMSetPt; 211 #endif 212 213 for (const auto& f : _fanInputs) 214 { 215 auto sensor = _mgr.getSensor(f); 216 ReadReturn r = sensor->read(); 217 _cachedValuesByName[f] = r.value; 218 219 /* 220 * TODO(venture): We should check when these were last read. 221 * However, these are the fans, so if I'm not getting updated values 222 * for them... what should I do? 223 */ 224 #ifdef __TUNING_LOGGING__ 225 _log << "," << r.value; 226 #endif 227 } 228 229 #ifdef __TUNING_LOGGING__ 230 for (const auto& t : _thermalInputs) 231 { 232 _log << "," << _cachedValuesByName[t]; 233 } 234 #endif 235 236 return; 237 } 238 239 void PIDZone::updateSensors(void) 240 { 241 using namespace std::chrono; 242 /* margin and temp are stored as temp */ 243 tstamp now = high_resolution_clock::now(); 244 245 for (const auto& t : _thermalInputs) 246 { 247 auto sensor = _mgr.getSensor(t); 248 ReadReturn r = sensor->read(); 249 int64_t timeout = sensor->getTimeout(); 250 251 _cachedValuesByName[t] = r.value; 252 tstamp then = r.updated; 253 254 if (sensor->getFailed()) 255 { 256 _failSafeSensors.insert(t); 257 } 258 /* Only go into failsafe if the timeout is set for 259 * the sensor. 260 */ 261 else if (timeout > 0) 262 { 263 auto duration = 264 duration_cast<std::chrono::seconds>(now - then).count(); 265 auto period = std::chrono::seconds(timeout).count(); 266 if (duration >= period) 267 { 268 // std::cerr << "Entering fail safe mode.\n"; 269 _failSafeSensors.insert(t); 270 } 271 else 272 { 273 // Check if it's in there: remove it. 274 auto kt = _failSafeSensors.find(t); 275 if (kt != _failSafeSensors.end()) 276 { 277 _failSafeSensors.erase(kt); 278 } 279 } 280 } 281 } 282 283 return; 284 } 285 286 void PIDZone::initializeCache(void) 287 { 288 for (const auto& f : _fanInputs) 289 { 290 _cachedValuesByName[f] = 0; 291 } 292 293 for (const auto& t : _thermalInputs) 294 { 295 _cachedValuesByName[t] = 0; 296 297 // Start all sensors in fail-safe mode. 298 _failSafeSensors.insert(t); 299 } 300 } 301 302 void PIDZone::dumpCache(void) 303 { 304 std::cerr << "Cache values now: \n"; 305 for (const auto& k : _cachedValuesByName) 306 { 307 std::cerr << k.first << ": " << k.second << "\n"; 308 } 309 } 310 311 void PIDZone::processFans(void) 312 { 313 for (auto& p : _fans) 314 { 315 p->process(); 316 } 317 } 318 319 void PIDZone::processThermals(void) 320 { 321 for (auto& p : _thermals) 322 { 323 p->process(); 324 } 325 } 326 327 Sensor* PIDZone::getSensor(const std::string& name) 328 { 329 return _mgr.getSensor(name); 330 } 331 332 bool PIDZone::manual(bool value) 333 { 334 std::cerr << "manual: " << value << std::endl; 335 setManualMode(value); 336 return ModeObject::manual(value); 337 } 338 339 bool PIDZone::failSafe() const 340 { 341 return getFailSafeMode(); 342 } 343