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 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 86 hostBus(io, sdbusplus::bus::new_system().release()); 87 static sdbusplus::asio::connection 88 passiveBus(io, sdbusplus::bus::new_system().release()); 89 90 namespace pid_control 91 { 92 93 std::filesystem::path searchConfigurationPath() 94 { 95 static constexpr auto name = "config.json"; 96 97 for (auto pathSeg : {std::filesystem::current_path(), 98 std::filesystem::path{"/var/lib/swampd"}, 99 std::filesystem::path{"/usr/share/swampd"}}) 100 { 101 auto file = pathSeg / name; 102 if (std::filesystem::exists(file)) 103 { 104 return file; 105 } 106 } 107 108 return name; 109 } 110 111 void stopControlLoops() 112 { 113 for (const auto& timer : state::timers) 114 { 115 timer->cancel(); 116 } 117 state::isCanceling = true; 118 state::timers.clear(); 119 120 if (state::zones.size() > 0 && state::zones.begin()->second.use_count() > 1) 121 { 122 throw std::runtime_error("wait for count back to 1"); 123 } 124 125 state::zones.clear(); 126 state::isCanceling = false; 127 } 128 129 void restartControlLoops() 130 { 131 stopControlLoops(); 132 133 const std::filesystem::path path = 134 (!configPath.empty()) ? configPath : searchConfigurationPath(); 135 136 if (std::filesystem::exists(path)) 137 { 138 /* 139 * When building the sensors, if any of the dbus passive ones aren't on 140 * the bus, it'll fail immediately. 141 */ 142 try 143 { 144 auto jsonData = parseValidateJson(path); 145 sensorConfig = buildSensorsFromJson(jsonData); 146 std::tie(zoneConfig, 147 zoneDetailsConfig) = buildPIDsFromJson(jsonData); 148 } 149 catch (const std::exception& e) 150 { 151 std::cerr << "Failed during building: " << e.what() << "\n"; 152 exit(EXIT_FAILURE); /* fatal error. */ 153 } 154 } 155 else 156 { 157 static boost::asio::steady_timer reloadTimer(io); 158 if (!dbus_configuration::init(modeControlBus, reloadTimer, sensorConfig, 159 zoneConfig, zoneDetailsConfig)) 160 { 161 return; // configuration not ready 162 } 163 } 164 165 state::mgmr = buildSensors(sensorConfig, passiveBus, hostBus); 166 state::zones = buildZones(zoneConfig, zoneDetailsConfig, state::mgmr, 167 modeControlBus); 168 169 if (0 == state::zones.size()) 170 { 171 std::cerr << "No zones defined, exiting.\n"; 172 std::exit(EXIT_FAILURE); 173 } 174 175 for (const auto& i : state::zones) 176 { 177 std::shared_ptr<boost::asio::steady_timer> timer = 178 state::timers.emplace_back( 179 std::make_shared<boost::asio::steady_timer>(io)); 180 std::cerr << "pushing zone " << i.first << "\n"; 181 pidControlLoop(i.second, timer, &state::isCanceling); 182 } 183 } 184 185 void tryRestartControlLoops(bool first) 186 { 187 static const auto delayTime = std::chrono::seconds(10); 188 static boost::asio::steady_timer timer(io); 189 190 auto restartLbd = [](const boost::system::error_code& error) { 191 if (error == boost::asio::error::operation_aborted) 192 { 193 return; 194 } 195 196 // retry when restartControlLoops() has some failure. 197 try 198 { 199 restartControlLoops(); 200 } 201 catch (const std::exception& e) 202 { 203 std::cerr << "Failed during restartControlLoops, try again: " 204 << e.what() << "\n"; 205 tryRestartControlLoops(false); 206 } 207 }; 208 209 // first time of trying to restart the control loop without a delay 210 if (first) 211 { 212 boost::asio::post(io, 213 std::bind(restartLbd, boost::system::error_code())); 214 } 215 // re-try control loop, set up a delay. 216 else 217 { 218 timer.expires_after(delayTime); 219 timer.async_wait(restartLbd); 220 } 221 222 return; 223 } 224 225 void tryTerminateControlLoops(bool first) 226 { 227 static const auto delayTime = std::chrono::milliseconds(50); 228 static boost::asio::steady_timer timer(io); 229 230 auto stopLbd = [](const boost::system::error_code& error) { 231 if (error == boost::asio::error::operation_aborted) 232 { 233 return; 234 } 235 236 // retry when stopControlLoops() has some failure. 237 try 238 { 239 stopControlLoops(); 240 } 241 catch (const std::exception& e) 242 { 243 std::cerr << "Failed during stopControlLoops, try again: " 244 << e.what() << "\n"; 245 tryTerminateControlLoops(false); 246 return; 247 } 248 io.stop(); 249 }; 250 251 // first time of trying to stop the control loop without a delay 252 if (first) 253 { 254 boost::asio::post(io, std::bind(stopLbd, boost::system::error_code())); 255 } 256 // re-try control loop, set up a delay. 257 else 258 { 259 timer.expires_after(delayTime); 260 timer.async_wait(stopLbd); 261 } 262 263 return; 264 } 265 266 } // namespace pid_control 267 268 void signalHandler(const boost::system::error_code& error, int signal_number) 269 { 270 static boost::asio::steady_timer timer(io); 271 272 if (error) 273 { 274 std::cout << "Signal " << signal_number 275 << " handler error: " << error.message() << "\n"; 276 return; 277 } 278 if (signal_number == SIGTERM) 279 { 280 pid_control::tryTerminateControlLoops(true); 281 } 282 else 283 { 284 timer.expires_after(std::chrono::seconds(1)); 285 timer.async_wait([](const boost::system::error_code ec) { 286 if (ec) 287 { 288 std::cout << "Signal timer error: " << ec.message() << "\n"; 289 return; 290 } 291 292 std::cout << "reloading configuration\n"; 293 pid_control::tryRestartControlLoops(); 294 }); 295 } 296 297 signals.async_wait(signalHandler); 298 } 299 300 int main(int argc, char* argv[]) 301 { 302 loggingPath = ""; 303 loggingEnabled = false; 304 tuningEnabled = false; 305 debugEnabled = false; 306 coreLoggingEnabled = false; 307 308 CLI::App app{"OpenBMC Fan Control Daemon"}; 309 310 app.add_option("-c,--conf", configPath, 311 "Optional parameter to specify configuration at run-time") 312 ->check(CLI::ExistingFile); 313 app.add_option("-l,--log", loggingPath, 314 "Optional parameter to specify logging folder") 315 ->check(CLI::ExistingDirectory); 316 app.add_flag("-t,--tuning", tuningEnabled, "Enable or disable tuning"); 317 app.add_flag("-d,--debug", debugEnabled, "Enable or disable debug mode"); 318 app.add_flag("-g,--corelogging", coreLoggingEnabled, 319 "Enable or disable logging of core PID loop computations"); 320 321 CLI11_PARSE(app, argc, argv); 322 323 static constexpr auto loggingEnablePath = "/etc/thermal.d/logging"; 324 static constexpr auto tuningEnablePath = "/etc/thermal.d/tuning"; 325 static constexpr auto debugEnablePath = "/etc/thermal.d/debugging"; 326 static constexpr auto coreLoggingEnablePath = "/etc/thermal.d/corelogging"; 327 328 // Set up default logging path, preferring command line if it was given 329 std::string defLoggingPath(loggingPath); 330 if (defLoggingPath.empty()) 331 { 332 defLoggingPath = std::filesystem::temp_directory_path(); 333 } 334 else 335 { 336 // Enable logging, if user explicitly gave path on command line 337 loggingEnabled = true; 338 } 339 340 // If this file exists, enable logging at runtime 341 std::ifstream fsLogging(loggingEnablePath); 342 if (fsLogging) 343 { 344 // Allow logging path to be changed by file content 345 std::string altPath; 346 std::getline(fsLogging, altPath); 347 fsLogging.close(); 348 349 if (std::filesystem::exists(altPath)) 350 { 351 loggingPath = altPath; 352 } 353 else 354 { 355 loggingPath = defLoggingPath; 356 } 357 358 loggingEnabled = true; 359 } 360 if (loggingEnabled) 361 { 362 std::cerr << "Logging enabled: " << loggingPath << "\n"; 363 } 364 365 // If this file exists, enable tuning at runtime 366 if (std::filesystem::exists(tuningEnablePath)) 367 { 368 tuningEnabled = true; 369 } 370 if (tuningEnabled) 371 { 372 std::cerr << "Tuning enabled\n"; 373 } 374 375 // If this file exists, enable debug mode at runtime 376 if (std::filesystem::exists(debugEnablePath)) 377 { 378 debugEnabled = true; 379 } 380 381 if (debugEnabled) 382 { 383 std::cerr << "Debug mode enabled\n"; 384 } 385 386 // If this file exists, enable core logging at runtime 387 if (std::filesystem::exists(coreLoggingEnablePath)) 388 { 389 coreLoggingEnabled = true; 390 } 391 if (coreLoggingEnabled) 392 { 393 std::cerr << "Core logging enabled\n"; 394 } 395 396 static constexpr auto modeRoot = "/xyz/openbmc_project/settings/fanctrl"; 397 // Create a manager for the ModeBus because we own it. 398 sdbusplus::server::manager_t(static_cast<sdbusplus::bus_t&>(modeControlBus), 399 modeRoot); 400 hostBus.request_name("xyz.openbmc_project.Hwmon.external"); 401 modeControlBus.request_name("xyz.openbmc_project.State.FanCtrl"); 402 sdbusplus::server::manager_t objManager(modeControlBus, modeRoot); 403 404 // Enable SIGHUP handling to reload JSON config 405 signals.async_wait(signalHandler); 406 407 /* 408 * All sensors are managed by one manager, but each zone has a pointer to 409 * it. 410 */ 411 412 pid_control::tryRestartControlLoops(); 413 414 io.run(); 415 return 0; 416 } 417