1 /** 2 * Copyright © 2021 IBM Corporation 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 "sdbusplus.hpp" 18 19 #include <CLI/CLI.hpp> 20 #include <sdbusplus/bus.hpp> 21 22 #include <iomanip> 23 #include <iostream> 24 25 using SDBusPlus = phosphor::fan::util::SDBusPlus; 26 27 /** 28 * @function extracts fan name from dbus path string (last token where 29 * delimiter is the / character), with proper bounds checking. 30 * @param[in] path - D-Bus path 31 * @return just the fan name. 32 */ 33 34 std::string justFanName(std::string const& path) 35 { 36 std::string fanName; 37 38 auto itr = path.rfind("/"); 39 if (itr != std::string::npos && itr < path.size()) 40 { 41 fanName = path.substr(1 + itr); 42 } 43 44 return fanName; 45 } 46 47 /** 48 * @function produces subtree paths whose names match fan token names. 49 * @param[in] path - D-Bus path to obtain subtree from 50 * @param[in] iface - interface to obtain subTreePaths from 51 * @param[in] fans - label matching tokens to filter by 52 * @param[in] shortPath - flag to shorten fan token 53 * @return map of paths by fan name 54 */ 55 56 std::map<std::string, std::vector<std::string>> 57 getPathsFromIface(const std::string& path, const std::string& iface, 58 const std::vector<std::string>& fans, 59 bool shortPath = false) 60 { 61 std::map<std::string, std::vector<std::string>> dest; 62 63 for (auto& path : 64 SDBusPlus::getSubTreePathsRaw(SDBusPlus::getBus(), path, iface, 0)) 65 { 66 for (auto& fan : fans) 67 { 68 if (shortPath) 69 { 70 if (fan == justFanName(path)) 71 { 72 dest[fan].push_back(path); 73 } 74 } 75 else if (std::string::npos != path.find(fan + "_")) 76 { 77 dest[fan].push_back(path); 78 } 79 } 80 } 81 82 return dest; 83 } 84 85 /** 86 * @function consolidated function to load dbus paths and fan names 87 */ 88 auto loadDBusData() 89 { 90 auto& bus{SDBusPlus::getBus()}; 91 92 std::vector<std::string> fanNames; 93 94 // paths by D-bus interface,fan name 95 std::map<std::string, std::map<std::string, std::vector<std::string>>> 96 pathMap; 97 98 std::string method("RPM"); 99 100 std::map<const std::string, const std::string> interfaces{ 101 {"FanSpeed", "xyz.openbmc_project.Control.FanSpeed"}, 102 {"FanPwm", "xyz.openbmc_project.Control.FanPwm"}, 103 {"SensorValue", "xyz.openbmc_project.Sensor.Value"}, 104 {"Item", "xyz.openbmc_project.Inventory.Item"}, 105 {"OpStatus", "xyz.openbmc_project.State.Decorator.OperationalStatus"}}; 106 107 std::map<const std::string, const std::string> paths{ 108 {"motherboard", 109 "/xyz/openbmc_project/inventory/system/chassis/motherboard"}, 110 {"tach", "/xyz/openbmc_project/sensors/fan_tach"}}; 111 112 // build a list of all fans 113 for (auto& path : SDBusPlus::getSubTreePathsRaw(bus, paths["tach"], 114 interfaces["FanSpeed"], 0)) 115 { 116 // special case where we build the list of fans 117 auto fan = justFanName(path); 118 fan = fan.substr(0, fan.rfind("_")); 119 fanNames.push_back(fan); 120 } 121 122 // retry with PWM mode if none found 123 if (0 == fanNames.size()) 124 { 125 method = "PWM"; 126 127 for (auto& path : SDBusPlus::getSubTreePathsRaw( 128 bus, paths["tach"], interfaces["FanPwm"], 0)) 129 { 130 // special case where we build the list of fans 131 auto fan = justFanName(path); 132 fan = fan.substr(0, fan.rfind("_")); 133 fanNames.push_back(fan); 134 } 135 } 136 137 // load tach sensor paths for each fan 138 pathMap["tach"] = 139 getPathsFromIface(paths["tach"], interfaces["SensorValue"], fanNames); 140 141 // load inventory Item data for each fan 142 pathMap["inventory"] = getPathsFromIface( 143 paths["motherboard"], interfaces["Item"], fanNames, true); 144 145 // load operational status data for each fan 146 pathMap["opstatus"] = getPathsFromIface( 147 paths["motherboard"], interfaces["OpStatus"], fanNames, true); 148 149 return std::make_tuple(fanNames, pathMap, interfaces, method); 150 } 151 152 /** 153 * @function gets the states of phosphor-fanctl. equivalent to 154 * "systemctl status phosphor-fan-control@0" 155 * @return a list of several (sub)states of fanctl (loaded, 156 * active, running) as well as D-Bus properties representing 157 * BMC states (bmc state,chassis power state, host state) 158 */ 159 160 std::array<std::string, 6> getStates() 161 { 162 using DBusTuple = 163 std::tuple<std::string, std::string, std::string, std::string, 164 std::string, std::string, sdbusplus::message::object_path, 165 uint32_t, std::string, sdbusplus::message::object_path>; 166 167 constexpr auto systemdMgrIface = "org.freedesktop.systemd1.Manager"; 168 constexpr auto systemdPath = "/org/freedesktop/systemd1"; 169 constexpr auto systemdService = "org.freedesktop.systemd1"; 170 171 std::array<std::string, 6> ret; 172 173 std::vector<std::string> services{"phosphor-fan-control@0.service"}; 174 175 try 176 { 177 auto fields{SDBusPlus::callMethodAndRead<std::vector<DBusTuple>>( 178 systemdService, systemdPath, systemdMgrIface, "ListUnitsByNames", 179 services)}; 180 181 if (fields.size() > 0) 182 { 183 ret[0] = std::get<2>(fields[0]); 184 ret[1] = std::get<3>(fields[0]); 185 ret[2] = std::get<4>(fields[0]); 186 } 187 else 188 { 189 std::cout << "No units found for systemd service: " << services[0] 190 << std::endl; 191 } 192 } 193 catch (const std::exception& e) 194 { 195 std::cerr << "Failure retrieving phosphor-fan-control states: " 196 << e.what() << std::endl; 197 } 198 199 std::string path("/xyz/openbmc_project/state/bmc0"); 200 std::string iface("xyz.openbmc_project.State.BMC"); 201 ret[3] = 202 SDBusPlus::getProperty<std::string>(path, iface, "CurrentBMCState"); 203 204 path = "/xyz/openbmc_project/state/chassis0"; 205 iface = "xyz.openbmc_project.State.Chassis"; 206 ret[4] = 207 SDBusPlus::getProperty<std::string>(path, iface, "CurrentPowerState"); 208 209 path = "/xyz/openbmc_project/state/host0"; 210 iface = "xyz.openbmc_project.State.Host"; 211 ret[5] = 212 SDBusPlus::getProperty<std::string>(path, iface, "CurrentHostState"); 213 214 return ret; 215 } 216 217 /** 218 * @function helper to determine interface type from a given control method 219 */ 220 std::string ifaceTypeFromMethod(const std::string& method) 221 { 222 return (method == "RPM" ? "FanSpeed" : "FanPwm"); 223 } 224 225 /** 226 * @function performs the "status" command from the cmdline. 227 * get states and sensor data and output to the console 228 */ 229 void status() 230 { 231 using std::cout; 232 using std::endl; 233 using std::setw; 234 235 auto busData = loadDBusData(); 236 auto& method = std::get<3>(busData); 237 238 std::string property; 239 240 // get the state,substate of fan-control and obmc 241 auto states = getStates(); 242 243 // print the header 244 cout << "Fan Control Service State : " << states[0] << ", " << states[1] 245 << "(" << states[2] << ")" << endl; 246 cout << endl; 247 cout << "CurrentBMCState : " << states[3] << endl; 248 cout << "CurrentPowerState : " << states[4] << endl; 249 cout << "CurrentHostState : " << states[5] << endl; 250 cout << endl; 251 cout << " FAN " 252 << "TARGET(" << method << ") FEEDBACKS(RPMS) PRESENT" 253 << " FUNCTIONAL" << endl; 254 cout << "===============================================================" 255 << endl; 256 257 auto& fanNames{std::get<0>(busData)}; 258 auto& pathMap{std::get<1>(busData)}; 259 auto& interfaces{std::get<2>(busData)}; 260 261 for (auto& fan : fanNames) 262 { 263 cout << " " << fan << setw(18); 264 265 // get the target RPM 266 property = "Target"; 267 cout << SDBusPlus::getProperty<uint64_t>( 268 pathMap["tach"][fan][0], 269 interfaces[ifaceTypeFromMethod(method)], property) 270 << setw(11); 271 272 // get the sensor RPM 273 property = "Value"; 274 275 int numRotors = pathMap["tach"][fan].size(); 276 // print tach readings for each rotor 277 for (auto& path : pathMap["tach"][fan]) 278 { 279 cout << SDBusPlus::getProperty<double>( 280 path, interfaces["SensorValue"], property); 281 282 // dont print slash on last rotor 283 if (--numRotors) 284 cout << "/"; 285 } 286 cout << setw(10); 287 288 // print the Present property 289 property = "Present"; 290 std::string val; 291 for (auto& path : pathMap["inventory"][fan]) 292 { 293 try 294 { 295 if (SDBusPlus::getProperty<bool>(path, interfaces["Item"], 296 property)) 297 { 298 val = "true"; 299 } 300 else 301 { 302 val = "false"; 303 } 304 } 305 catch (phosphor::fan::util::DBusPropertyError&) 306 { 307 val = "Unknown"; 308 } 309 cout << val; 310 } 311 312 cout << setw(13); 313 314 // and the functional property 315 property = "Functional"; 316 for (auto& path : pathMap["opstatus"][fan]) 317 { 318 try 319 { 320 if (SDBusPlus::getProperty<bool>(path, interfaces["OpStatus"], 321 property)) 322 { 323 val = "true"; 324 } 325 else 326 { 327 val = "false"; 328 } 329 } 330 catch (phosphor::fan::util::DBusPropertyError&) 331 { 332 val = "Unknown"; 333 } 334 cout << val; 335 } 336 337 cout << endl; 338 } 339 } 340 341 /** 342 * @function print target RPM/PWM and tach readings from each fan 343 */ 344 void get() 345 { 346 using std::cout; 347 using std::endl; 348 using std::setw; 349 350 auto busData = loadDBusData(); 351 352 auto& fanNames{std::get<0>(busData)}; 353 auto& pathMap{std::get<1>(busData)}; 354 auto& interfaces{std::get<2>(busData)}; 355 auto& method = std::get<3>(busData); 356 357 std::string property; 358 359 // print the header 360 cout << "TARGET SENSOR" << setw(11) << "TARGET(" << method 361 << ") FEEDBACK SENSOR "; 362 cout << "FEEDBACK(" << method << ")" << endl; 363 cout << "===============================================================" 364 << endl; 365 366 for (auto& fan : fanNames) 367 { 368 if (pathMap["tach"][fan].size() == 0) 369 continue; 370 // print just the sensor name 371 auto shortPath = pathMap["tach"][fan][0]; 372 shortPath = justFanName(shortPath); 373 cout << shortPath << setw(22); 374 375 // print its target RPM/PWM 376 property = "Target"; 377 cout << SDBusPlus::getProperty<uint64_t>( 378 pathMap["tach"][fan][0], 379 interfaces[ifaceTypeFromMethod(method)], property) 380 << setw(12) << " "; 381 382 // print readings for each rotor 383 property = "Value"; 384 385 auto indent = 0U; 386 for (auto& path : pathMap["tach"][fan]) 387 { 388 cout << setw(indent); 389 cout << justFanName(path) << setw(17) 390 << SDBusPlus::getProperty<double>( 391 path, interfaces["SensorValue"], property) 392 << endl; 393 394 if (0 == indent) 395 indent = 46; 396 } 397 } 398 } 399 400 /** 401 * @function main entry point for the application 402 */ 403 int main(int argc, char* argv[]) 404 { 405 auto rc = 0; 406 407 try 408 { 409 CLI::App app{R"(Manually control, get fan tachs, view status, and resume 410 automatic control of all fans within a chassis.)"}; 411 412 app.set_help_flag("-h,--help", "Print this help page and exit."); 413 414 // App requires only 1 subcommand to be given 415 app.require_subcommand(1); 416 417 // This represents the command given 418 auto commands = app.add_option_group("Commands"); 419 420 // Configure method 421 auto cmdStatus = commands->add_subcommand( 422 "status", 423 "Get the fan tach targets/values and fan-control service status"); 424 425 cmdStatus->set_help_flag( 426 "-h, --help", "Prints fan target/tach readings, present/functional " 427 "states, and fan-monitor/BMC/Power service status"); 428 cmdStatus->require_option(0); 429 430 // Get method 431 auto cmdGet = commands->add_subcommand( 432 "get", 433 "Get the current fan target and feedback speeds for all rotors"); 434 cmdGet->set_help_flag( 435 "-h, --help", 436 "Get the current fan target and feedback speeds for all rotors"); 437 cmdGet->require_option(0); 438 439 CLI11_PARSE(app, argc, argv); 440 441 if (app.got_subcommand("status")) 442 { 443 status(); 444 } 445 else if (app.got_subcommand("get")) 446 { 447 get(); 448 } 449 } 450 catch (const std::exception& e) 451 { 452 rc = -1; 453 std::cerr << argv[0] << " failed: " << e.what() << std::endl; 454 } 455 456 return rc; 457 } 458