1 /**
2 * Copyright 2017 Google Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "config.h"
18
19 #include "buildjson/buildjson.hpp"
20 #include "conf.hpp"
21 #include "dbus/dbusconfiguration.hpp"
22 #include "failsafeloggers/builder.hpp"
23 #include "interfaces.hpp"
24 #include "pid/builder.hpp"
25 #include "pid/buildjson.hpp"
26 #include "pid/pidloop.hpp"
27 #include "pid/tuning.hpp"
28 #include "pid/zone.hpp"
29 #include "sensors/builder.hpp"
30 #include "sensors/buildjson.hpp"
31 #include "sensors/manager.hpp"
32 #include "util.hpp"
33
34 #include <CLI/CLI.hpp>
35 #include <boost/asio/io_context.hpp>
36 #include <boost/asio/signal_set.hpp>
37 #include <boost/asio/steady_timer.hpp>
38 #include <sdbusplus/asio/connection.hpp>
39 #include <sdbusplus/bus.hpp>
40 #include <sdbusplus/server/manager.hpp>
41
42 #include <chrono>
43 #include <filesystem>
44 #include <iostream>
45 #include <list>
46 #include <map>
47 #include <memory>
48 #include <thread>
49 #include <unordered_map>
50 #include <utility>
51 #include <vector>
52
53 namespace pid_control
54 {
55
56 /* The configuration converted sensor list. */
57 std::map<std::string, conf::SensorConfig> sensorConfig = {};
58 /* The configuration converted PID list. */
59 std::map<int64_t, conf::PIDConf> zoneConfig = {};
60 /* The configuration converted Zone configuration. */
61 std::map<int64_t, conf::ZoneConfig> zoneDetailsConfig = {};
62
63 namespace state
64 {
65 /* Set to true while canceling is in progress */
66 static bool isCanceling = false;
67 /* The zones build from configuration */
68 static std::unordered_map<int64_t, std::shared_ptr<ZoneInterface>> zones;
69 /* The timers used by the PID loop */
70 static std::vector<std::shared_ptr<boost::asio::steady_timer>> timers;
71 /* The sensors build from configuration */
72 static SensorManager mgmr;
73 } // namespace state
74
75 } // namespace pid_control
76
77 std::filesystem::path configPath = "";
78
79 /* async io context for operation */
80 boost::asio::io_context io;
81 /* async signal_set for signal handling */
82 boost::asio::signal_set signals(io, SIGHUP, SIGTERM);
83
84 /* buses for system control */
85 static sdbusplus::asio::connection modeControlBus(io);
86 static sdbusplus::asio::connection
87 hostBus(io, sdbusplus::bus::new_system().release());
88 static sdbusplus::asio::connection
89 passiveBus(io, sdbusplus::bus::new_system().release());
90
91 namespace pid_control
92 {
93
searchConfigurationPath()94 std::filesystem::path searchConfigurationPath()
95 {
96 static constexpr auto name = "config.json";
97
98 for (auto pathSeg : {std::filesystem::current_path(),
99 std::filesystem::path{"/var/lib/swampd"},
100 std::filesystem::path{"/usr/share/swampd"}})
101 {
102 auto file = pathSeg / name;
103 if (std::filesystem::exists(file))
104 {
105 return file;
106 }
107 }
108
109 return name;
110 }
111
stopControlLoops()112 void stopControlLoops()
113 {
114 for (const auto& timer : state::timers)
115 {
116 timer->cancel();
117 }
118 state::isCanceling = true;
119 state::timers.clear();
120
121 if (state::zones.size() > 0 && state::zones.begin()->second.use_count() > 1)
122 {
123 throw std::runtime_error("wait for count back to 1");
124 }
125
126 state::zones.clear();
127 state::isCanceling = false;
128 }
129
restartControlLoops()130 void restartControlLoops()
131 {
132 stopControlLoops();
133
134 const std::filesystem::path path =
135 (!configPath.empty()) ? configPath : searchConfigurationPath();
136
137 if (std::filesystem::exists(path))
138 {
139 /*
140 * When building the sensors, if any of the dbus passive ones aren't on
141 * the bus, it'll fail immediately.
142 */
143 try
144 {
145 auto jsonData = parseValidateJson(path);
146 sensorConfig = buildSensorsFromJson(jsonData);
147 std::tie(zoneConfig, zoneDetailsConfig) =
148 buildPIDsFromJson(jsonData);
149 }
150 catch (const std::exception& e)
151 {
152 std::cerr << "Failed during building: " << e.what() << "\n";
153 exit(EXIT_FAILURE); /* fatal error. */
154 }
155 }
156 else
157 {
158 static boost::asio::steady_timer reloadTimer(io);
159 if (!dbus_configuration::init(modeControlBus, reloadTimer, sensorConfig,
160 zoneConfig, zoneDetailsConfig))
161 {
162 return; // configuration not ready
163 }
164 }
165
166 state::mgmr = buildSensors(sensorConfig, passiveBus, hostBus);
167 state::zones =
168 buildZones(zoneConfig, zoneDetailsConfig, state::mgmr, modeControlBus);
169 // Set `logMaxCountPerSecond` to 20 will limit the number of logs output per
170 // second in each zone. Using 20 here would limit the output rate to be no
171 // larger than 100 per sec for most platforms as the number of zones are
172 // usually <=3. This will effectively avoid resource exhaustion.
173 buildFailsafeLoggers(state::zones, /* logMaxCountPerSecond = */ 20);
174
175 if (0 == state::zones.size())
176 {
177 std::cerr << "No zones defined, exiting.\n";
178 std::exit(EXIT_FAILURE);
179 }
180
181 for (const auto& i : state::zones)
182 {
183 std::shared_ptr<boost::asio::steady_timer> timer =
184 state::timers.emplace_back(
185 std::make_shared<boost::asio::steady_timer>(io));
186 std::cerr << "pushing zone " << i.first << "\n";
187 pidControlLoop(i.second, timer, &state::isCanceling);
188 }
189 }
190
tryRestartControlLoops(bool first)191 void tryRestartControlLoops(bool first)
192 {
193 static const auto delayTime = std::chrono::seconds(10);
194 static boost::asio::steady_timer timer(io);
195
196 auto restartLbd = [](const boost::system::error_code& error) {
197 if (error == boost::asio::error::operation_aborted)
198 {
199 return;
200 }
201
202 // retry when restartControlLoops() has some failure.
203 try
204 {
205 restartControlLoops();
206 }
207 catch (const std::exception& e)
208 {
209 std::cerr << "Failed during restartControlLoops, try again: "
210 << e.what() << "\n";
211 tryRestartControlLoops(false);
212 }
213 };
214
215 // first time of trying to restart the control loop without a delay
216 if (first)
217 {
218 boost::asio::post(io,
219 std::bind(restartLbd, boost::system::error_code()));
220 }
221 // re-try control loop, set up a delay.
222 else
223 {
224 timer.expires_after(delayTime);
225 timer.async_wait(restartLbd);
226 }
227
228 return;
229 }
230
tryTerminateControlLoops(bool first)231 void tryTerminateControlLoops(bool first)
232 {
233 static const auto delayTime = std::chrono::milliseconds(50);
234 static boost::asio::steady_timer timer(io);
235
236 auto stopLbd = [](const boost::system::error_code& error) {
237 if (error == boost::asio::error::operation_aborted)
238 {
239 return;
240 }
241
242 // retry when stopControlLoops() has some failure.
243 try
244 {
245 stopControlLoops();
246 }
247 catch (const std::exception& e)
248 {
249 std::cerr << "Failed during stopControlLoops, try again: "
250 << e.what() << "\n";
251 tryTerminateControlLoops(false);
252 return;
253 }
254 io.stop();
255 };
256
257 // first time of trying to stop the control loop without a delay
258 if (first)
259 {
260 boost::asio::post(io, std::bind(stopLbd, boost::system::error_code()));
261 }
262 // re-try control loop, set up a delay.
263 else
264 {
265 timer.expires_after(delayTime);
266 timer.async_wait(stopLbd);
267 }
268
269 return;
270 }
271
272 } // namespace pid_control
273
signalHandler(const boost::system::error_code & error,int signal_number)274 void signalHandler(const boost::system::error_code& error, int signal_number)
275 {
276 static boost::asio::steady_timer timer(io);
277
278 if (error)
279 {
280 std::cout << "Signal " << signal_number
281 << " handler error: " << error.message() << "\n";
282 return;
283 }
284 if (signal_number == SIGTERM)
285 {
286 pid_control::tryTerminateControlLoops(true);
287 }
288 else
289 {
290 timer.expires_after(std::chrono::seconds(1));
291 timer.async_wait([](const boost::system::error_code ec) {
292 if (ec)
293 {
294 std::cout << "Signal timer error: " << ec.message() << "\n";
295 return;
296 }
297
298 std::cout << "reloading configuration\n";
299 pid_control::tryRestartControlLoops();
300 });
301 }
302
303 signals.async_wait(signalHandler);
304 }
305
main(int argc,char * argv[])306 int main(int argc, char* argv[])
307 {
308 loggingPath = "";
309 loggingEnabled = false;
310 tuningEnabled = false;
311 debugEnabled = false;
312 coreLoggingEnabled = false;
313
314 CLI::App app{"OpenBMC Fan Control Daemon"};
315
316 app.add_option("-c,--conf", configPath,
317 "Optional parameter to specify configuration at run-time")
318 ->check(CLI::ExistingFile);
319 app.add_option("-l,--log", loggingPath,
320 "Optional parameter to specify logging folder")
321 ->check(CLI::ExistingDirectory);
322 app.add_flag("-t,--tuning", tuningEnabled, "Enable or disable tuning");
323 app.add_flag("-d,--debug", debugEnabled, "Enable or disable debug mode");
324 app.add_flag("-g,--corelogging", coreLoggingEnabled,
325 "Enable or disable logging of core PID loop computations");
326
327 CLI11_PARSE(app, argc, argv);
328
329 static constexpr auto loggingEnablePath = "/etc/thermal.d/logging";
330 static constexpr auto tuningEnablePath = "/etc/thermal.d/tuning";
331 static constexpr auto debugEnablePath = "/etc/thermal.d/debugging";
332 static constexpr auto coreLoggingEnablePath = "/etc/thermal.d/corelogging";
333
334 // Set up default logging path, preferring command line if it was given
335 std::string defLoggingPath(loggingPath);
336 if (defLoggingPath.empty())
337 {
338 defLoggingPath = std::filesystem::temp_directory_path();
339 }
340 else
341 {
342 // Enable logging, if user explicitly gave path on command line
343 loggingEnabled = true;
344 }
345
346 // If this file exists, enable logging at runtime
347 std::ifstream fsLogging(loggingEnablePath);
348 if (fsLogging)
349 {
350 // Allow logging path to be changed by file content
351 std::string altPath;
352 std::getline(fsLogging, altPath);
353 fsLogging.close();
354
355 if (std::filesystem::exists(altPath))
356 {
357 loggingPath = altPath;
358 }
359 else
360 {
361 loggingPath = defLoggingPath;
362 }
363
364 loggingEnabled = true;
365 }
366 if (loggingEnabled)
367 {
368 std::cerr << "Logging enabled: " << loggingPath << "\n";
369 }
370
371 // If this file exists, enable tuning at runtime
372 if (std::filesystem::exists(tuningEnablePath))
373 {
374 tuningEnabled = true;
375 }
376 if (tuningEnabled)
377 {
378 std::cerr << "Tuning enabled\n";
379 }
380
381 // If this file exists, enable debug mode at runtime
382 if (std::filesystem::exists(debugEnablePath))
383 {
384 debugEnabled = true;
385 }
386
387 if (debugEnabled)
388 {
389 std::cerr << "Debug mode enabled\n";
390 }
391
392 // If this file exists, enable core logging at runtime
393 if (std::filesystem::exists(coreLoggingEnablePath))
394 {
395 coreLoggingEnabled = true;
396 }
397 if (coreLoggingEnabled)
398 {
399 std::cerr << "Core logging enabled\n";
400 }
401
402 static constexpr auto modeRoot = "/xyz/openbmc_project/settings/fanctrl";
403 // Create a manager for the ModeBus because we own it.
404 sdbusplus::server::manager_t(static_cast<sdbusplus::bus_t&>(modeControlBus),
405 modeRoot);
406 hostBus.request_name("xyz.openbmc_project.Hwmon.external");
407 modeControlBus.request_name("xyz.openbmc_project.State.FanCtrl");
408 sdbusplus::server::manager_t objManager(modeControlBus, modeRoot);
409
410 // Enable SIGHUP handling to reload JSON config
411 signals.async_wait(signalHandler);
412
413 /*
414 * All sensors are managed by one manager, but each zone has a pointer to
415 * it.
416 */
417
418 pid_control::tryRestartControlLoops();
419
420 io.run();
421 return 0;
422 }
423