1ce6a3f36SJames Feist /**
2ce6a3f36SJames Feist  * Copyright 2017 Google Inc.
3ce6a3f36SJames Feist  *
4ce6a3f36SJames Feist  * Licensed under the Apache License, Version 2.0 (the "License");
5ce6a3f36SJames Feist  * you may not use this file except in compliance with the License.
6ce6a3f36SJames Feist  * You may obtain a copy of the License at
7ce6a3f36SJames Feist  *
8ce6a3f36SJames Feist  *     http://www.apache.org/licenses/LICENSE-2.0
9ce6a3f36SJames Feist  *
10ce6a3f36SJames Feist  * Unless required by applicable law or agreed to in writing, software
11ce6a3f36SJames Feist  * distributed under the License is distributed on an "AS IS" BASIS,
12ce6a3f36SJames Feist  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13ce6a3f36SJames Feist  * See the License for the specific language governing permissions and
14ce6a3f36SJames Feist  * limitations under the License.
15ce6a3f36SJames Feist  */
16ce6a3f36SJames Feist 
17ce6a3f36SJames Feist #include "pidloop.hpp"
18ce6a3f36SJames Feist 
19ce6a3f36SJames Feist #include "pid/pidcontroller.hpp"
20ce6a3f36SJames Feist #include "pid/tuning.hpp"
217a98c19aSPatrick Venture #include "pid/zone_interface.hpp"
22ce6a3f36SJames Feist #include "sensors/sensor.hpp"
23ce6a3f36SJames Feist 
24ce6a3f36SJames Feist #include <boost/asio/steady_timer.hpp>
25a83a3eccSPatrick Venture 
26ce6a3f36SJames Feist #include <chrono>
27ce6a3f36SJames Feist #include <map>
28ce6a3f36SJames Feist #include <memory>
297a98c19aSPatrick Venture #include <sstream>
30ce6a3f36SJames Feist #include <thread>
31ce6a3f36SJames Feist #include <vector>
32ce6a3f36SJames Feist 
33a076487aSPatrick Venture namespace pid_control
34a076487aSPatrick Venture {
35a076487aSPatrick Venture 
processThermals(std::shared_ptr<ZoneInterface> zone)365301aae3SJohnathan Mantey static void processThermals(std::shared_ptr<ZoneInterface> zone)
37ce6a3f36SJames Feist {
38ce6a3f36SJames Feist     // Get the latest margins.
39ce6a3f36SJames Feist     zone->updateSensors();
409bbf333dSPatrick Venture     // Zero out the set point goals.
419bbf333dSPatrick Venture     zone->clearSetPoints();
42ce6a3f36SJames Feist     zone->clearRPMCeilings();
43ce6a3f36SJames Feist     // Run the margin PIDs.
44ce6a3f36SJames Feist     zone->processThermals();
45ce6a3f36SJames Feist     // Get the maximum RPM setpoint.
46f7a2dd5cSPatrick Venture     zone->determineMaxSetPointRequest();
47ce6a3f36SJames Feist }
48ce6a3f36SJames Feist 
pidControlLoop(std::shared_ptr<ZoneInterface> zone,std::shared_ptr<boost::asio::steady_timer> timer,const bool * isCanceling,bool first,uint64_t cycleCnt)495301aae3SJohnathan Mantey void pidControlLoop(std::shared_ptr<ZoneInterface> zone,
505301aae3SJohnathan Mantey                     std::shared_ptr<boost::asio::steady_timer> timer,
510e8fc398SBonnie Lo                     const bool* isCanceling, bool first, uint64_t cycleCnt)
52ce6a3f36SJames Feist {
53b6a0b89eSHao Jiang     if (*isCanceling)
54b6a0b89eSHao Jiang         return;
55b6a0b89eSHao Jiang 
56*9f9a06aaSJosh Lehan     std::chrono::steady_clock::time_point nextTime;
57*9f9a06aaSJosh Lehan 
58ce6a3f36SJames Feist     if (first)
59ce6a3f36SJames Feist     {
60de79ee05SPatrick Venture         if (loggingEnabled)
61ce6a3f36SJames Feist         {
62ce6a3f36SJames Feist             zone->initializeLog();
63ce6a3f36SJames Feist         }
64ce6a3f36SJames Feist 
65ce6a3f36SJames Feist         zone->initializeCache();
66ce6a3f36SJames Feist         processThermals(zone);
67*9f9a06aaSJosh Lehan 
68*9f9a06aaSJosh Lehan         nextTime = std::chrono::steady_clock::now();
69*9f9a06aaSJosh Lehan     }
70*9f9a06aaSJosh Lehan     else
71*9f9a06aaSJosh Lehan     {
72*9f9a06aaSJosh Lehan         nextTime = timer->expiry();
73ce6a3f36SJames Feist     }
74ce6a3f36SJames Feist 
75*9f9a06aaSJosh Lehan     uint64_t msPerFanCycle = zone->getCycleIntervalTime();
76*9f9a06aaSJosh Lehan 
77*9f9a06aaSJosh Lehan     // Push forward the original expiration time of timer, instead of just
78*9f9a06aaSJosh Lehan     // resetting it with expires_after() from now, to make sure the interval
79*9f9a06aaSJosh Lehan     // is of the expected duration, and not stretched out by CPU time taken.
80*9f9a06aaSJosh Lehan     nextTime += std::chrono::milliseconds(msPerFanCycle);
81*9f9a06aaSJosh Lehan     timer->expires_at(nextTime);
82*9f9a06aaSJosh Lehan     timer->async_wait([zone, timer, cycleCnt, isCanceling, msPerFanCycle](
83b6a0b89eSHao Jiang                           const boost::system::error_code& ec) mutable {
841fe08952SJames Feist         if (ec == boost::asio::error::operation_aborted)
851fe08952SJames Feist         {
861fe08952SJames Feist             return; // timer being canceled, stop loop
871fe08952SJames Feist         }
881fe08952SJames Feist 
89ce6a3f36SJames Feist         /*
90ce6a3f36SJames Feist          * This should sleep on the conditional wait for the listen thread
91ce6a3f36SJames Feist          * to tell us it's in sync.  But then we also need a timeout option
92ce6a3f36SJames Feist          * in case phosphor-hwmon is down, we can go into some weird failure
93ce6a3f36SJames Feist          * more.
94ce6a3f36SJames Feist          *
95ce6a3f36SJames Feist          * Another approach would be to start all sensors in worst-case
96ce6a3f36SJames Feist          * values, and fail-safe mode and then clear out of fail-safe mode
97ce6a3f36SJames Feist          * once we start getting values.  Which I think it is a solid
98ce6a3f36SJames Feist          * approach.
99ce6a3f36SJames Feist          *
100ce6a3f36SJames Feist          * For now this runs before it necessarily has any sensor values.
101ce6a3f36SJames Feist          * For the host sensors they start out in fail-safe mode.  For the
102ce6a3f36SJames Feist          * fans, they start out as 0 as input and then are adjusted once
103ce6a3f36SJames Feist          * they have values.
104ce6a3f36SJames Feist          *
105ce6a3f36SJames Feist          * If a fan has failed, it's value will be whatever we're told or
106ce6a3f36SJames Feist          * however we retrieve it.  This program disregards fan values of 0,
107ce6a3f36SJames Feist          * so any code providing a fan speed can set to 0 on failure and
108ce6a3f36SJames Feist          * that fan value will be effectively ignored.  The PID algorithm
109ce6a3f36SJames Feist          * will be unhappy but nothing bad will happen.
110ce6a3f36SJames Feist          *
111ce6a3f36SJames Feist          * TODO(venture): If the fan value is 0 should that loop just be
112ce6a3f36SJames Feist          * skipped? Right now, a 0 value is ignored in
113ce6a3f36SJames Feist          * FanController::inputProc()
114ce6a3f36SJames Feist          */
115ce6a3f36SJames Feist 
116ce6a3f36SJames Feist         // Check if we should just go back to sleep.
117ce6a3f36SJames Feist         if (zone->getManualMode())
118ce6a3f36SJames Feist         {
1190e8fc398SBonnie Lo             pidControlLoop(zone, timer, isCanceling, false, cycleCnt);
120ce6a3f36SJames Feist             return;
121ce6a3f36SJames Feist         }
122ce6a3f36SJames Feist 
123ce6a3f36SJames Feist         // Get the latest fan speeds.
124ce6a3f36SJames Feist         zone->updateFanTelemetry();
125ce6a3f36SJames Feist 
126*9f9a06aaSJosh Lehan         uint64_t msPerThermalCycle = zone->getUpdateThermalsCycle();
127*9f9a06aaSJosh Lehan 
128*9f9a06aaSJosh Lehan         // Process thermal cycles at a rate that is less often than fan
129*9f9a06aaSJosh Lehan         // cycles. If thermal time is not an exact multiple of fan time,
130*9f9a06aaSJosh Lehan         // there will be some remainder left over, to keep the timing
131*9f9a06aaSJosh Lehan         // correct, as the intervals are staggered into one another.
132*9f9a06aaSJosh Lehan         if (cycleCnt >= msPerThermalCycle)
133ce6a3f36SJames Feist         {
134*9f9a06aaSJosh Lehan             cycleCnt -= msPerThermalCycle;
135ce6a3f36SJames Feist 
136ce6a3f36SJames Feist             processThermals(zone);
137ce6a3f36SJames Feist         }
138ce6a3f36SJames Feist 
139ce6a3f36SJames Feist         // Run the fan PIDs every iteration.
140ce6a3f36SJames Feist         zone->processFans();
141ce6a3f36SJames Feist 
142de79ee05SPatrick Venture         if (loggingEnabled)
143ce6a3f36SJames Feist         {
1447a98c19aSPatrick Venture             std::ostringstream out;
1457a98c19aSPatrick Venture             out << "," << zone->getFailSafeMode() << std::endl;
1467a98c19aSPatrick Venture             zone->writeLog(out.str());
147ce6a3f36SJames Feist         }
148ce6a3f36SJames Feist 
149*9f9a06aaSJosh Lehan         // Count how many milliseconds have elapsed, so we can know when
150*9f9a06aaSJosh Lehan         // to perform thermal cycles, in proper ratio with fan cycles.
151*9f9a06aaSJosh Lehan         cycleCnt += msPerFanCycle;
152ce6a3f36SJames Feist 
1530e8fc398SBonnie Lo         pidControlLoop(zone, timer, isCanceling, false, cycleCnt);
154ce6a3f36SJames Feist     });
155ce6a3f36SJames Feist }
156a076487aSPatrick Venture 
157a076487aSPatrick Venture } // namespace pid_control
158