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