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