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