blob: ed8cd0fadf56faef3c6a9259570e5ac8a7820a8f [file] [log] [blame]
Patrick Ventured8012182018-03-08 08:21:38 -08001/**
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
36using tstamp = std::chrono::high_resolution_clock::time_point;
37using namespace std::literals::chrono_literals;
38
39static constexpr bool deferSignals = true;
40static constexpr auto objectPath = "/xyz/openbmc_project/settings/fanctrl/zone";
41
42float PIDZone::getMaxRPMRequest(void) const
43{
44 return _maximumRPMSetPt;
45}
46
47bool PIDZone::getManualMode(void) const
48{
49 return _manualMode;
50}
51
52void PIDZone::setManualMode(bool mode)
53{
54 _manualMode = mode;
55}
56
57bool 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
63int64_t PIDZone::getZoneId(void) const
64{
65 return _zoneId;
66}
67
68void PIDZone::addRPMSetPoint(float setpoint)
69{
70 _RPMSetPoints.push_back(setpoint);
71}
72
73void PIDZone::clearRPMSetPoints(void)
74{
75 _RPMSetPoints.clear();
76}
77
78float PIDZone::getFailSafePercent(void) const
79{
80 return _failSafePercent;
81}
82
83float PIDZone::getMinThermalRpmSetPt(void) const
84{
85 return _minThermalRpmSetPt;
86}
87
88void PIDZone::addFanPID(std::unique_ptr<PIDController> pid)
89{
90 _fans.push_back(std::move(pid));
91}
92
93void PIDZone::addThermalPID(std::unique_ptr<PIDController> pid)
94{
95 _thermals.push_back(std::move(pid));
96}
97
98double PIDZone::getCachedValue(const std::string& name)
99{
100 return _cachedValuesByName.at(name);
101}
102
103void PIDZone::addFanInput(std::string fan)
104{
105 _fanInputs.push_back(fan);
106}
107
108void PIDZone::addThermalInput(std::string therm)
109{
110 _thermalInputs.push_back(therm);
111}
112
113void 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__
159void PIDZone::initializeLog(void)
160{
Patrick Venture5f02ad22018-04-24 10:18:40 -0700161 /* Print header for log file:
162 * epoch_ms,setpt,fan1,fan2,fanN,sensor1,sensor2,sensorN,failsafe
163 */
Patrick Ventured8012182018-03-08 08:21:38 -0800164
165 _log << "epoch_ms,setpt";
166
167 for (auto& f : _fanInputs)
168 {
169 _log << "," << f;
170 }
Patrick Venture5f02ad22018-04-24 10:18:40 -0700171 for (auto& t : _thermalInputs)
172 {
173 _log << "," << t;
174 }
175 _log << ",failsafe";
Patrick Ventured8012182018-03-08 08:21:38 -0800176 _log << std::endl;
177
178 return;
179}
180
181std::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 */
200void 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 {
Patrick Venturefe75b192018-06-08 11:19:43 -0700214 auto& sensor = _mgr.getSensor(f);
Patrick Ventured8012182018-03-08 08:21:38 -0800215 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
Patrick Venture5f02ad22018-04-24 10:18:40 -0700228#ifdef __TUNING_LOGGING__
229 for (auto& t : _thermalInputs)
230 {
231 _log << "," << _cachedValuesByName[t];
232 }
233#endif
234
Patrick Ventured8012182018-03-08 08:21:38 -0800235 return;
236}
237
238void 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 {
Patrick Venturefe75b192018-06-08 11:19:43 -0700246 auto& sensor = _mgr.getSensor(t);
Patrick Ventured8012182018-03-08 08:21:38 -0800247 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
281void 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
297void 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
306void PIDZone::process_fans(void)
307{
308 for (auto& p : _fans)
309 {
310 p->pid_process();
311 }
312}
313
314void PIDZone::process_thermals(void)
315{
316 for (auto& p : _thermals)
317 {
318 p->pid_process();
319 }
320}
321
Patrick Venturefe75b192018-06-08 11:19:43 -0700322const std::unique_ptr<Sensor>& PIDZone::getSensor(std::string name)
Patrick Ventured8012182018-03-08 08:21:38 -0800323{
Patrick Venturefe75b192018-06-08 11:19:43 -0700324 return _mgr.getSensor(name);
Patrick Ventured8012182018-03-08 08:21:38 -0800325}
326
327bool PIDZone::manual(bool value)
328{
329 std::cerr << "manual: " << value << std::endl;
330 setManualMode(value);
331 return ModeObject::manual(value);
332}
333
334bool PIDZone::failSafe() const
335{
336 return getFailSafeMode();
337}
338
339static std::string GetControlPath(int64_t zone)
340{
341 return std::string(objectPath) + std::to_string(zone);
342}
343
344std::map<int64_t, std::shared_ptr<PIDZone>> BuildZones(
345 std::map<int64_t, PIDConf>& ZonePids,
346 std::map<int64_t, struct zone>& ZoneConfigs,
Patrick Venturefe75b192018-06-08 11:19:43 -0700347 SensorManager& mgr,
Patrick Ventured8012182018-03-08 08:21:38 -0800348 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
447std::map<int64_t, std::shared_ptr<PIDZone>> BuildZonesFromConfig(
448 std::string& path,
Patrick Venturefe75b192018-06-08 11:19:43 -0700449 SensorManager& mgr,
Patrick Ventured8012182018-03-08 08:21:38 -0800450 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}