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