blob: 6af6c1eafee7be5bbe74e7a3042754f99f068495 [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
Mike Cappsff968232021-06-30 18:24:39 -040017#include "config.h"
18
Mike Capps385b1982021-06-28 10:40:42 -040019#include "sdbusplus.hpp"
20
21#include <CLI/CLI.hpp>
22#include <sdbusplus/bus.hpp>
23
24#include <iomanip>
25#include <iostream>
26
27using SDBusPlus = phosphor::fan::util::SDBusPlus;
28
Mike Capps16aab352021-06-30 10:17:21 -040029constexpr auto systemdMgrIface = "org.freedesktop.systemd1.Manager";
30constexpr auto systemdPath = "/org/freedesktop/systemd1";
31constexpr auto systemdService = "org.freedesktop.systemd1";
Mike Cappsff968232021-06-30 18:24:39 -040032constexpr auto phosphorServiceName = "phosphor-fan-control@0.service";
Mike Capps16aab352021-06-30 10:17:21 -040033
34enum
35{
36 FAN_NAMES = 0,
37 PATH_MAP = 1,
38 IFACES = 2,
39 METHOD = 3
40};
41
Mike Capps385b1982021-06-28 10:40:42 -040042/**
43 * @function extracts fan name from dbus path string (last token where
44 * delimiter is the / character), with proper bounds checking.
45 * @param[in] path - D-Bus path
46 * @return just the fan name.
47 */
48
49std::string justFanName(std::string const& path)
50{
51 std::string fanName;
52
53 auto itr = path.rfind("/");
54 if (itr != std::string::npos && itr < path.size())
55 {
56 fanName = path.substr(1 + itr);
57 }
58
59 return fanName;
60}
61
62/**
63 * @function produces subtree paths whose names match fan token names.
64 * @param[in] path - D-Bus path to obtain subtree from
65 * @param[in] iface - interface to obtain subTreePaths from
66 * @param[in] fans - label matching tokens to filter by
67 * @param[in] shortPath - flag to shorten fan token
68 * @return map of paths by fan name
69 */
70
71std::map<std::string, std::vector<std::string>>
72 getPathsFromIface(const std::string& path, const std::string& iface,
73 const std::vector<std::string>& fans,
74 bool shortPath = false)
75{
76 std::map<std::string, std::vector<std::string>> dest;
77
78 for (auto& path :
79 SDBusPlus::getSubTreePathsRaw(SDBusPlus::getBus(), path, iface, 0))
80 {
81 for (auto& fan : fans)
82 {
83 if (shortPath)
84 {
85 if (fan == justFanName(path))
86 {
87 dest[fan].push_back(path);
88 }
89 }
90 else if (std::string::npos != path.find(fan + "_"))
91 {
92 dest[fan].push_back(path);
93 }
94 }
95 }
96
97 return dest;
98}
99
100/**
Mike Capps530c6552021-06-29 09:50:38 -0400101 * @function consolidated function to load dbus paths and fan names
102 */
103auto loadDBusData()
104{
105 auto& bus{SDBusPlus::getBus()};
106
107 std::vector<std::string> fanNames;
108
109 // paths by D-bus interface,fan name
110 std::map<std::string, std::map<std::string, std::vector<std::string>>>
111 pathMap;
112
113 std::string method("RPM");
114
115 std::map<const std::string, const std::string> interfaces{
116 {"FanSpeed", "xyz.openbmc_project.Control.FanSpeed"},
117 {"FanPwm", "xyz.openbmc_project.Control.FanPwm"},
118 {"SensorValue", "xyz.openbmc_project.Sensor.Value"},
119 {"Item", "xyz.openbmc_project.Inventory.Item"},
120 {"OpStatus", "xyz.openbmc_project.State.Decorator.OperationalStatus"}};
121
122 std::map<const std::string, const std::string> paths{
123 {"motherboard",
124 "/xyz/openbmc_project/inventory/system/chassis/motherboard"},
125 {"tach", "/xyz/openbmc_project/sensors/fan_tach"}};
126
127 // build a list of all fans
128 for (auto& path : SDBusPlus::getSubTreePathsRaw(bus, paths["tach"],
129 interfaces["FanSpeed"], 0))
130 {
131 // special case where we build the list of fans
132 auto fan = justFanName(path);
133 fan = fan.substr(0, fan.rfind("_"));
134 fanNames.push_back(fan);
135 }
136
137 // retry with PWM mode if none found
138 if (0 == fanNames.size())
139 {
140 method = "PWM";
141
142 for (auto& path : SDBusPlus::getSubTreePathsRaw(
143 bus, paths["tach"], interfaces["FanPwm"], 0))
144 {
145 // special case where we build the list of fans
146 auto fan = justFanName(path);
147 fan = fan.substr(0, fan.rfind("_"));
148 fanNames.push_back(fan);
149 }
150 }
151
152 // load tach sensor paths for each fan
153 pathMap["tach"] =
154 getPathsFromIface(paths["tach"], interfaces["SensorValue"], fanNames);
155
156 // load inventory Item data for each fan
157 pathMap["inventory"] = getPathsFromIface(
158 paths["motherboard"], interfaces["Item"], fanNames, true);
159
160 // load operational status data for each fan
161 pathMap["opstatus"] = getPathsFromIface(
162 paths["motherboard"], interfaces["OpStatus"], fanNames, true);
163
164 return std::make_tuple(fanNames, pathMap, interfaces, method);
165}
166
167/**
Mike Capps385b1982021-06-28 10:40:42 -0400168 * @function gets the states of phosphor-fanctl. equivalent to
169 * "systemctl status phosphor-fan-control@0"
170 * @return a list of several (sub)states of fanctl (loaded,
171 * active, running) as well as D-Bus properties representing
172 * BMC states (bmc state,chassis power state, host state)
173 */
174
175std::array<std::string, 6> getStates()
176{
177 using DBusTuple =
178 std::tuple<std::string, std::string, std::string, std::string,
179 std::string, std::string, sdbusplus::message::object_path,
180 uint32_t, std::string, sdbusplus::message::object_path>;
181
182 std::array<std::string, 6> ret;
183
Mike Capps16aab352021-06-30 10:17:21 -0400184 std::vector<std::string> services{phosphorServiceName};
Mike Capps385b1982021-06-28 10:40:42 -0400185
186 try
187 {
188 auto fields{SDBusPlus::callMethodAndRead<std::vector<DBusTuple>>(
189 systemdService, systemdPath, systemdMgrIface, "ListUnitsByNames",
190 services)};
191
192 if (fields.size() > 0)
193 {
194 ret[0] = std::get<2>(fields[0]);
195 ret[1] = std::get<3>(fields[0]);
196 ret[2] = std::get<4>(fields[0]);
197 }
198 else
199 {
200 std::cout << "No units found for systemd service: " << services[0]
201 << std::endl;
202 }
203 }
Mike Capps530c6552021-06-29 09:50:38 -0400204 catch (const std::exception& e)
Mike Capps385b1982021-06-28 10:40:42 -0400205 {
206 std::cerr << "Failure retrieving phosphor-fan-control states: "
207 << e.what() << std::endl;
208 }
209
210 std::string path("/xyz/openbmc_project/state/bmc0");
211 std::string iface("xyz.openbmc_project.State.BMC");
212 ret[3] =
213 SDBusPlus::getProperty<std::string>(path, iface, "CurrentBMCState");
214
215 path = "/xyz/openbmc_project/state/chassis0";
216 iface = "xyz.openbmc_project.State.Chassis";
217 ret[4] =
218 SDBusPlus::getProperty<std::string>(path, iface, "CurrentPowerState");
219
220 path = "/xyz/openbmc_project/state/host0";
221 iface = "xyz.openbmc_project.State.Host";
222 ret[5] =
223 SDBusPlus::getProperty<std::string>(path, iface, "CurrentHostState");
224
225 return ret;
226}
227
228/**
Mike Capps530c6552021-06-29 09:50:38 -0400229 * @function helper to determine interface type from a given control method
230 */
231std::string ifaceTypeFromMethod(const std::string& method)
232{
233 return (method == "RPM" ? "FanSpeed" : "FanPwm");
234}
235
236/**
Mike Capps385b1982021-06-28 10:40:42 -0400237 * @function performs the "status" command from the cmdline.
238 * get states and sensor data and output to the console
239 */
240void status()
241{
242 using std::cout;
243 using std::endl;
244 using std::setw;
245
Mike Capps530c6552021-06-29 09:50:38 -0400246 auto busData = loadDBusData();
Mike Capps16aab352021-06-30 10:17:21 -0400247 auto& method = std::get<METHOD>(busData);
Mike Capps385b1982021-06-28 10:40:42 -0400248
Mike Capps530c6552021-06-29 09:50:38 -0400249 std::string property;
Mike Capps385b1982021-06-28 10:40:42 -0400250
251 // get the state,substate of fan-control and obmc
252 auto states = getStates();
253
254 // print the header
255 cout << "Fan Control Service State : " << states[0] << ", " << states[1]
256 << "(" << states[2] << ")" << endl;
257 cout << endl;
258 cout << "CurrentBMCState : " << states[3] << endl;
259 cout << "CurrentPowerState : " << states[4] << endl;
260 cout << "CurrentHostState : " << states[5] << endl;
261 cout << endl;
262 cout << " FAN "
Mike Cappsff968232021-06-30 18:24:39 -0400263 << "TARGET(" << method << ") FEEDBACK(RPM) PRESENT"
264 << " FUNCTIONAL" << endl;
Mike Capps385b1982021-06-28 10:40:42 -0400265 cout << "==============================================================="
266 << endl;
267
Mike Capps16aab352021-06-30 10:17:21 -0400268 auto& fanNames{std::get<FAN_NAMES>(busData)};
269 auto& pathMap{std::get<PATH_MAP>(busData)};
270 auto& interfaces{std::get<IFACES>(busData)};
Mike Capps530c6552021-06-29 09:50:38 -0400271
Mike Capps385b1982021-06-28 10:40:42 -0400272 for (auto& fan : fanNames)
273 {
Mike Cappsff968232021-06-30 18:24:39 -0400274 cout << " " << fan << setw(14);
Mike Capps385b1982021-06-28 10:40:42 -0400275
276 // get the target RPM
277 property = "Target";
278 cout << SDBusPlus::getProperty<uint64_t>(
Mike Capps530c6552021-06-29 09:50:38 -0400279 pathMap["tach"][fan][0],
280 interfaces[ifaceTypeFromMethod(method)], property)
Mike Cappsff968232021-06-30 18:24:39 -0400281 << setw(12);
Mike Capps385b1982021-06-28 10:40:42 -0400282
283 // get the sensor RPM
284 property = "Value";
Mike Capps530c6552021-06-29 09:50:38 -0400285
Mike Cappsff968232021-06-30 18:24:39 -0400286 std::ostringstream output;
Mike Capps385b1982021-06-28 10:40:42 -0400287 int numRotors = pathMap["tach"][fan].size();
288 // print tach readings for each rotor
289 for (auto& path : pathMap["tach"][fan])
290 {
Mike Cappsff968232021-06-30 18:24:39 -0400291 output << SDBusPlus::getProperty<double>(
Mike Capps385b1982021-06-28 10:40:42 -0400292 path, interfaces["SensorValue"], property);
293
294 // dont print slash on last rotor
295 if (--numRotors)
Mike Cappsff968232021-06-30 18:24:39 -0400296 output << "/";
Mike Capps385b1982021-06-28 10:40:42 -0400297 }
Mike Cappsff968232021-06-30 18:24:39 -0400298 cout << setw(18) << output.str() << setw(10);
Mike Capps385b1982021-06-28 10:40:42 -0400299
Mike Capps530c6552021-06-29 09:50:38 -0400300 // print the Present property
Mike Capps385b1982021-06-28 10:40:42 -0400301 property = "Present";
302 std::string val;
303 for (auto& path : pathMap["inventory"][fan])
304 {
305 try
306 {
307 if (SDBusPlus::getProperty<bool>(path, interfaces["Item"],
308 property))
309 {
310 val = "true";
311 }
312 else
313 {
314 val = "false";
315 }
316 }
Patrick Williamsddb773b2021-10-06 11:24:49 -0500317 catch (const phosphor::fan::util::DBusPropertyError&)
Mike Capps385b1982021-06-28 10:40:42 -0400318 {
319 val = "Unknown";
320 }
321 cout << val;
322 }
323
324 cout << setw(13);
325
Mike Capps530c6552021-06-29 09:50:38 -0400326 // and the functional property
Mike Capps385b1982021-06-28 10:40:42 -0400327 property = "Functional";
328 for (auto& path : pathMap["opstatus"][fan])
329 {
330 try
331 {
332 if (SDBusPlus::getProperty<bool>(path, interfaces["OpStatus"],
333 property))
334 {
335 val = "true";
336 }
337 else
338 {
339 val = "false";
340 }
341 }
Patrick Williamsddb773b2021-10-06 11:24:49 -0500342 catch (const phosphor::fan::util::DBusPropertyError&)
Mike Capps385b1982021-06-28 10:40:42 -0400343 {
344 val = "Unknown";
345 }
346 cout << val;
347 }
348
349 cout << endl;
350 }
351}
352
353/**
Mike Capps530c6552021-06-29 09:50:38 -0400354 * @function print target RPM/PWM and tach readings from each fan
355 */
356void get()
357{
358 using std::cout;
359 using std::endl;
360 using std::setw;
361
362 auto busData = loadDBusData();
363
Mike Capps16aab352021-06-30 10:17:21 -0400364 auto& fanNames{std::get<FAN_NAMES>(busData)};
365 auto& pathMap{std::get<PATH_MAP>(busData)};
366 auto& interfaces{std::get<IFACES>(busData)};
367 auto& method = std::get<METHOD>(busData);
Mike Capps530c6552021-06-29 09:50:38 -0400368
369 std::string property;
370
371 // print the header
372 cout << "TARGET SENSOR" << setw(11) << "TARGET(" << method
Mike Cappsff968232021-06-30 18:24:39 -0400373 << ") FEEDBACK SENSOR FEEDBACK(RPM)" << endl;
Mike Capps530c6552021-06-29 09:50:38 -0400374 cout << "==============================================================="
375 << endl;
376
377 for (auto& fan : fanNames)
378 {
379 if (pathMap["tach"][fan].size() == 0)
380 continue;
381 // print just the sensor name
382 auto shortPath = pathMap["tach"][fan][0];
383 shortPath = justFanName(shortPath);
Mike Cappsff968232021-06-30 18:24:39 -0400384 cout << shortPath << setw(18);
Mike Capps530c6552021-06-29 09:50:38 -0400385
386 // print its target RPM/PWM
387 property = "Target";
388 cout << SDBusPlus::getProperty<uint64_t>(
389 pathMap["tach"][fan][0],
390 interfaces[ifaceTypeFromMethod(method)], property)
391 << setw(12) << " ";
392
393 // print readings for each rotor
394 property = "Value";
395
396 auto indent = 0U;
397 for (auto& path : pathMap["tach"][fan])
398 {
399 cout << setw(indent);
Mike Cappsff968232021-06-30 18:24:39 -0400400 cout << justFanName(path) << setw(16)
Mike Capps530c6552021-06-29 09:50:38 -0400401 << SDBusPlus::getProperty<double>(
402 path, interfaces["SensorValue"], property)
403 << endl;
404
405 if (0 == indent)
Mike Cappsff968232021-06-30 18:24:39 -0400406 indent = 42;
Mike Capps530c6552021-06-29 09:50:38 -0400407 }
408 }
409}
410
411/**
Mike Capps16aab352021-06-30 10:17:21 -0400412 * @function set fan[s] to a target RPM
413 */
Mike Cappsff968232021-06-30 18:24:39 -0400414void set(uint64_t target, std::vector<std::string>& fanList)
Mike Capps16aab352021-06-30 10:17:21 -0400415{
416 auto busData = loadDBusData();
Mike Capps16aab352021-06-30 10:17:21 -0400417 auto& bus{SDBusPlus::getBus()};
418 auto& pathMap{std::get<PATH_MAP>(busData)};
419 auto& interfaces{std::get<IFACES>(busData)};
420 auto& method = std::get<METHOD>(busData);
421
422 std::string ifaceType(method == "RPM" ? "FanSpeed" : "FanPwm");
423
424 // stop the fan-control service
Mike Cappsff968232021-06-30 18:24:39 -0400425 SDBusPlus::callMethodAndRead<sdbusplus::message::object_path>(
Mike Capps16aab352021-06-30 10:17:21 -0400426 systemdService, systemdPath, systemdMgrIface, "StopUnit",
427 phosphorServiceName, "replace");
428
429 if (fanList.size() == 0)
430 {
431 fanList = std::get<FAN_NAMES>(busData);
432 }
433
434 for (auto& fan : fanList)
435 {
436 try
437 {
438 auto paths(pathMap["tach"].find(fan));
439
440 if (pathMap["tach"].end() == paths)
441 {
442 // try again, maybe it was a sensor name instead of a fan name
443 for (const auto& [fanName, sensors] : pathMap["tach"])
444 {
445 for (const auto& path : sensors)
446 {
447 std::string sensor(path.substr(path.rfind("/")));
448
449 if (sensor.size() > 0)
450 {
451 sensor = sensor.substr(1);
452
453 if (sensor == fan)
454 {
455 paths = pathMap["tach"].find(fanName);
456
457 break;
458 }
459 }
460 }
461 }
462 }
463
464 if (pathMap["tach"].end() == paths)
465 {
466 std::cout << "Could not find tach path for fan: " << fan
467 << std::endl;
468 continue;
469 }
470
471 // set the target RPM
472 SDBusPlus::setProperty<uint64_t>(bus, paths->second[0],
473 interfaces[ifaceType], "Target",
474 std::move(target));
475 }
476 catch (const phosphor::fan::util::DBusPropertyError& e)
477 {
478 std::cerr << "Cannot set target rpm for " << fan
479 << " caught D-Bus exception: " << e.what() << std::endl;
480 }
481 }
482}
483
484/**
485 * @function restart fan-control to allow it to manage fan speeds
486 */
487void resume()
488{
489 try
490 {
491 auto retval =
492 SDBusPlus::callMethodAndRead<sdbusplus::message::object_path>(
493 systemdService, systemdPath, systemdMgrIface, "StartUnit",
494 phosphorServiceName, "replace");
495 }
496 catch (const phosphor::fan::util::DBusMethodError& e)
497 {
498 std::cerr << "Unable to start fan control: " << e.what() << std::endl;
499 }
500}
501
502/**
Mike Cappsff968232021-06-30 18:24:39 -0400503 * @function force reload of control files by sending HUP signal
504 */
505void reload()
506{
507#ifdef CONTROL_USE_JSON
508 try
509 {
510 SDBusPlus::callMethod(systemdService, systemdPath, systemdMgrIface,
511 "KillUnit", phosphorServiceName, "main", SIGHUP);
512 }
513 catch (const phosphor::fan::util::DBusPropertyError& e)
514 {
515 std::cerr << "Unable to reload configuration files: " << e.what()
516 << std::endl;
517 }
518#else
519 // YAML config doesn't support SIGHUP-based reloads
520 std::cerr << "Error: reload function unavailable for YAML-configuration"
521 << std::endl;
522#endif
523}
524
525/**
526 * @function setup the CLI object to accept all options
527 */
528void initCLI(CLI::App& app, uint64_t& target, std::vector<std::string>& fanList)
529{
530 app.set_help_flag("-h,--help", "Print this help page and exit.");
531
532 // App requires only 1 subcommand to be given
533 app.require_subcommand(1);
534
535 // This represents the command given
536 auto commands = app.add_option_group("Commands");
537
538 // status method
539 std::string strHelp("Prints fan target/tach readings, present/functional "
540 "states, and fan-monitor/BMC/Power service status");
541 auto cmdStatus = commands->add_subcommand("status", strHelp);
542 cmdStatus->set_help_flag("-h, --help", strHelp);
543 cmdStatus->require_option(0);
544
545 // get method
546 strHelp = "Get the current fan target and feedback speeds for all rotors";
547 auto cmdGet = commands->add_subcommand("get", strHelp);
548 cmdGet->set_help_flag("-h, --help", strHelp);
549 cmdGet->require_option(0);
550
551 // set method
552 strHelp = "Set target (all rotors) for one-or-more fans";
553 auto cmdSet = commands->add_subcommand("set", strHelp);
554 strHelp = R"(set <TARGET> [TARGET SENSOR(S)]
555 <TARGET>
556 - RPM/PWM target to set the fans
557[TARGET SENSOR LIST]
558- list of target sensors to set)";
559 cmdSet->set_help_flag("-h, --help", strHelp);
560 cmdSet->add_option("target", target, "RPM/PWM target to set the fans");
561 cmdSet->add_option(
562 "fan list", fanList,
563 "[optional] list of 1+ fans to set target RPM/PWM (default: all)");
564 cmdSet->require_option();
565
566 strHelp = "Reload phosphor-fan configuration files";
567 auto cmdReload = commands->add_subcommand("reload", strHelp);
568 cmdReload->set_help_flag("-h, --help", strHelp);
569 cmdReload->require_option(0);
570
571 strHelp = "Resume running phosphor-fan-control";
572 auto cmdResume = commands->add_subcommand("resume", strHelp);
573 cmdResume->set_help_flag("-h, --help", strHelp);
574 cmdResume->require_option(0);
575}
576
577/**
Mike Capps385b1982021-06-28 10:40:42 -0400578 * @function main entry point for the application
579 */
580int main(int argc, char* argv[])
581{
582 auto rc = 0;
Mike Capps16aab352021-06-30 10:17:21 -0400583 uint64_t target{0U};
584 std::vector<std::string> fanList;
Mike Capps385b1982021-06-28 10:40:42 -0400585
586 try
587 {
Mike Cappsea5a8fc2021-09-24 11:26:17 -0400588 CLI::App app{"Manually control, get fan tachs, view status, and resume"
589 " automatic control of all fans within a chassis."};
Mike Capps385b1982021-06-28 10:40:42 -0400590
Mike Cappsff968232021-06-30 18:24:39 -0400591 initCLI(app, target, fanList);
Mike Capps16aab352021-06-30 10:17:21 -0400592
Mike Capps385b1982021-06-28 10:40:42 -0400593 CLI11_PARSE(app, argc, argv);
594
Mike Cappsff968232021-06-30 18:24:39 -0400595 if (app.got_subcommand("get"))
Mike Capps385b1982021-06-28 10:40:42 -0400596 {
Mike Cappsff968232021-06-30 18:24:39 -0400597 get();
Mike Capps385b1982021-06-28 10:40:42 -0400598 }
Mike Capps16aab352021-06-30 10:17:21 -0400599 else if (app.got_subcommand("set"))
600 {
601 set(target, fanList);
602 }
Mike Cappsff968232021-06-30 18:24:39 -0400603 else if (app.got_subcommand("reload"))
Mike Capps530c6552021-06-29 09:50:38 -0400604 {
Mike Cappsff968232021-06-30 18:24:39 -0400605 reload();
Mike Capps530c6552021-06-29 09:50:38 -0400606 }
Mike Capps16aab352021-06-30 10:17:21 -0400607 else if (app.got_subcommand("resume"))
608 {
609 resume();
610 }
Mike Cappsff968232021-06-30 18:24:39 -0400611 else if (app.got_subcommand("status"))
612 {
613 status();
614 }
Mike Capps385b1982021-06-28 10:40:42 -0400615 }
616 catch (const std::exception& e)
617 {
618 rc = -1;
619 std::cerr << argv[0] << " failed: " << e.what() << std::endl;
620 }
621
622 return rc;
623}