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