1 /** 2 * Copyright © 2022 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 "config.h" 18 19 #include "sdbusplus.hpp" 20 21 #include <CLI/CLI.hpp> 22 #include <nlohmann/json.hpp> 23 #include <sdbusplus/bus.hpp> 24 25 #include <filesystem> 26 #include <iomanip> 27 #include <iostream> 28 29 using SDBusPlus = phosphor::fan::util::SDBusPlus; 30 31 constexpr auto systemdMgrIface = "org.freedesktop.systemd1.Manager"; 32 constexpr auto systemdPath = "/org/freedesktop/systemd1"; 33 constexpr auto systemdService = "org.freedesktop.systemd1"; 34 constexpr auto phosphorServiceName = "phosphor-fan-control@0.service"; 35 constexpr auto dumpFile = "/tmp/fan_control_dump.json"; 36 37 enum 38 { 39 FAN_NAMES = 0, 40 PATH_MAP = 1, 41 IFACES = 2, 42 METHOD = 3 43 }; 44 45 struct DumpQuery 46 { 47 std::string section; 48 std::string name; 49 std::vector<std::string> properties; 50 bool dump{false}; 51 }; 52 53 struct SensorOpts 54 { 55 std::string type; 56 std::string name; 57 bool verbose{false}; 58 }; 59 60 struct SensorOutput 61 { 62 std::string name; 63 double value; 64 bool functional; 65 bool available; 66 }; 67 68 /** 69 * @function extracts fan name from dbus path string (last token where 70 * delimiter is the / character), with proper bounds checking. 71 * @param[in] path - D-Bus path 72 * @return just the fan name. 73 */ 74 justFanName(const std::string & path)75 std::string justFanName(const std::string& path) 76 { 77 std::string fanName; 78 79 auto itr = path.rfind("/"); 80 if (itr != std::string::npos && itr < path.size()) 81 { 82 fanName = path.substr(1 + itr); 83 } 84 85 return fanName; 86 } 87 88 /** 89 * @function produces subtree paths whose names match fan token names. 90 * @param[in] path - D-Bus path to obtain subtree from 91 * @param[in] iface - interface to obtain subTreePaths from 92 * @param[in] fans - label matching tokens to filter by 93 * @param[in] shortPath - flag to shorten fan token 94 * @return map of paths by fan name 95 */ 96 getPathsFromIface(const std::string & path,const std::string & iface,const std::vector<std::string> & fans,bool shortPath=false)97 std::map<std::string, std::vector<std::string>> getPathsFromIface( 98 const std::string& path, const std::string& iface, 99 const std::vector<std::string>& fans, bool shortPath = false) 100 { 101 std::map<std::string, std::vector<std::string>> dest; 102 103 for (auto& path : 104 SDBusPlus::getSubTreePathsRaw(SDBusPlus::getBus(), path, iface, 0)) 105 { 106 for (auto& fan : fans) 107 { 108 if (shortPath) 109 { 110 if (fan == justFanName(path)) 111 { 112 dest[fan].push_back(path); 113 } 114 } 115 else if (std::string::npos != path.find(fan + "_")) 116 { 117 dest[fan].push_back(path); 118 } 119 } 120 } 121 122 return dest; 123 } 124 125 /** 126 * @function consolidated function to load dbus paths and fan names 127 */ loadDBusData()128 auto loadDBusData() 129 { 130 auto& bus{SDBusPlus::getBus()}; 131 132 std::vector<std::string> fanNames; 133 134 // paths by D-bus interface,fan name 135 std::map<std::string, std::map<std::string, std::vector<std::string>>> 136 pathMap; 137 138 std::string method("RPM"); 139 140 std::map<const std::string, const std::string> interfaces{ 141 {"FanSpeed", "xyz.openbmc_project.Control.FanSpeed"}, 142 {"FanPwm", "xyz.openbmc_project.Control.FanPwm"}, 143 {"SensorValue", "xyz.openbmc_project.Sensor.Value"}, 144 {"Item", "xyz.openbmc_project.Inventory.Item"}, 145 {"OpStatus", "xyz.openbmc_project.State.Decorator.OperationalStatus"}}; 146 147 std::map<const std::string, const std::string> paths{ 148 {"motherboard", 149 "/xyz/openbmc_project/inventory/system/chassis/motherboard"}, 150 {"tach", "/xyz/openbmc_project/sensors/fan_tach"}}; 151 152 // build a list of all fans 153 for (auto& path : SDBusPlus::getSubTreePathsRaw(bus, paths["tach"], 154 interfaces["FanSpeed"], 0)) 155 { 156 // special case where we build the list of fans 157 auto fan = justFanName(path); 158 fan = fan.substr(0, fan.rfind("_")); 159 fanNames.push_back(fan); 160 } 161 162 // retry with PWM mode if none found 163 if (0 == fanNames.size()) 164 { 165 method = "PWM"; 166 167 for (auto& path : SDBusPlus::getSubTreePathsRaw( 168 bus, paths["tach"], interfaces["FanPwm"], 0)) 169 { 170 // special case where we build the list of fans 171 auto fan = justFanName(path); 172 fan = fan.substr(0, fan.rfind("_")); 173 fanNames.push_back(fan); 174 } 175 } 176 177 // load tach sensor paths for each fan 178 pathMap["tach"] = 179 getPathsFromIface(paths["tach"], interfaces["SensorValue"], fanNames); 180 181 // load inventory Item data for each fan 182 pathMap["inventory"] = getPathsFromIface( 183 paths["motherboard"], interfaces["Item"], fanNames, true); 184 185 // load operational status data for each fan 186 pathMap["opstatus"] = getPathsFromIface( 187 paths["motherboard"], interfaces["OpStatus"], fanNames, true); 188 189 return std::make_tuple(fanNames, pathMap, interfaces, method); 190 } 191 192 /** 193 * @function gets the states of phosphor-fanctl. equivalent to 194 * "systemctl status phosphor-fan-control@0" 195 * @return a list of several (sub)states of fanctl (loaded, 196 * active, running) as well as D-Bus properties representing 197 * BMC states (bmc state,chassis power state, host state) 198 */ 199 getStates()200 std::array<std::string, 6> getStates() 201 { 202 using DBusTuple = 203 std::tuple<std::string, std::string, std::string, std::string, 204 std::string, std::string, sdbusplus::message::object_path, 205 uint32_t, std::string, sdbusplus::message::object_path>; 206 207 std::array<std::string, 6> ret; 208 209 std::vector<std::string> services{phosphorServiceName}; 210 211 try 212 { 213 auto fields{SDBusPlus::callMethodAndRead<std::vector<DBusTuple>>( 214 systemdService, systemdPath, systemdMgrIface, "ListUnitsByNames", 215 services)}; 216 217 if (fields.size() > 0) 218 { 219 ret[0] = std::get<2>(fields[0]); 220 ret[1] = std::get<3>(fields[0]); 221 ret[2] = std::get<4>(fields[0]); 222 } 223 else 224 { 225 std::cout << "No units found for systemd service: " << services[0] 226 << std::endl; 227 } 228 } 229 catch (const std::exception& e) 230 { 231 std::cerr << "Failure retrieving phosphor-fan-control states: " 232 << e.what() << std::endl; 233 } 234 235 std::string path("/xyz/openbmc_project/state/bmc0"); 236 std::string iface("xyz.openbmc_project.State.BMC"); 237 ret[3] = 238 SDBusPlus::getProperty<std::string>(path, iface, "CurrentBMCState"); 239 240 path = "/xyz/openbmc_project/state/chassis0"; 241 iface = "xyz.openbmc_project.State.Chassis"; 242 ret[4] = 243 SDBusPlus::getProperty<std::string>(path, iface, "CurrentPowerState"); 244 245 path = "/xyz/openbmc_project/state/host0"; 246 iface = "xyz.openbmc_project.State.Host"; 247 ret[5] = 248 SDBusPlus::getProperty<std::string>(path, iface, "CurrentHostState"); 249 250 return ret; 251 } 252 253 /** 254 * @function helper to determine interface type from a given control method 255 */ ifaceTypeFromMethod(const std::string & method)256 std::string ifaceTypeFromMethod(const std::string& method) 257 { 258 return (method == "RPM" ? "FanSpeed" : "FanPwm"); 259 } 260 261 /** 262 * @function performs the "status" command from the cmdline. 263 * get states and sensor data and output to the console 264 */ status()265 void status() 266 { 267 using std::cout; 268 using std::endl; 269 using std::setw; 270 271 auto busData = loadDBusData(); 272 auto& method = std::get<METHOD>(busData); 273 274 std::string property; 275 276 // get the state,substate of fan-control and obmc 277 auto states = getStates(); 278 279 // print the header 280 cout << "Fan Control Service State : " << states[0] << ", " << states[1] 281 << "(" << states[2] << ")" << endl; 282 cout << endl; 283 cout << "CurrentBMCState : " << states[3] << endl; 284 cout << "CurrentPowerState : " << states[4] << endl; 285 cout << "CurrentHostState : " << states[5] << endl; 286 cout << endl; 287 cout << "FAN " 288 << "TARGET(" << method << ") FEEDBACKS(RPM) PRESENT" 289 << " FUNCTIONAL" << endl; 290 cout << "===============================================================" 291 << endl; 292 293 auto& fanNames{std::get<FAN_NAMES>(busData)}; 294 auto& pathMap{std::get<PATH_MAP>(busData)}; 295 auto& interfaces{std::get<IFACES>(busData)}; 296 297 for (auto& fan : fanNames) 298 { 299 cout << setw(8) << std::left << fan << std::right << setw(13); 300 301 // get the target RPM 302 property = "Target"; 303 cout << SDBusPlus::getProperty<uint64_t>( 304 pathMap["tach"][fan][0], 305 interfaces[ifaceTypeFromMethod(method)], property) 306 << setw(19); 307 308 // get the sensor RPM 309 property = "Value"; 310 311 std::ostringstream output; 312 int numRotors = pathMap["tach"][fan].size(); 313 // print tach readings for each rotor 314 for (auto& path : pathMap["tach"][fan]) 315 { 316 output << SDBusPlus::getProperty<double>( 317 path, interfaces["SensorValue"], property); 318 319 // dont print slash on last rotor 320 if (--numRotors) 321 output << "/"; 322 } 323 cout << output.str() << setw(10); 324 325 // print the Present property 326 property = "Present"; 327 auto itFan = pathMap["inventory"].find(fan); 328 if (itFan != pathMap["inventory"].end()) 329 { 330 for (auto& path : itFan->second) 331 { 332 try 333 { 334 cout << std::boolalpha 335 << SDBusPlus::getProperty<bool>( 336 path, interfaces["Item"], property); 337 } 338 catch (const phosphor::fan::util::DBusError&) 339 { 340 cout << "Unknown"; 341 } 342 } 343 } 344 else 345 { 346 cout << "Unknown"; 347 } 348 349 cout << setw(13); 350 351 // and the functional property 352 property = "Functional"; 353 itFan = pathMap["opstatus"].find(fan); 354 if (itFan != pathMap["opstatus"].end()) 355 { 356 for (auto& path : itFan->second) 357 { 358 try 359 { 360 cout << std::boolalpha 361 << SDBusPlus::getProperty<bool>( 362 path, interfaces["OpStatus"], property); 363 } 364 catch (const phosphor::fan::util::DBusError&) 365 { 366 cout << "Unknown"; 367 } 368 } 369 } 370 else 371 { 372 cout << "Unknown"; 373 } 374 375 cout << endl; 376 } 377 } 378 379 /** 380 * @function print target RPM/PWM and tach readings from each fan 381 */ get()382 void get() 383 { 384 using std::cout; 385 using std::endl; 386 using std::setw; 387 388 auto busData = loadDBusData(); 389 390 auto& fanNames{std::get<FAN_NAMES>(busData)}; 391 auto& pathMap{std::get<PATH_MAP>(busData)}; 392 auto& interfaces{std::get<IFACES>(busData)}; 393 auto& method = std::get<METHOD>(busData); 394 395 std::string property; 396 397 // print the header 398 cout << "TARGET SENSOR" << setw(11) << "TARGET(" << method 399 << ") FEEDBACK SENSOR FEEDBACK(RPM)" << endl; 400 cout << "===============================================================" 401 << endl; 402 403 for (auto& fan : fanNames) 404 { 405 if (pathMap["tach"][fan].size() == 0) 406 continue; 407 // print just the sensor name 408 auto shortPath = pathMap["tach"][fan][0]; 409 shortPath = justFanName(shortPath); 410 cout << setw(13) << std::left << shortPath << std::right << setw(15); 411 412 // print its target RPM/PWM 413 property = "Target"; 414 cout << SDBusPlus::getProperty<uint64_t>( 415 pathMap["tach"][fan][0], interfaces[ifaceTypeFromMethod(method)], 416 property); 417 418 // print readings for each rotor 419 property = "Value"; 420 421 auto indent = 0; 422 for (auto& path : pathMap["tach"][fan]) 423 { 424 cout << setw(18 + indent) << justFanName(path) << setw(17) 425 << SDBusPlus::getProperty<double>( 426 path, interfaces["SensorValue"], property) 427 << endl; 428 429 if (0 == indent) 430 indent = 28; 431 } 432 } 433 } 434 435 /** 436 * @function set fan[s] to a target RPM 437 */ set(uint64_t target,std::vector<std::string> & fanList)438 void set(uint64_t target, std::vector<std::string>& fanList) 439 { 440 auto busData = loadDBusData(); 441 auto& bus{SDBusPlus::getBus()}; 442 auto& pathMap{std::get<PATH_MAP>(busData)}; 443 auto& interfaces{std::get<IFACES>(busData)}; 444 auto& method = std::get<METHOD>(busData); 445 446 std::string ifaceType(method == "RPM" ? "FanSpeed" : "FanPwm"); 447 448 // stop the fan-control service 449 SDBusPlus::callMethodAndRead<sdbusplus::message::object_path>( 450 systemdService, systemdPath, systemdMgrIface, "StopUnit", 451 phosphorServiceName, "replace"); 452 453 if (fanList.size() == 0) 454 { 455 fanList = std::get<FAN_NAMES>(busData); 456 } 457 458 for (auto& fan : fanList) 459 { 460 try 461 { 462 auto paths(pathMap["tach"].find(fan)); 463 464 if (pathMap["tach"].end() == paths) 465 { 466 // try again, maybe it was a sensor name instead of a fan name 467 for (const auto& [fanName, sensors] : pathMap["tach"]) 468 { 469 for (const auto& path : sensors) 470 { 471 std::string sensor(path.substr(path.rfind("/"))); 472 473 if (sensor.size() > 0) 474 { 475 sensor = sensor.substr(1); 476 477 if (sensor == fan) 478 { 479 paths = pathMap["tach"].find(fanName); 480 481 break; 482 } 483 } 484 } 485 } 486 } 487 488 if (pathMap["tach"].end() == paths) 489 { 490 std::cout << "Could not find tach path for fan: " << fan 491 << std::endl; 492 continue; 493 } 494 495 // set the target RPM 496 SDBusPlus::setProperty<uint64_t>(bus, paths->second[0], 497 interfaces[ifaceType], "Target", 498 std::move(target)); 499 } 500 catch (const phosphor::fan::util::DBusPropertyError& e) 501 { 502 std::cerr << "Cannot set target rpm for " << fan 503 << " caught D-Bus exception: " << e.what() << std::endl; 504 } 505 } 506 } 507 508 /** 509 * @function restart fan-control to allow it to manage fan speeds 510 */ resume()511 void resume() 512 { 513 try 514 { 515 auto retval = 516 SDBusPlus::callMethodAndRead<sdbusplus::message::object_path>( 517 systemdService, systemdPath, systemdMgrIface, "StartUnit", 518 phosphorServiceName, "replace"); 519 } 520 catch (const phosphor::fan::util::DBusMethodError& e) 521 { 522 std::cerr << "Unable to start fan control: " << e.what() << std::endl; 523 } 524 } 525 526 /** 527 * @function force reload of control files by sending HUP signal 528 */ reload()529 void reload() 530 { 531 try 532 { 533 SDBusPlus::callMethod(systemdService, systemdPath, systemdMgrIface, 534 "KillUnit", phosphorServiceName, "main", SIGHUP); 535 } 536 catch (const phosphor::fan::util::DBusPropertyError& e) 537 { 538 std::cerr << "Unable to reload configuration files: " << e.what() 539 << std::endl; 540 } 541 } 542 543 /** 544 * @function dump debug data 545 */ dumpFanControl()546 void dumpFanControl() 547 { 548 namespace fs = std::filesystem; 549 550 try 551 { 552 // delete existing file 553 if (fs::exists(dumpFile)) 554 { 555 std::filesystem::remove(dumpFile); 556 } 557 558 SDBusPlus::callMethod(systemdService, systemdPath, systemdMgrIface, 559 "KillUnit", phosphorServiceName, "main", SIGUSR1); 560 561 bool done = false; 562 size_t tries = 0; 563 const size_t maxTries = 30; 564 565 do 566 { 567 // wait for file to be detected 568 sleep(1); 569 570 if (fs::exists(dumpFile)) 571 { 572 try 573 { 574 auto unused{nlohmann::json::parse(std::ifstream{dumpFile})}; 575 done = true; 576 } 577 catch (...) 578 {} 579 } 580 581 if (++tries > maxTries) 582 { 583 std::cerr << "Timed out waiting for fan control dump.\n"; 584 return; 585 } 586 } while (!done); 587 588 std::cout << "Fan control dump written to: " << dumpFile << std::endl; 589 } 590 catch (const phosphor::fan::util::DBusPropertyError& e) 591 { 592 std::cerr << "Unable to dump fan control: " << e.what() << std::endl; 593 } 594 } 595 596 /** 597 * @function Query items in the dump file 598 */ queryDumpFile(const DumpQuery & dq)599 void queryDumpFile(const DumpQuery& dq) 600 { 601 nlohmann::json output; 602 std::ifstream file{dumpFile}; 603 604 if (!file.good()) 605 { 606 std::cerr << "Unable to open dump file, please run 'fanctl dump'.\n"; 607 return; 608 } 609 610 auto dumpData = nlohmann::json::parse(file); 611 612 if (!dumpData.contains(dq.section)) 613 { 614 std::cerr << "Error: Dump file does not contain " << dq.section 615 << " section" 616 << "\n"; 617 return; 618 } 619 620 const auto& section = dumpData.at(dq.section); 621 622 if (section.is_array()) 623 { 624 for (const auto& entry : section) 625 { 626 if (!entry.is_string() || dq.name.empty() || 627 (entry.get<std::string>().find(dq.name) != std::string::npos)) 628 { 629 output[dq.section].push_back(entry); 630 } 631 } 632 std::cout << std::setw(4) << output << "\n"; 633 return; 634 } 635 636 for (const auto& [key1, values1] : section.items()) 637 { 638 if (dq.name.empty() || (key1.find(dq.name) != std::string::npos)) 639 { 640 // If no properties specified, print the whole JSON value 641 if (dq.properties.empty()) 642 { 643 output[key1] = values1; 644 continue; 645 } 646 647 // Look for properties both one and two levels down. 648 // Future improvement: Use recursion. 649 for (const auto& [key2, values2] : values1.items()) 650 { 651 for (const auto& prop : dq.properties) 652 { 653 if (prop == key2) 654 { 655 output[key1][prop] = values2; 656 } 657 } 658 659 for (const auto& [key3, values3] : values2.items()) 660 { 661 for (const auto& prop : dq.properties) 662 { 663 if (prop == key3) 664 { 665 output[key1][prop] = values3; 666 } 667 } 668 } 669 } 670 } 671 } 672 673 if (!output.empty()) 674 { 675 std::cout << std::setw(4) << output << "\n"; 676 } 677 } 678 679 /** 680 * @function Get the sensor type based on the sensor name 681 * 682 * @param sensor The sensor object path 683 */ getSensorType(const std::string & sensor)684 std::string getSensorType(const std::string& sensor) 685 { 686 // Get type from /xyz/openbmc_project/sensors/<type>/<name> 687 try 688 { 689 auto type = 690 sensor.substr(std::string{"/xyz/openbmc_project/sensors/"}.size()); 691 return type.substr(0, type.find_first_of('/')); 692 } 693 catch (const std::exception& e) 694 { 695 std::cerr << "Failed extracting type from sensor " << sensor << ": " 696 << e.what() << "\n"; 697 } 698 return std::string{}; 699 } 700 701 /** 702 * @function Print the sensors passed in 703 * 704 * @param sensors The sensors to print 705 */ printSensors(const std::vector<SensorOutput> & sensors)706 void printSensors(const std::vector<SensorOutput>& sensors) 707 { 708 size_t maxNameSize = 0; 709 710 std::ranges::for_each(sensors, [&maxNameSize](const auto& s) { 711 maxNameSize = std::max(maxNameSize, s.name.size()); 712 }); 713 714 std::ranges::for_each(sensors, [maxNameSize](const auto& sensor) { 715 auto nameField = sensor.name + ':'; 716 std::cout << std::left << std::setw(maxNameSize + 2) << nameField 717 << sensor.value; 718 if (!sensor.functional) 719 { 720 std::cout << " (Functional=false)"; 721 } 722 723 if (!sensor.available) 724 { 725 std::cout << " (Available=false)"; 726 } 727 std::cout << "\n"; 728 }); 729 } 730 731 /** 732 * @function Extracts the sensor out of the GetManagedObjects output 733 * for the one object path passed in. 734 * 735 * @param object The GetManagedObjects output for a single object path 736 * @param opts The sensor options 737 * @param[out] sensors Filled in with the sensor data 738 */ extractSensorData(const auto & object,const SensorOpts & opts,std::vector<SensorOutput> & sensors)739 void extractSensorData(const auto& object, const SensorOpts& opts, 740 std::vector<SensorOutput>& sensors) 741 { 742 auto it = object.second.find("xyz.openbmc_project.Sensor.Value"); 743 if (it == object.second.end()) 744 { 745 return; 746 } 747 auto value = std::get<double>(it->second.at("Value")); 748 749 // Use the full D-Bus path of the sensor for the name if verbose 750 std::string name = object.first.str; 751 name = name.substr(name.find_last_of('/') + 1); 752 std::string printName = name; 753 if (opts.verbose) 754 { 755 printName = object.first.str; 756 } 757 758 // Apply the name filter 759 if (!opts.name.empty()) 760 { 761 if (!name.contains(opts.name)) 762 { 763 return; 764 } 765 } 766 767 // Apply the type filter 768 if (!opts.type.empty()) 769 { 770 if (opts.type != getSensorType(object.first.str)) 771 { 772 return; 773 } 774 } 775 776 bool functional = true; 777 it = object.second.find( 778 "xyz.openbmc_project.State.Decorator.OperationalStatus"); 779 if (it != object.second.end()) 780 { 781 functional = std::get<bool>(it->second.at("Functional")); 782 } 783 784 bool available = true; 785 it = object.second.find("xyz.openbmc_project.State.Decorator.Availability"); 786 if (it != object.second.end()) 787 { 788 available = std::get<bool>(it->second.at("Available")); 789 } 790 791 sensors.emplace_back(printName, value, functional, available); 792 } 793 794 /** 795 * @function Call GetManagedObjects on all sensor object managers and then 796 * print the sensor values. 797 * 798 * @param sensorManagers map<service, path> of sensor ObjectManagers 799 * @param opts The sensor options 800 */ readSensorsAndPrint(std::map<std::string,std::string> & sensorManagers,const SensorOpts & opts)801 void readSensorsAndPrint(std::map<std::string, std::string>& sensorManagers, 802 const SensorOpts& opts) 803 { 804 std::vector<SensorOutput> sensors; 805 806 using PropertyVariantType = 807 std::variant<bool, int32_t, int64_t, double, std::string>; 808 809 std::ranges::for_each(sensorManagers, [&opts, &sensors](const auto& entry) { 810 auto values = SDBusPlus::getManagedObjects<PropertyVariantType>( 811 SDBusPlus::getBus(), entry.first, entry.second); 812 813 // Pull out the sensor details 814 std::ranges::for_each(values, [&opts, &sensors](const auto& sensor) { 815 extractSensorData(sensor, opts, sensors); 816 }); 817 }); 818 819 std::ranges::sort(sensors, [](const auto& left, const auto& right) { 820 return left.name < right.name; 821 }); 822 823 printSensors(sensors); 824 } 825 826 /** 827 * @function Prints sensor values 828 * 829 * @param opts The sensor options 830 */ displaySensors(const SensorOpts & opts)831 void displaySensors(const SensorOpts& opts) 832 { 833 // Find the services that provide sensors 834 auto sensorObjects = SDBusPlus::getSubTreeRaw( 835 SDBusPlus::getBus(), "/", "xyz.openbmc_project.Sensor.Value", 0); 836 837 std::set<std::string> sensorServices; 838 839 std::ranges::for_each(sensorObjects, [&sensorServices](const auto& object) { 840 sensorServices.insert(object.second.begin()->first); 841 }); 842 843 // Find the ObjectManagers for those services 844 auto objectManagers = SDBusPlus::getSubTreeRaw( 845 SDBusPlus::getBus(), "/", "org.freedesktop.DBus.ObjectManager", 0); 846 847 std::map<std::string, std::string> managers; 848 849 std::ranges::for_each( 850 objectManagers, [&sensorServices, &managers](const auto& object) { 851 // Check every service on this path 852 std::ranges::for_each( 853 object.second, [&managers, path = object.first, 854 &sensorServices](const auto& entry) { 855 // Check if this service provides sensors 856 if (std::ranges::contains(sensorServices, entry.first)) 857 { 858 managers[entry.first] = path; 859 } 860 }); 861 }); 862 863 readSensorsAndPrint(managers, opts); 864 } 865 866 /** 867 * @function setup the CLI object to accept all options 868 */ initCLI(CLI::App & app,uint64_t & target,std::vector<std::string> & fanList,DumpQuery & dq,SensorOpts & sensorOpts)869 void initCLI(CLI::App& app, uint64_t& target, std::vector<std::string>& fanList, 870 [[maybe_unused]] DumpQuery& dq, SensorOpts& sensorOpts) 871 { 872 app.set_help_flag("-h,--help", "Print this help page and exit."); 873 874 // App requires only 1 subcommand to be given 875 app.require_subcommand(1); 876 877 // This represents the command given 878 auto commands = app.add_option_group("Commands"); 879 880 // status method 881 std::string strHelp("Prints fan target/tach readings, present/functional " 882 "states, and fan-monitor/BMC/Power service status"); 883 884 auto cmdStatus = commands->add_subcommand("status", strHelp); 885 cmdStatus->set_help_flag("-h, --help", strHelp); 886 cmdStatus->require_option(0); 887 888 // get method 889 strHelp = "Get the current fan target and feedback speeds for all rotors"; 890 auto cmdGet = commands->add_subcommand("get", strHelp); 891 cmdGet->set_help_flag("-h, --help", strHelp); 892 cmdGet->require_option(0); 893 894 // set method 895 strHelp = "Set target (all rotors) for one-or-more fans"; 896 auto cmdSet = commands->add_subcommand("set", strHelp); 897 strHelp = R"(set <TARGET> [TARGET SENSOR(S)] 898 <TARGET> 899 - RPM/PWM target to set the fans 900 [TARGET SENSOR LIST] 901 - list of target sensors to set)"; 902 cmdSet->set_help_flag("-h, --help", strHelp); 903 cmdSet->add_option("target", target, "RPM/PWM target to set the fans"); 904 cmdSet->add_option( 905 "fan list", fanList, 906 "[optional] list of 1+ fans to set target RPM/PWM (default: all)"); 907 cmdSet->require_option(); 908 909 #ifdef CONTROL_USE_JSON 910 strHelp = "Reload phosphor-fan configuration files"; 911 auto cmdReload = commands->add_subcommand("reload", strHelp); 912 cmdReload->set_help_flag("-h, --help", strHelp); 913 cmdReload->require_option(0); 914 #endif 915 916 strHelp = "Resume running phosphor-fan-control"; 917 auto cmdResume = commands->add_subcommand("resume", strHelp); 918 cmdResume->set_help_flag("-h, --help", strHelp); 919 cmdResume->require_option(0); 920 921 // Dump method 922 auto cmdDump = commands->add_subcommand("dump", "Dump debug data"); 923 cmdDump->set_help_flag("-h, --help", "Dump debug data"); 924 cmdDump->require_option(0); 925 926 #ifdef CONTROL_USE_JSON 927 // Query dump 928 auto cmdDumpQuery = 929 commands->add_subcommand("query_dump", "Query the dump file"); 930 931 cmdDumpQuery->set_help_flag("-h, --help", "Query the dump file"); 932 cmdDumpQuery 933 ->add_option("-s, --section", dq.section, "Dump file section name") 934 ->required(); 935 cmdDumpQuery->add_option("-n, --name", dq.name, 936 "Optional dump file entry name (or substring)"); 937 cmdDumpQuery->add_option("-p, --properties", dq.properties, 938 "Optional list of dump file property names"); 939 cmdDumpQuery->add_flag("-d, --dump", dq.dump, 940 "Force a dump before the query"); 941 #endif 942 943 auto cmdSensors = 944 commands->add_subcommand("sensors", "Retrieve sensor values"); 945 cmdSensors->set_help_flag("-h, --help", "Retrieve sensor values"); 946 cmdSensors->add_option( 947 "-t, --type", sensorOpts.type, 948 "Only show sensors of this type (i.e. 'temperature'). Optional"); 949 cmdSensors->add_option( 950 "-n, --name", sensorOpts.name, 951 "Only show sensors with this string in the name. Optional"); 952 cmdSensors->add_flag("-v, --verbose", sensorOpts.verbose, 953 "Verbose: Use sensor object path for the name"); 954 } 955 956 /** 957 * @function main entry point for the application 958 */ main(int argc,char * argv[])959 int main(int argc, char* argv[]) 960 { 961 auto rc = 0; 962 uint64_t target{0U}; 963 std::vector<std::string> fanList; 964 DumpQuery dq; 965 SensorOpts sensorOpts; 966 967 try 968 { 969 CLI::App app{"Manually control, get fan tachs, view status, and resume " 970 "automatic control of all fans within a chassis. Full " 971 "documentation can be found at the readme:\n" 972 "https://github.com/openbmc/phosphor-fan-presence/tree/" 973 "master/docs/control/fanctl"}; 974 975 initCLI(app, target, fanList, dq, sensorOpts); 976 977 CLI11_PARSE(app, argc, argv); 978 979 if (app.got_subcommand("get")) 980 { 981 get(); 982 } 983 else if (app.got_subcommand("set")) 984 { 985 set(target, fanList); 986 } 987 #ifdef CONTROL_USE_JSON 988 else if (app.got_subcommand("reload")) 989 { 990 reload(); 991 } 992 #endif 993 else if (app.got_subcommand("resume")) 994 { 995 resume(); 996 } 997 else if (app.got_subcommand("status")) 998 { 999 status(); 1000 } 1001 else if (app.got_subcommand("dump")) 1002 { 1003 #ifdef CONTROL_USE_JSON 1004 dumpFanControl(); 1005 #else 1006 std::ofstream(dumpFile) 1007 << "{\n\"msg\": \"Unable to create dump on " 1008 "non-JSON config based system\"\n}"; 1009 #endif 1010 } 1011 #ifdef CONTROL_USE_JSON 1012 else if (app.got_subcommand("query_dump")) 1013 { 1014 if (dq.dump) 1015 { 1016 dumpFanControl(); 1017 } 1018 queryDumpFile(dq); 1019 } 1020 #endif 1021 else if (app.got_subcommand("sensors")) 1022 { 1023 displaySensors(sensorOpts); 1024 } 1025 } 1026 catch (const std::exception& e) 1027 { 1028 rc = -1; 1029 std::cerr << argv[0] << " failed: " << e.what() << std::endl; 1030 } 1031 1032 return rc; 1033 } 1034