xref: /openbmc/phosphor-pid-control/pid/pidloop.cpp (revision 46a755fce8dc0bdd9c0c5ea09d55d3e5494f335f)
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