1 // SPDX-License-Identifier: Apache-2.0
2 // SPDX-FileCopyrightText: Copyright 2017 Google Inc
3
4 #include "pidloop.hpp"
5
6 #include "pid/pidcontroller.hpp"
7 #include "pid/tuning.hpp"
8 #include "pid/zone_interface.hpp"
9
10 #include <boost/asio/error.hpp>
11 #include <boost/asio/steady_timer.hpp>
12
13 #include <chrono>
14 #include <cstdint>
15 #include <memory>
16 #include <ostream>
17 #include <sstream>
18
19 namespace pid_control
20 {
21
processThermals(const std::shared_ptr<ZoneInterface> & zone)22 static void processThermals(const std::shared_ptr<ZoneInterface>& zone)
23 {
24 // Get the latest margins.
25 zone->updateSensors();
26 // Zero out the set point goals.
27 zone->clearSetPoints();
28 zone->clearRPMCeilings();
29 // Run the margin PIDs.
30 zone->processThermals();
31 // Get the maximum RPM setpoint.
32 zone->determineMaxSetPointRequest();
33 }
34
pidControlLoop(const std::shared_ptr<ZoneInterface> & zone,const std::shared_ptr<boost::asio::steady_timer> & timer,const bool * isCanceling,bool first,uint64_t cycleCnt)35 void pidControlLoop(const std::shared_ptr<ZoneInterface>& zone,
36 const std::shared_ptr<boost::asio::steady_timer>& timer,
37 const bool* isCanceling, bool first, uint64_t cycleCnt)
38 {
39 if (*isCanceling)
40 {
41 return;
42 }
43
44 std::chrono::steady_clock::time_point nextTime;
45
46 if (first)
47 {
48 if (loggingEnabled)
49 {
50 zone->initializeLog();
51 }
52
53 zone->initializeCache();
54 processThermals(zone);
55
56 nextTime = std::chrono::steady_clock::now();
57 }
58 else
59 {
60 nextTime = timer->expiry();
61 }
62
63 uint64_t msPerFanCycle = zone->getCycleIntervalTime();
64
65 // Push forward the original expiration time of timer, instead of just
66 // resetting it with expires_after() from now, to make sure the interval
67 // is of the expected duration, and not stretched out by CPU time taken.
68 nextTime += std::chrono::milliseconds(msPerFanCycle);
69 timer->expires_at(nextTime);
70 timer->async_wait([zone, timer, cycleCnt, isCanceling, msPerFanCycle](
71 const boost::system::error_code& ec) mutable {
72 if (ec == boost::asio::error::operation_aborted)
73 {
74 return; // timer being canceled, stop loop
75 }
76
77 /*
78 * This should sleep on the conditional wait for the listen thread
79 * to tell us it's in sync. But then we also need a timeout option
80 * in case phosphor-hwmon is down, we can go into some weird failure
81 * more.
82 *
83 * Another approach would be to start all sensors in worst-case
84 * values, and fail-safe mode and then clear out of fail-safe mode
85 * once we start getting values. Which I think it is a solid
86 * approach.
87 *
88 * For now this runs before it necessarily has any sensor values.
89 * For the host sensors they start out in fail-safe mode. For the
90 * fans, they start out as 0 as input and then are adjusted once
91 * they have values.
92 *
93 * If a fan has failed, it's value will be whatever we're told or
94 * however we retrieve it. This program disregards fan values of 0,
95 * so any code providing a fan speed can set to 0 on failure and
96 * that fan value will be effectively ignored. The PID algorithm
97 * will be unhappy but nothing bad will happen.
98 *
99 * TODO(venture): If the fan value is 0 should that loop just be
100 * skipped? Right now, a 0 value is ignored in
101 * FanController::inputProc()
102 */
103
104 // Check if we should just go back to sleep.
105 if (zone->getManualMode())
106 {
107 pidControlLoop(zone, timer, isCanceling, false, cycleCnt);
108 return;
109 }
110
111 // Get the latest fan speeds.
112 zone->updateFanTelemetry();
113
114 uint64_t msPerThermalCycle = zone->getUpdateThermalsCycle();
115
116 // Process thermal cycles at a rate that is less often than fan
117 // cycles. If thermal time is not an exact multiple of fan time,
118 // there will be some remainder left over, to keep the timing
119 // correct, as the intervals are staggered into one another.
120 if (cycleCnt >= msPerThermalCycle)
121 {
122 cycleCnt -= msPerThermalCycle;
123
124 processThermals(zone);
125 }
126
127 // Run the fan PIDs every iteration.
128 zone->processFans();
129
130 if (loggingEnabled)
131 {
132 std::ostringstream out;
133 out << "," << zone->getFailSafeMode() << std::endl;
134 zone->writeLog(out.str());
135 }
136
137 // Count how many milliseconds have elapsed, so we can know when
138 // to perform thermal cycles, in proper ratio with fan cycles.
139 cycleCnt += msPerFanCycle;
140
141 pidControlLoop(zone, timer, isCanceling, false, cycleCnt);
142 });
143 }
144
145 } // namespace pid_control
146