xref: /openbmc/phosphor-pid-control/pid/zone.cpp (revision 391b8b0f)
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