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