xref: /openbmc/phosphor-pid-control/experiments/drive.cpp (revision 46a755fce8dc0bdd9c0c5ea09d55d3e5494f335f)
1 // SPDX-License-Identifier: Apache-2.0
2 // SPDX-FileCopyrightText: Copyright 2017 Google Inc
3 
4 #include "drive.hpp"
5 
6 #include "interfaces.hpp"
7 #include "sensor.hpp"
8 #include "sensors/pluggable.hpp"
9 #include "sysfs/sysfsread.hpp"
10 #include "sysfs/sysfswrite.hpp"
11 
12 #include <cerrno>
13 #include <chrono>
14 #include <cstdint>
15 #include <iostream>
16 #include <memory>
17 #include <string>
18 #include <tuple>
19 #include <utility>
20 #include <vector>
21 
22 namespace pid_control
23 {
24 
25 using tstamp = std::chrono::high_resolution_clock::time_point;
26 
27 #define DRIVE_TIME 1
28 #define DRIVE_GOAL 2
29 #define DRIVE DRIVE_TIME
30 #define MAX_PWM 255
31 
Create(const std::string & readpath,const std::string & writepath)32 static std::unique_ptr<Sensor> Create(const std::string& readpath,
33                                       const std::string& writepath)
34 {
35     return std::make_unique<PluggableSensor>(
36         readpath, 0, /* default the timeout to disabled */
37         std::make_unique<SysFsRead>(readpath),
38         std::make_unique<SysFsWrite>(writepath, 0, MAX_PWM));
39 }
40 
getAverage(std::tuple<tstamp,int64_t,int64_t> & values)41 int64_t getAverage(std::tuple<tstamp, int64_t, int64_t>& values)
42 {
43     return (std::get<1>(values) + std::get<2>(values)) / 2;
44 }
45 
valueClose(int64_t value,int64_t goal)46 bool valueClose(int64_t value, int64_t goal)
47 {
48 #if 0
49     int64_t delta = 100; /* within 100 */
50     if (value < (goal + delta) &&
51         value > (goal - delta))
52     {
53         return true;
54     }
55 #endif
56 
57     /* let's make sure it's below goal. */
58     if (value < goal)
59     {
60         return true;
61     }
62 
63     return false;
64 }
65 
driveGoal(int64_t & seriesCnt,int64_t setPwm,int64_t goal,std::vector<std::tuple<tstamp,int64_t,int64_t>> & series,std::vector<std::unique_ptr<Sensor>> & fanSensors)66 static void driveGoal(int64_t& seriesCnt, int64_t setPwm, int64_t goal,
67                       std::vector<std::tuple<tstamp, int64_t, int64_t>>& series,
68                       std::vector<std::unique_ptr<Sensor>>& fanSensors)
69 {
70     bool reading = true;
71 
72     auto& fan0 = fanSensors.at(0);
73     auto& fan1 = fanSensors.at(1);
74 
75     fan0->write(setPwm);
76     fan1->write(setPwm);
77 
78     while (reading)
79     {
80         bool check;
81         ReadReturn r0 = fan0->read();
82         ReadReturn r1 = fan1->read();
83         int64_t n0 = static_cast<int64_t>(r0.value);
84         int64_t n1 = static_cast<int64_t>(r1.value);
85 
86         tstamp t1 = std::chrono::high_resolution_clock::now();
87 
88         series.emplace_back(t1, n0, n1);
89         seriesCnt += 1;
90 
91         int64_t avgn = (n0 + n1) / 2;
92         /* check last three values against goal if this is close */
93         check = valueClose(avgn, goal);
94 
95         /* We know the last entry is within range. */
96         if (check && seriesCnt > 3)
97         {
98             /* n-2 values */
99             std::tuple<tstamp, int64_t, int64_t> nm2 = series.at(seriesCnt - 3);
100             /* n-1 values */
101             std::tuple<tstamp, int64_t, int64_t> nm1 = series.at(seriesCnt - 2);
102 
103             int64_t avgnm2 = getAverage(nm2);
104             int64_t avgnm1 = getAverage(nm1);
105 
106             int64_t together = (avgnm2 + avgnm1) / 2;
107 
108             reading = !valueClose(together, goal);
109 
110             if (!reading)
111             {
112                 std::cerr << "finished reaching goal\n";
113             }
114         }
115 
116         /* Early abort for testing. */
117         if (seriesCnt > 150000)
118         {
119             std::cerr << "aborting after 150000 reads.\n";
120             reading = false;
121         }
122     }
123 
124     return;
125 }
126 
driveTime(int64_t & seriesCnt,int64_t setPwm,int64_t goal,std::vector<std::tuple<tstamp,int64_t,int64_t>> & series,std::vector<std::unique_ptr<Sensor>> & fanSensors)127 static void driveTime([[maybe_unused]] int64_t& seriesCnt, int64_t setPwm,
128                       [[maybe_unused]] int64_t goal,
129                       std::vector<std::tuple<tstamp, int64_t, int64_t>>& series,
130                       std::vector<std::unique_ptr<Sensor>>& fanSensors)
131 {
132     using namespace std::literals::chrono_literals;
133 
134     bool reading = true;
135 
136     auto& fan0 = fanSensors.at(0);
137     auto& fan1 = fanSensors.at(1);
138 
139     auto& s0 = series.at(0);
140     tstamp t0 = std::get<0>(s0);
141 
142     fan0->write(setPwm);
143     fan1->write(setPwm);
144 
145     while (reading)
146     {
147         ReadReturn r0 = fan0->read();
148         ReadReturn r1 = fan1->read();
149         int64_t n0 = static_cast<int64_t>(r0.value);
150         int64_t n1 = static_cast<int64_t>(r1.value);
151         tstamp t1 = std::chrono::high_resolution_clock::now();
152 
153         series.emplace_back(t1, n0, n1);
154 
155         auto duration =
156             std::chrono::duration_cast<std::chrono::microseconds>(t1 - t0)
157                 .count();
158         if (duration >= (20000000us).count())
159         {
160             reading = false;
161         }
162     }
163 
164     return;
165 }
166 
driveMain(void)167 int driveMain(void)
168 {
169     /* Time series of the data, the timestamp after both are read and the
170      * values. */
171     std::vector<std::tuple<tstamp, int64_t, int64_t>> series;
172     int64_t seriesCnt = 0; /* in case vector count isn't constant time */
173     int drive = DRIVE;
174 
175     /*
176      * The fan map:
177      *  --> 0 | 4
178      *  --> 1 | 5
179      *  --> 2 | 6
180      *  --> 3 | 7
181      */
182     std::vector<std::string> fans = {"/sys/class/hwmon/hwmon0/fan0_input",
183                                      "/sys/class/hwmon/hwmon0/fan4_input"};
184 
185     std::vector<std::string> pwms = {"/sys/class/hwmon/hwmon0/pwm0",
186                                      "/sys/class/hwmon/hwmon0/pwm4"};
187 
188     std::vector<std::unique_ptr<Sensor>> fanSensors;
189 
190     auto fan0 = Create(fans[0], pwms[0]);
191     auto fan1 = Create(fans[1], pwms[1]);
192 
193     ReadReturn r0 = fan0->read();
194     ReadReturn r1 = fan1->read();
195     int64_t pwm0_value = static_cast<int64_t>(r0.value);
196     int64_t pwm1_value = static_cast<int64_t>(r1.value);
197 
198     if (MAX_PWM != pwm0_value || MAX_PWM != pwm1_value)
199     {
200         std::cerr << "bad PWM starting point.\n";
201         return -EINVAL;
202     }
203 
204     r0 = fan0->read();
205     r1 = fan1->read();
206     int64_t fan0_start = r0.value;
207     int64_t fan1_start = r1.value;
208     tstamp t1 = std::chrono::high_resolution_clock::now();
209 
210     /*
211      * I've done experiments, and seen 9080,10243 as a starting point
212      * which leads to a 50% goal of 4830.5, which is higher than the
213      * average that they reach, 4668.  -- i guess i could try to figure out
214      * a good increase from one to the other, but how fast they're going
215      * actually influences how much they influence, so at slower speeds the
216      * improvement is less.
217      */
218 
219     series.emplace_back(t1, fan0_start, fan1_start);
220     seriesCnt += 1;
221 
222     int64_t average = (fan0_start + fan1_start) / 2;
223     int64_t goal = 0.5 * average;
224 
225     std::cerr << "goal: " << goal << "\n";
226 
227     // fan0 @ 128: 4691
228     // fan4 @ 128: 4707
229 
230     fanSensors.push_back(std::move(fan0));
231     fanSensors.push_back(std::move(fan1));
232 
233     if (DRIVE_TIME == drive)
234     {
235         driveTime(seriesCnt, 128, goal, series, fanSensors);
236     }
237     else if (DRIVE_GOAL == drive)
238     {
239         driveGoal(seriesCnt, 128, goal, series, fanSensors);
240     }
241     tstamp tp = t1;
242 
243     /* Output the values and the timepoints as a time series for review. */
244     for (const auto& t : series)
245     {
246         tstamp ts = std::get<0>(t);
247         int64_t n0 = std::get<1>(t);
248         int64_t n1 = std::get<2>(t);
249 
250         auto duration =
251             std::chrono::duration_cast<std::chrono::microseconds>(ts - tp)
252                 .count();
253         std::cout << duration << "us, " << n0 << ", " << n1 << "\n";
254 
255         tp = ts;
256     }
257 
258     return 0;
259 }
260 
261 } // namespace pid_control
262