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 <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 state 63 { 64 /* Set to true while canceling is in progress */ 65 static bool isCanceling = false; 66 /* The zones build from configuration */ 67 static std::unordered_map<int64_t, std::shared_ptr<ZoneInterface>> zones; 68 /* The timers used by the PID loop */ 69 static std::vector<std::shared_ptr<boost::asio::steady_timer>> timers; 70 /* The sensors build from configuration */ 71 static SensorManager mgmr; 72 } // namespace state 73 74 } // namespace pid_control 75 76 std::filesystem::path configPath = ""; 77 78 /* async io context for operation */ 79 boost::asio::io_context io; 80 /* async signal_set for signal handling */ 81 boost::asio::signal_set signals(io, SIGHUP, SIGTERM); 82 83 /* buses for system control */ 84 static sdbusplus::asio::connection modeControlBus(io); 85 static sdbusplus::asio::connection hostBus(io, sdbusplus::bus::new_bus()); 86 static sdbusplus::asio::connection passiveBus(io, sdbusplus::bus::new_bus()); 87 88 namespace pid_control 89 { 90 91 std::filesystem::path searchConfigurationPath() 92 { 93 static constexpr auto name = "config.json"; 94 95 for (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 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 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 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, 216 std::bind(restartLbd, boost::system::error_code())); 217 } 218 // re-try control loop, set up a delay. 219 else 220 { 221 timer.expires_after(delayTime); 222 timer.async_wait(restartLbd); 223 } 224 225 return; 226 } 227 228 void tryTerminateControlLoops(bool first) 229 { 230 static const auto delayTime = std::chrono::milliseconds(50); 231 static boost::asio::steady_timer timer(io); 232 233 auto stopLbd = [](const boost::system::error_code& error) { 234 if (error == boost::asio::error::operation_aborted) 235 { 236 return; 237 } 238 239 // retry when stopControlLoops() has some failure. 240 try 241 { 242 stopControlLoops(); 243 } 244 catch (const std::exception& e) 245 { 246 std::cerr << "Failed during stopControlLoops, try again: " 247 << e.what() << "\n"; 248 tryTerminateControlLoops(false); 249 return; 250 } 251 io.stop(); 252 }; 253 254 // first time of trying to stop the control loop without a delay 255 if (first) 256 { 257 boost::asio::post(io, std::bind(stopLbd, boost::system::error_code())); 258 } 259 // re-try control loop, set up a delay. 260 else 261 { 262 timer.expires_after(delayTime); 263 timer.async_wait(stopLbd); 264 } 265 266 return; 267 } 268 269 } // namespace pid_control 270 271 void signalHandler(const boost::system::error_code& error, int signal_number) 272 { 273 static boost::asio::steady_timer timer(io); 274 275 if (error) 276 { 277 std::cout << "Signal " << signal_number 278 << " handler error: " << error.message() << "\n"; 279 return; 280 } 281 if (signal_number == SIGTERM) 282 { 283 pid_control::tryTerminateControlLoops(true); 284 } 285 else 286 { 287 timer.expires_after(std::chrono::seconds(1)); 288 timer.async_wait([](const boost::system::error_code ec) { 289 if (ec) 290 { 291 std::cout << "Signal timer error: " << ec.message() << "\n"; 292 return; 293 } 294 295 std::cout << "reloading configuration\n"; 296 pid_control::tryRestartControlLoops(); 297 }); 298 } 299 300 signals.async_wait(signalHandler); 301 } 302 303 int main(int argc, char* argv[]) 304 { 305 loggingPath = ""; 306 loggingEnabled = false; 307 tuningEnabled = false; 308 debugEnabled = false; 309 coreLoggingEnabled = false; 310 311 CLI::App app{"OpenBMC Fan Control Daemon"}; 312 313 app.add_option("-c,--conf", configPath, 314 "Optional parameter to specify configuration at run-time") 315 ->check(CLI::ExistingFile); 316 app.add_option("-l,--log", loggingPath, 317 "Optional parameter to specify logging folder") 318 ->check(CLI::ExistingDirectory); 319 app.add_flag("-t,--tuning", tuningEnabled, "Enable or disable tuning"); 320 app.add_flag("-d,--debug", debugEnabled, "Enable or disable debug mode"); 321 app.add_flag("-g,--corelogging", coreLoggingEnabled, 322 "Enable or disable logging of core PID loop computations"); 323 324 CLI11_PARSE(app, argc, argv); 325 326 static constexpr auto loggingEnablePath = "/etc/thermal.d/logging"; 327 static constexpr auto tuningEnablePath = "/etc/thermal.d/tuning"; 328 static constexpr auto debugEnablePath = "/etc/thermal.d/debugging"; 329 static constexpr auto coreLoggingEnablePath = "/etc/thermal.d/corelogging"; 330 331 // Set up default logging path, preferring command line if it was given 332 std::string defLoggingPath(loggingPath); 333 if (defLoggingPath.empty()) 334 { 335 defLoggingPath = std::filesystem::temp_directory_path(); 336 } 337 else 338 { 339 // Enable logging, if user explicitly gave path on command line 340 loggingEnabled = true; 341 } 342 343 // If this file exists, enable logging at runtime 344 std::ifstream fsLogging(loggingEnablePath); 345 if (fsLogging) 346 { 347 // Allow logging path to be changed by file content 348 std::string altPath; 349 std::getline(fsLogging, altPath); 350 fsLogging.close(); 351 352 if (std::filesystem::exists(altPath)) 353 { 354 loggingPath = altPath; 355 } 356 else 357 { 358 loggingPath = defLoggingPath; 359 } 360 361 loggingEnabled = true; 362 } 363 if (loggingEnabled) 364 { 365 std::cerr << "Logging enabled: " << loggingPath << "\n"; 366 } 367 368 // If this file exists, enable tuning at runtime 369 if (std::filesystem::exists(tuningEnablePath)) 370 { 371 tuningEnabled = true; 372 } 373 if (tuningEnabled) 374 { 375 std::cerr << "Tuning enabled\n"; 376 } 377 378 // If this file exists, enable debug mode at runtime 379 if (std::filesystem::exists(debugEnablePath)) 380 { 381 debugEnabled = true; 382 } 383 384 if (debugEnabled) 385 { 386 std::cerr << "Debug mode enabled\n"; 387 } 388 389 // If this file exists, enable core logging at runtime 390 if (std::filesystem::exists(coreLoggingEnablePath)) 391 { 392 coreLoggingEnabled = true; 393 } 394 if (coreLoggingEnabled) 395 { 396 std::cerr << "Core logging enabled\n"; 397 } 398 399 static constexpr auto modeRoot = "/xyz/openbmc_project/settings/fanctrl"; 400 // Create a manager for the ModeBus because we own it. 401 sdbusplus::server::manager_t(static_cast<sdbusplus::bus_t&>(modeControlBus), 402 modeRoot); 403 hostBus.request_name("xyz.openbmc_project.Hwmon.external"); 404 modeControlBus.request_name("xyz.openbmc_project.State.FanCtrl"); 405 sdbusplus::server::manager_t objManager(modeControlBus, modeRoot); 406 407 // Enable SIGHUP handling to reload JSON config 408 signals.async_wait(signalHandler); 409 410 /* 411 * All sensors are managed by one manager, but each zone has a pointer to 412 * it. 413 */ 414 415 pid_control::tryRestartControlLoops(); 416 417 io.run(); 418 return 0; 419 } 420