blob: 3576cac49035ddccd4c4a2436bf98495b441c851 [file] [log] [blame]
Alexander Hansen46a755f2025-10-27 16:31:08 +01001// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright 2017 Google Inc
James Feistce6a3f32019-03-12 11:20:16 -07003
4#include "pidloop.hpp"
5
6#include "pid/pidcontroller.hpp"
7#include "pid/tuning.hpp"
Patrick Venture7a98c192020-08-12 08:35:16 -07008#include "pid/zone_interface.hpp"
James Feistce6a3f32019-03-12 11:20:16 -07009
Ed Tanousf8b6e552025-06-27 13:27:50 -070010#include <boost/asio/error.hpp>
James Feistce6a3f32019-03-12 11:20:16 -070011#include <boost/asio/steady_timer.hpp>
Patrick Venturea83a3ec2020-08-04 09:52:05 -070012
James Feistce6a3f32019-03-12 11:20:16 -070013#include <chrono>
Ed Tanousf8b6e552025-06-27 13:27:50 -070014#include <cstdint>
James Feistce6a3f32019-03-12 11:20:16 -070015#include <memory>
Ed Tanousf8b6e552025-06-27 13:27:50 -070016#include <ostream>
Patrick Venture7a98c192020-08-12 08:35:16 -070017#include <sstream>
James Feistce6a3f32019-03-12 11:20:16 -070018
Patrick Venturea0764872020-08-08 07:48:43 -070019namespace pid_control
20{
21
Ed Tanousd2768c52025-06-26 11:42:57 -070022static void processThermals(const std::shared_ptr<ZoneInterface>& zone)
James Feistce6a3f32019-03-12 11:20:16 -070023{
24 // Get the latest margins.
25 zone->updateSensors();
Patrick Venture9bbf3332019-07-16 10:50:37 -070026 // Zero out the set point goals.
27 zone->clearSetPoints();
James Feistce6a3f32019-03-12 11:20:16 -070028 zone->clearRPMCeilings();
29 // Run the margin PIDs.
30 zone->processThermals();
31 // Get the maximum RPM setpoint.
Patrick Venturef7a2dd52019-07-16 14:31:13 -070032 zone->determineMaxSetPointRequest();
James Feistce6a3f32019-03-12 11:20:16 -070033}
34
Ed Tanousd2768c52025-06-26 11:42:57 -070035void pidControlLoop(const std::shared_ptr<ZoneInterface>& zone,
36 const std::shared_ptr<boost::asio::steady_timer>& timer,
Bonnie Lo0e8fc392022-10-05 10:20:55 +080037 const bool* isCanceling, bool first, uint64_t cycleCnt)
James Feistce6a3f32019-03-12 11:20:16 -070038{
Hao Jiangb6a0b892021-02-21 18:00:45 -080039 if (*isCanceling)
Ed Tanousd2768c52025-06-26 11:42:57 -070040 {
Hao Jiangb6a0b892021-02-21 18:00:45 -080041 return;
Ed Tanousd2768c52025-06-26 11:42:57 -070042 }
Hao Jiangb6a0b892021-02-21 18:00:45 -080043
Josh Lehan9f9a06a2022-12-14 10:39:45 -080044 std::chrono::steady_clock::time_point nextTime;
45
James Feistce6a3f32019-03-12 11:20:16 -070046 if (first)
47 {
Patrick Venturede79ee02019-05-08 14:50:00 -070048 if (loggingEnabled)
James Feistce6a3f32019-03-12 11:20:16 -070049 {
50 zone->initializeLog();
51 }
52
53 zone->initializeCache();
54 processThermals(zone);
Josh Lehan9f9a06a2022-12-14 10:39:45 -080055
56 nextTime = std::chrono::steady_clock::now();
57 }
58 else
59 {
60 nextTime = timer->expiry();
James Feistce6a3f32019-03-12 11:20:16 -070061 }
62
Josh Lehan9f9a06a2022-12-14 10:39:45 -080063 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](
Hao Jiangb6a0b892021-02-21 18:00:45 -080071 const boost::system::error_code& ec) mutable {
72 if (ec == boost::asio::error::operation_aborted)
73 {
74 return; // timer being canceled, stop loop
75 }
James Feist1fe08952019-05-07 09:17:16 -070076
Hao Jiangb6a0b892021-02-21 18:00:45 -080077 /*
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 */
James Feistce6a3f32019-03-12 11:20:16 -0700103
Hao Jiangb6a0b892021-02-21 18:00:45 -0800104 // Check if we should just go back to sleep.
105 if (zone->getManualMode())
106 {
Bonnie Lo0e8fc392022-10-05 10:20:55 +0800107 pidControlLoop(zone, timer, isCanceling, false, cycleCnt);
Hao Jiangb6a0b892021-02-21 18:00:45 -0800108 return;
109 }
James Feistce6a3f32019-03-12 11:20:16 -0700110
Hao Jiangb6a0b892021-02-21 18:00:45 -0800111 // Get the latest fan speeds.
112 zone->updateFanTelemetry();
James Feistce6a3f32019-03-12 11:20:16 -0700113
Josh Lehan9f9a06a2022-12-14 10:39:45 -0800114 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)
Hao Jiangb6a0b892021-02-21 18:00:45 -0800121 {
Josh Lehan9f9a06a2022-12-14 10:39:45 -0800122 cycleCnt -= msPerThermalCycle;
James Feistce6a3f32019-03-12 11:20:16 -0700123
Hao Jiangb6a0b892021-02-21 18:00:45 -0800124 processThermals(zone);
125 }
James Feistce6a3f32019-03-12 11:20:16 -0700126
Hao Jiangb6a0b892021-02-21 18:00:45 -0800127 // Run the fan PIDs every iteration.
128 zone->processFans();
James Feistce6a3f32019-03-12 11:20:16 -0700129
Hao Jiangb6a0b892021-02-21 18:00:45 -0800130 if (loggingEnabled)
131 {
132 std::ostringstream out;
133 out << "," << zone->getFailSafeMode() << std::endl;
134 zone->writeLog(out.str());
135 }
James Feistce6a3f32019-03-12 11:20:16 -0700136
Josh Lehan9f9a06a2022-12-14 10:39:45 -0800137 // 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;
James Feistce6a3f32019-03-12 11:20:16 -0700140
Bonnie Lo0e8fc392022-10-05 10:20:55 +0800141 pidControlLoop(zone, timer, isCanceling, false, cycleCnt);
Hao Jiangb6a0b892021-02-21 18:00:45 -0800142 });
James Feistce6a3f32019-03-12 11:20:16 -0700143}
Patrick Venturea0764872020-08-08 07:48:43 -0700144
145} // namespace pid_control