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