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, zoneDetailsConfig) = 134 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 291 loggingEnabled = true; 292 } 293 if (loggingEnabled) 294 { 295 std::cerr << "Logging enabled: " << loggingPath << "\n"; 296 } 297 298 // If this file exists, enable tuning at runtime 299 if (std::filesystem::exists(tuningEnablePath)) 300 { 301 tuningEnabled = true; 302 } 303 if (tuningEnabled) 304 { 305 std::cerr << "Tuning enabled\n"; 306 } 307 308 // If this file exists, enable debug mode at runtime 309 if (std::filesystem::exists(debugEnablePath)) 310 { 311 debugEnabled = true; 312 } 313 314 if (debugEnabled) 315 { 316 std::cerr << "Debug mode enabled\n"; 317 } 318 319 // If this file exists, enable core logging at runtime 320 if (std::filesystem::exists(coreLoggingEnablePath)) 321 { 322 coreLoggingEnabled = true; 323 } 324 if (coreLoggingEnabled) 325 { 326 std::cerr << "Core logging enabled\n"; 327 } 328 329 static constexpr auto modeRoot = "/xyz/openbmc_project/settings/fanctrl"; 330 // Create a manager for the ModeBus because we own it. 331 sdbusplus::server::manager_t(static_cast<sdbusplus::bus_t&>(modeControlBus), 332 modeRoot); 333 hostBus.request_name("xyz.openbmc_project.Hwmon.external"); 334 modeControlBus.request_name("xyz.openbmc_project.State.FanCtrl"); 335 sdbusplus::server::manager_t objManager(modeControlBus, modeRoot); 336 337 // Enable SIGHUP handling to reload JSON config 338 signals.async_wait(sighupHandler); 339 340 /* 341 * All sensors are managed by one manager, but each zone has a pointer to 342 * it. 343 */ 344 345 pid_control::tryRestartControlLoops(); 346 347 io.run(); 348 return 0; 349 } 350