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