blob: 006109430cb17979d894ba17687889d4d178cbfe [file] [log] [blame]
Mike Capps385b1982021-06-28 10:40:42 -04001/**
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
25using SDBusPlus = phosphor::fan::util::SDBusPlus;
26
Mike Capps385b1982021-06-28 10:40:42 -040027/**
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
34std::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
56std::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/**
Mike Capps530c6552021-06-29 09:50:38 -040086 * @function consolidated function to load dbus paths and fan names
87 */
88auto 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/**
Mike Capps385b1982021-06-28 10:40:42 -0400153 * @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
160std::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
Mike Capps530c6552021-06-29 09:50:38 -0400167 constexpr auto systemdMgrIface = "org.freedesktop.systemd1.Manager";
168 constexpr auto systemdPath = "/org/freedesktop/systemd1";
169 constexpr auto systemdService = "org.freedesktop.systemd1";
170
Mike Capps385b1982021-06-28 10:40:42 -0400171 std::array<std::string, 6> ret;
172
Mike Capps530c6552021-06-29 09:50:38 -0400173 std::vector<std::string> services{"phosphor-fan-control@0.service"};
Mike Capps385b1982021-06-28 10:40:42 -0400174
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 }
Mike Capps530c6552021-06-29 09:50:38 -0400193 catch (const std::exception& e)
Mike Capps385b1982021-06-28 10:40:42 -0400194 {
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/**
Mike Capps530c6552021-06-29 09:50:38 -0400218 * @function helper to determine interface type from a given control method
219 */
220std::string ifaceTypeFromMethod(const std::string& method)
221{
222 return (method == "RPM" ? "FanSpeed" : "FanPwm");
223}
224
225/**
Mike Capps385b1982021-06-28 10:40:42 -0400226 * @function performs the "status" command from the cmdline.
227 * get states and sensor data and output to the console
228 */
229void status()
230{
231 using std::cout;
232 using std::endl;
233 using std::setw;
234
Mike Capps530c6552021-06-29 09:50:38 -0400235 auto busData = loadDBusData();
236 auto& method = std::get<3>(busData);
Mike Capps385b1982021-06-28 10:40:42 -0400237
Mike Capps530c6552021-06-29 09:50:38 -0400238 std::string property;
Mike Capps385b1982021-06-28 10:40:42 -0400239
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 "
Mike Capps530c6552021-06-29 09:50:38 -0400252 << "TARGET(" << method << ") FEEDBACKS(RPMS) PRESENT"
Mike Capps385b1982021-06-28 10:40:42 -0400253 << " FUNCTIONAL" << endl;
254 cout << "==============================================================="
255 << endl;
256
Mike Capps530c6552021-06-29 09:50:38 -0400257 auto& fanNames{std::get<0>(busData)};
258 auto& pathMap{std::get<1>(busData)};
259 auto& interfaces{std::get<2>(busData)};
260
Mike Capps385b1982021-06-28 10:40:42 -0400261 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>(
Mike Capps530c6552021-06-29 09:50:38 -0400268 pathMap["tach"][fan][0],
269 interfaces[ifaceTypeFromMethod(method)], property)
Mike Capps385b1982021-06-28 10:40:42 -0400270 << setw(11);
271
272 // get the sensor RPM
273 property = "Value";
Mike Capps530c6552021-06-29 09:50:38 -0400274
Mike Capps385b1982021-06-28 10:40:42 -0400275 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
Mike Capps530c6552021-06-29 09:50:38 -0400288 // print the Present property
Mike Capps385b1982021-06-28 10:40:42 -0400289 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 }
Patrick Williamsddb773b2021-10-06 11:24:49 -0500305 catch (const phosphor::fan::util::DBusPropertyError&)
Mike Capps385b1982021-06-28 10:40:42 -0400306 {
307 val = "Unknown";
308 }
309 cout << val;
310 }
311
312 cout << setw(13);
313
Mike Capps530c6552021-06-29 09:50:38 -0400314 // and the functional property
Mike Capps385b1982021-06-28 10:40:42 -0400315 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 }
Patrick Williamsddb773b2021-10-06 11:24:49 -0500330 catch (const phosphor::fan::util::DBusPropertyError&)
Mike Capps385b1982021-06-28 10:40:42 -0400331 {
332 val = "Unknown";
333 }
334 cout << val;
335 }
336
337 cout << endl;
338 }
339}
340
341/**
Mike Capps530c6552021-06-29 09:50:38 -0400342 * @function print target RPM/PWM and tach readings from each fan
343 */
344void 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/**
Mike Capps385b1982021-06-28 10:40:42 -0400401 * @function main entry point for the application
402 */
403int 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
Mike Capps530c6552021-06-29 09:50:38 -0400430 // 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
Mike Capps385b1982021-06-28 10:40:42 -0400439 CLI11_PARSE(app, argc, argv);
440
441 if (app.got_subcommand("status"))
442 {
443 status();
444 }
Mike Capps530c6552021-06-29 09:50:38 -0400445 else if (app.got_subcommand("get"))
446 {
447 get();
448 }
Mike Capps385b1982021-06-28 10:40:42 -0400449 }
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}