blob: 9789c24c0513c7c5360b723bda24e277e377ee42 [file] [log] [blame]
Mike Capps385b1982021-06-28 10:40:42 -04001/**
Mike Cappsb2e9a4f2022-06-13 10:15:42 -04002 * Copyright © 2022 IBM Corporation
Mike Capps385b1982021-06-28 10:40:42 -04003 *
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>
Matt Spinlerb564e152021-10-29 13:10:18 -050022#include <nlohmann/json.hpp>
Mike Capps385b1982021-06-28 10:40:42 -040023#include <sdbusplus/bus.hpp>
24
Matt Spinlerb564e152021-10-29 13:10:18 -050025#include <chrono>
26#include <filesystem>
Mike Capps385b1982021-06-28 10:40:42 -040027#include <iomanip>
28#include <iostream>
29
30using SDBusPlus = phosphor::fan::util::SDBusPlus;
31
Mike Capps16aab352021-06-30 10:17:21 -040032constexpr auto systemdMgrIface = "org.freedesktop.systemd1.Manager";
33constexpr auto systemdPath = "/org/freedesktop/systemd1";
34constexpr auto systemdService = "org.freedesktop.systemd1";
Mike Cappsff968232021-06-30 18:24:39 -040035constexpr auto phosphorServiceName = "phosphor-fan-control@0.service";
Matt Spinlerb564e152021-10-29 13:10:18 -050036constexpr auto dumpFile = "/tmp/fan_control_dump.json";
Mike Capps16aab352021-06-30 10:17:21 -040037
38enum
39{
40 FAN_NAMES = 0,
41 PATH_MAP = 1,
42 IFACES = 2,
43 METHOD = 3
44};
45
Matt Spinlerb564e152021-10-29 13:10:18 -050046struct DumpQuery
47{
48 std::string section;
49 std::string name;
50 std::vector<std::string> properties;
Matt Spinler12cb6902023-05-12 08:30:08 -050051 bool dump{false};
Matt Spinlerb564e152021-10-29 13:10:18 -050052};
53
Mike Capps385b1982021-06-28 10:40:42 -040054/**
55 * @function extracts fan name from dbus path string (last token where
56 * delimiter is the / character), with proper bounds checking.
57 * @param[in] path - D-Bus path
58 * @return just the fan name.
59 */
60
Patrick Williams61b73292023-05-10 07:50:12 -050061std::string justFanName(const std::string& path)
Mike Capps385b1982021-06-28 10:40:42 -040062{
63 std::string fanName;
64
65 auto itr = path.rfind("/");
66 if (itr != std::string::npos && itr < path.size())
67 {
68 fanName = path.substr(1 + itr);
69 }
70
71 return fanName;
72}
73
74/**
75 * @function produces subtree paths whose names match fan token names.
76 * @param[in] path - D-Bus path to obtain subtree from
77 * @param[in] iface - interface to obtain subTreePaths from
78 * @param[in] fans - label matching tokens to filter by
79 * @param[in] shortPath - flag to shorten fan token
80 * @return map of paths by fan name
81 */
82
Patrick Williamsdfddd642024-08-16 15:21:51 -040083std::map<std::string, std::vector<std::string>> getPathsFromIface(
84 const std::string& path, const std::string& iface,
85 const std::vector<std::string>& fans, bool shortPath = false)
Mike Capps385b1982021-06-28 10:40:42 -040086{
87 std::map<std::string, std::vector<std::string>> dest;
88
89 for (auto& path :
90 SDBusPlus::getSubTreePathsRaw(SDBusPlus::getBus(), path, iface, 0))
91 {
92 for (auto& fan : fans)
93 {
94 if (shortPath)
95 {
96 if (fan == justFanName(path))
97 {
98 dest[fan].push_back(path);
99 }
100 }
101 else if (std::string::npos != path.find(fan + "_"))
102 {
103 dest[fan].push_back(path);
104 }
105 }
106 }
107
108 return dest;
109}
110
111/**
Mike Capps530c6552021-06-29 09:50:38 -0400112 * @function consolidated function to load dbus paths and fan names
113 */
114auto loadDBusData()
115{
116 auto& bus{SDBusPlus::getBus()};
117
118 std::vector<std::string> fanNames;
119
120 // paths by D-bus interface,fan name
121 std::map<std::string, std::map<std::string, std::vector<std::string>>>
122 pathMap;
123
124 std::string method("RPM");
125
126 std::map<const std::string, const std::string> interfaces{
127 {"FanSpeed", "xyz.openbmc_project.Control.FanSpeed"},
128 {"FanPwm", "xyz.openbmc_project.Control.FanPwm"},
129 {"SensorValue", "xyz.openbmc_project.Sensor.Value"},
130 {"Item", "xyz.openbmc_project.Inventory.Item"},
131 {"OpStatus", "xyz.openbmc_project.State.Decorator.OperationalStatus"}};
132
133 std::map<const std::string, const std::string> paths{
134 {"motherboard",
135 "/xyz/openbmc_project/inventory/system/chassis/motherboard"},
136 {"tach", "/xyz/openbmc_project/sensors/fan_tach"}};
137
138 // build a list of all fans
139 for (auto& path : SDBusPlus::getSubTreePathsRaw(bus, paths["tach"],
140 interfaces["FanSpeed"], 0))
141 {
142 // special case where we build the list of fans
143 auto fan = justFanName(path);
144 fan = fan.substr(0, fan.rfind("_"));
145 fanNames.push_back(fan);
146 }
147
148 // retry with PWM mode if none found
149 if (0 == fanNames.size())
150 {
151 method = "PWM";
152
153 for (auto& path : SDBusPlus::getSubTreePathsRaw(
154 bus, paths["tach"], interfaces["FanPwm"], 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
163 // load tach sensor paths for each fan
Patrick Williamsdfddd642024-08-16 15:21:51 -0400164 pathMap["tach"] =
165 getPathsFromIface(paths["tach"], interfaces["SensorValue"], fanNames);
Mike Capps530c6552021-06-29 09:50:38 -0400166
167 // load inventory Item data for each fan
168 pathMap["inventory"] = getPathsFromIface(
169 paths["motherboard"], interfaces["Item"], fanNames, true);
170
171 // load operational status data for each fan
172 pathMap["opstatus"] = getPathsFromIface(
173 paths["motherboard"], interfaces["OpStatus"], fanNames, true);
174
175 return std::make_tuple(fanNames, pathMap, interfaces, method);
176}
177
178/**
Mike Capps385b1982021-06-28 10:40:42 -0400179 * @function gets the states of phosphor-fanctl. equivalent to
180 * "systemctl status phosphor-fan-control@0"
181 * @return a list of several (sub)states of fanctl (loaded,
182 * active, running) as well as D-Bus properties representing
183 * BMC states (bmc state,chassis power state, host state)
184 */
185
186std::array<std::string, 6> getStates()
187{
188 using DBusTuple =
189 std::tuple<std::string, std::string, std::string, std::string,
190 std::string, std::string, sdbusplus::message::object_path,
191 uint32_t, std::string, sdbusplus::message::object_path>;
192
193 std::array<std::string, 6> ret;
194
Mike Capps16aab352021-06-30 10:17:21 -0400195 std::vector<std::string> services{phosphorServiceName};
Mike Capps385b1982021-06-28 10:40:42 -0400196
197 try
198 {
199 auto fields{SDBusPlus::callMethodAndRead<std::vector<DBusTuple>>(
200 systemdService, systemdPath, systemdMgrIface, "ListUnitsByNames",
201 services)};
202
203 if (fields.size() > 0)
204 {
205 ret[0] = std::get<2>(fields[0]);
206 ret[1] = std::get<3>(fields[0]);
207 ret[2] = std::get<4>(fields[0]);
208 }
209 else
210 {
211 std::cout << "No units found for systemd service: " << services[0]
212 << std::endl;
213 }
214 }
Mike Capps530c6552021-06-29 09:50:38 -0400215 catch (const std::exception& e)
Mike Capps385b1982021-06-28 10:40:42 -0400216 {
217 std::cerr << "Failure retrieving phosphor-fan-control states: "
218 << e.what() << std::endl;
219 }
220
221 std::string path("/xyz/openbmc_project/state/bmc0");
222 std::string iface("xyz.openbmc_project.State.BMC");
Patrick Williamsdfddd642024-08-16 15:21:51 -0400223 ret[3] =
224 SDBusPlus::getProperty<std::string>(path, iface, "CurrentBMCState");
Mike Capps385b1982021-06-28 10:40:42 -0400225
226 path = "/xyz/openbmc_project/state/chassis0";
227 iface = "xyz.openbmc_project.State.Chassis";
Patrick Williamsdfddd642024-08-16 15:21:51 -0400228 ret[4] =
229 SDBusPlus::getProperty<std::string>(path, iface, "CurrentPowerState");
Mike Capps385b1982021-06-28 10:40:42 -0400230
231 path = "/xyz/openbmc_project/state/host0";
232 iface = "xyz.openbmc_project.State.Host";
Patrick Williamsdfddd642024-08-16 15:21:51 -0400233 ret[5] =
234 SDBusPlus::getProperty<std::string>(path, iface, "CurrentHostState");
Mike Capps385b1982021-06-28 10:40:42 -0400235
236 return ret;
237}
238
239/**
Mike Capps530c6552021-06-29 09:50:38 -0400240 * @function helper to determine interface type from a given control method
241 */
242std::string ifaceTypeFromMethod(const std::string& method)
243{
244 return (method == "RPM" ? "FanSpeed" : "FanPwm");
245}
246
247/**
Mike Capps385b1982021-06-28 10:40:42 -0400248 * @function performs the "status" command from the cmdline.
249 * get states and sensor data and output to the console
250 */
251void status()
252{
253 using std::cout;
254 using std::endl;
255 using std::setw;
256
Mike Capps530c6552021-06-29 09:50:38 -0400257 auto busData = loadDBusData();
Mike Capps16aab352021-06-30 10:17:21 -0400258 auto& method = std::get<METHOD>(busData);
Mike Capps385b1982021-06-28 10:40:42 -0400259
Mike Capps530c6552021-06-29 09:50:38 -0400260 std::string property;
Mike Capps385b1982021-06-28 10:40:42 -0400261
262 // get the state,substate of fan-control and obmc
263 auto states = getStates();
264
265 // print the header
266 cout << "Fan Control Service State : " << states[0] << ", " << states[1]
267 << "(" << states[2] << ")" << endl;
268 cout << endl;
269 cout << "CurrentBMCState : " << states[3] << endl;
270 cout << "CurrentPowerState : " << states[4] << endl;
271 cout << "CurrentHostState : " << states[5] << endl;
272 cout << endl;
Matthew Barth3943b542022-02-07 14:16:03 -0600273 cout << "FAN "
274 << "TARGET(" << method << ") FEEDBACKS(RPM) PRESENT"
275 << " FUNCTIONAL" << endl;
Mike Capps385b1982021-06-28 10:40:42 -0400276 cout << "==============================================================="
277 << endl;
278
Mike Capps16aab352021-06-30 10:17:21 -0400279 auto& fanNames{std::get<FAN_NAMES>(busData)};
280 auto& pathMap{std::get<PATH_MAP>(busData)};
281 auto& interfaces{std::get<IFACES>(busData)};
Mike Capps530c6552021-06-29 09:50:38 -0400282
Mike Capps385b1982021-06-28 10:40:42 -0400283 for (auto& fan : fanNames)
284 {
Matthew Barth3943b542022-02-07 14:16:03 -0600285 cout << setw(8) << std::left << fan << std::right << setw(13);
Mike Capps385b1982021-06-28 10:40:42 -0400286
287 // get the target RPM
288 property = "Target";
289 cout << SDBusPlus::getProperty<uint64_t>(
Mike Capps530c6552021-06-29 09:50:38 -0400290 pathMap["tach"][fan][0],
291 interfaces[ifaceTypeFromMethod(method)], property)
Matthew Barth3943b542022-02-07 14:16:03 -0600292 << setw(19);
Mike Capps385b1982021-06-28 10:40:42 -0400293
294 // get the sensor RPM
295 property = "Value";
Mike Capps530c6552021-06-29 09:50:38 -0400296
Mike Cappsff968232021-06-30 18:24:39 -0400297 std::ostringstream output;
Mike Capps385b1982021-06-28 10:40:42 -0400298 int numRotors = pathMap["tach"][fan].size();
299 // print tach readings for each rotor
300 for (auto& path : pathMap["tach"][fan])
301 {
Mike Cappsff968232021-06-30 18:24:39 -0400302 output << SDBusPlus::getProperty<double>(
Mike Capps385b1982021-06-28 10:40:42 -0400303 path, interfaces["SensorValue"], property);
304
305 // dont print slash on last rotor
306 if (--numRotors)
Mike Cappsff968232021-06-30 18:24:39 -0400307 output << "/";
Mike Capps385b1982021-06-28 10:40:42 -0400308 }
Matthew Barth3943b542022-02-07 14:16:03 -0600309 cout << output.str() << setw(10);
Mike Capps385b1982021-06-28 10:40:42 -0400310
Mike Capps530c6552021-06-29 09:50:38 -0400311 // print the Present property
Mike Capps385b1982021-06-28 10:40:42 -0400312 property = "Present";
Matthew Barth3943b542022-02-07 14:16:03 -0600313 auto itFan = pathMap["inventory"].find(fan);
314 if (itFan != pathMap["inventory"].end())
Mike Capps385b1982021-06-28 10:40:42 -0400315 {
Matthew Barth3943b542022-02-07 14:16:03 -0600316 for (auto& path : itFan->second)
Mike Capps385b1982021-06-28 10:40:42 -0400317 {
Matthew Barth3943b542022-02-07 14:16:03 -0600318 try
Mike Capps385b1982021-06-28 10:40:42 -0400319 {
Matthew Barth3943b542022-02-07 14:16:03 -0600320 cout << std::boolalpha
321 << SDBusPlus::getProperty<bool>(
322 path, interfaces["Item"], property);
Mike Capps385b1982021-06-28 10:40:42 -0400323 }
Matthew Barth3943b542022-02-07 14:16:03 -0600324 catch (const phosphor::fan::util::DBusError&)
Mike Capps385b1982021-06-28 10:40:42 -0400325 {
Matthew Barth3943b542022-02-07 14:16:03 -0600326 cout << "Unknown";
Mike Capps385b1982021-06-28 10:40:42 -0400327 }
328 }
Matthew Barth3943b542022-02-07 14:16:03 -0600329 }
330 else
331 {
332 cout << "Unknown";
Mike Capps385b1982021-06-28 10:40:42 -0400333 }
334
335 cout << setw(13);
336
Mike Capps530c6552021-06-29 09:50:38 -0400337 // and the functional property
Mike Capps385b1982021-06-28 10:40:42 -0400338 property = "Functional";
Matthew Barth3943b542022-02-07 14:16:03 -0600339 itFan = pathMap["opstatus"].find(fan);
340 if (itFan != pathMap["opstatus"].end())
Mike Capps385b1982021-06-28 10:40:42 -0400341 {
Matthew Barth3943b542022-02-07 14:16:03 -0600342 for (auto& path : itFan->second)
Mike Capps385b1982021-06-28 10:40:42 -0400343 {
Matthew Barth3943b542022-02-07 14:16:03 -0600344 try
Mike Capps385b1982021-06-28 10:40:42 -0400345 {
Matthew Barth3943b542022-02-07 14:16:03 -0600346 cout << std::boolalpha
347 << SDBusPlus::getProperty<bool>(
348 path, interfaces["OpStatus"], property);
Mike Capps385b1982021-06-28 10:40:42 -0400349 }
Matthew Barth3943b542022-02-07 14:16:03 -0600350 catch (const phosphor::fan::util::DBusError&)
Mike Capps385b1982021-06-28 10:40:42 -0400351 {
Matthew Barth3943b542022-02-07 14:16:03 -0600352 cout << "Unknown";
Mike Capps385b1982021-06-28 10:40:42 -0400353 }
354 }
Matthew Barth3943b542022-02-07 14:16:03 -0600355 }
356 else
357 {
358 cout << "Unknown";
Mike Capps385b1982021-06-28 10:40:42 -0400359 }
360
361 cout << endl;
362 }
363}
364
365/**
Mike Capps530c6552021-06-29 09:50:38 -0400366 * @function print target RPM/PWM and tach readings from each fan
367 */
368void get()
369{
370 using std::cout;
371 using std::endl;
372 using std::setw;
373
374 auto busData = loadDBusData();
375
Mike Capps16aab352021-06-30 10:17:21 -0400376 auto& fanNames{std::get<FAN_NAMES>(busData)};
377 auto& pathMap{std::get<PATH_MAP>(busData)};
378 auto& interfaces{std::get<IFACES>(busData)};
379 auto& method = std::get<METHOD>(busData);
Mike Capps530c6552021-06-29 09:50:38 -0400380
381 std::string property;
382
383 // print the header
384 cout << "TARGET SENSOR" << setw(11) << "TARGET(" << method
Mike Cappsff968232021-06-30 18:24:39 -0400385 << ") FEEDBACK SENSOR FEEDBACK(RPM)" << endl;
Mike Capps530c6552021-06-29 09:50:38 -0400386 cout << "==============================================================="
387 << endl;
388
389 for (auto& fan : fanNames)
390 {
391 if (pathMap["tach"][fan].size() == 0)
392 continue;
393 // print just the sensor name
394 auto shortPath = pathMap["tach"][fan][0];
395 shortPath = justFanName(shortPath);
Matthew Barth2ca90172022-02-07 14:52:10 -0600396 cout << setw(13) << std::left << shortPath << std::right << setw(15);
Mike Capps530c6552021-06-29 09:50:38 -0400397
398 // print its target RPM/PWM
399 property = "Target";
400 cout << SDBusPlus::getProperty<uint64_t>(
Matthew Barth2ca90172022-02-07 14:52:10 -0600401 pathMap["tach"][fan][0], interfaces[ifaceTypeFromMethod(method)],
402 property);
Mike Capps530c6552021-06-29 09:50:38 -0400403
404 // print readings for each rotor
405 property = "Value";
406
Matthew Barth2ca90172022-02-07 14:52:10 -0600407 auto indent = 0;
Mike Capps530c6552021-06-29 09:50:38 -0400408 for (auto& path : pathMap["tach"][fan])
409 {
Matthew Barth2ca90172022-02-07 14:52:10 -0600410 cout << setw(18 + indent) << justFanName(path) << setw(17)
Mike Capps530c6552021-06-29 09:50:38 -0400411 << SDBusPlus::getProperty<double>(
412 path, interfaces["SensorValue"], property)
413 << endl;
414
415 if (0 == indent)
Matthew Barth2ca90172022-02-07 14:52:10 -0600416 indent = 28;
Mike Capps530c6552021-06-29 09:50:38 -0400417 }
418 }
419}
420
421/**
Mike Capps16aab352021-06-30 10:17:21 -0400422 * @function set fan[s] to a target RPM
423 */
Mike Cappsff968232021-06-30 18:24:39 -0400424void set(uint64_t target, std::vector<std::string>& fanList)
Mike Capps16aab352021-06-30 10:17:21 -0400425{
426 auto busData = loadDBusData();
Mike Capps16aab352021-06-30 10:17:21 -0400427 auto& bus{SDBusPlus::getBus()};
428 auto& pathMap{std::get<PATH_MAP>(busData)};
429 auto& interfaces{std::get<IFACES>(busData)};
430 auto& method = std::get<METHOD>(busData);
431
432 std::string ifaceType(method == "RPM" ? "FanSpeed" : "FanPwm");
433
434 // stop the fan-control service
Mike Cappsff968232021-06-30 18:24:39 -0400435 SDBusPlus::callMethodAndRead<sdbusplus::message::object_path>(
Mike Capps16aab352021-06-30 10:17:21 -0400436 systemdService, systemdPath, systemdMgrIface, "StopUnit",
437 phosphorServiceName, "replace");
438
439 if (fanList.size() == 0)
440 {
441 fanList = std::get<FAN_NAMES>(busData);
442 }
443
444 for (auto& fan : fanList)
445 {
446 try
447 {
448 auto paths(pathMap["tach"].find(fan));
449
450 if (pathMap["tach"].end() == paths)
451 {
452 // try again, maybe it was a sensor name instead of a fan name
453 for (const auto& [fanName, sensors] : pathMap["tach"])
454 {
455 for (const auto& path : sensors)
456 {
457 std::string sensor(path.substr(path.rfind("/")));
458
459 if (sensor.size() > 0)
460 {
461 sensor = sensor.substr(1);
462
463 if (sensor == fan)
464 {
465 paths = pathMap["tach"].find(fanName);
466
467 break;
468 }
469 }
470 }
471 }
472 }
473
474 if (pathMap["tach"].end() == paths)
475 {
476 std::cout << "Could not find tach path for fan: " << fan
477 << std::endl;
478 continue;
479 }
480
481 // set the target RPM
482 SDBusPlus::setProperty<uint64_t>(bus, paths->second[0],
483 interfaces[ifaceType], "Target",
484 std::move(target));
485 }
486 catch (const phosphor::fan::util::DBusPropertyError& e)
487 {
488 std::cerr << "Cannot set target rpm for " << fan
489 << " caught D-Bus exception: " << e.what() << std::endl;
490 }
491 }
492}
493
494/**
495 * @function restart fan-control to allow it to manage fan speeds
496 */
497void resume()
498{
499 try
500 {
501 auto retval =
502 SDBusPlus::callMethodAndRead<sdbusplus::message::object_path>(
503 systemdService, systemdPath, systemdMgrIface, "StartUnit",
504 phosphorServiceName, "replace");
505 }
506 catch (const phosphor::fan::util::DBusMethodError& e)
507 {
508 std::cerr << "Unable to start fan control: " << e.what() << std::endl;
509 }
510}
511
512/**
Mike Cappsff968232021-06-30 18:24:39 -0400513 * @function force reload of control files by sending HUP signal
514 */
515void reload()
516{
Mike Cappsff968232021-06-30 18:24:39 -0400517 try
518 {
519 SDBusPlus::callMethod(systemdService, systemdPath, systemdMgrIface,
520 "KillUnit", phosphorServiceName, "main", SIGHUP);
521 }
522 catch (const phosphor::fan::util::DBusPropertyError& e)
523 {
524 std::cerr << "Unable to reload configuration files: " << e.what()
525 << std::endl;
526 }
Mike Cappsff968232021-06-30 18:24:39 -0400527}
528
529/**
Matt Spinlerc70bce22023-03-16 10:49:49 -0500530 * @function dump debug data
Mike Capps7c8b97f2021-10-06 14:04:18 -0400531 */
Matt Spinlerb5c21a22021-10-14 16:52:12 -0500532void dumpFanControl()
Mike Capps7c8b97f2021-10-06 14:04:18 -0400533{
Mike Capps28945d42022-01-27 11:22:40 -0500534 namespace fs = std::filesystem;
535
Mike Capps7c8b97f2021-10-06 14:04:18 -0400536 try
537 {
Mike Capps28945d42022-01-27 11:22:40 -0500538 // delete existing file
539 if (fs::exists(dumpFile))
540 {
541 std::filesystem::remove(dumpFile);
542 }
543
Mike Capps7c8b97f2021-10-06 14:04:18 -0400544 SDBusPlus::callMethod(systemdService, systemdPath, systemdMgrIface,
545 "KillUnit", phosphorServiceName, "main", SIGUSR1);
Mike Capps28945d42022-01-27 11:22:40 -0500546
547 bool done = false;
Matt Spinlerdf769522022-08-26 14:19:02 -0500548 size_t tries = 0;
549 const size_t maxTries = 30;
Mike Capps28945d42022-01-27 11:22:40 -0500550
551 do
552 {
553 // wait for file to be detected
554 sleep(1);
555
556 if (fs::exists(dumpFile))
557 {
558 try
559 {
560 auto unused{nlohmann::json::parse(std::ifstream{dumpFile})};
561 done = true;
562 }
563 catch (...)
Matt Spinlerdf769522022-08-26 14:19:02 -0500564 {}
565 }
566
567 if (++tries > maxTries)
568 {
569 std::cerr << "Timed out waiting for fan control dump.\n";
570 return;
Mike Capps28945d42022-01-27 11:22:40 -0500571 }
572 } while (!done);
573
Matt Spinlerb564e152021-10-29 13:10:18 -0500574 std::cout << "Fan control dump written to: " << dumpFile << std::endl;
Mike Capps7c8b97f2021-10-06 14:04:18 -0400575 }
576 catch (const phosphor::fan::util::DBusPropertyError& e)
577 {
Matt Spinlerb5c21a22021-10-14 16:52:12 -0500578 std::cerr << "Unable to dump fan control: " << e.what() << std::endl;
Mike Capps7c8b97f2021-10-06 14:04:18 -0400579 }
580}
581
582/**
Matt Spinlerb564e152021-10-29 13:10:18 -0500583 * @function Query items in the dump file
584 */
585void queryDumpFile(const DumpQuery& dq)
586{
587 nlohmann::json output;
588 std::ifstream file{dumpFile};
589
590 if (!file.good())
591 {
592 std::cerr << "Unable to open dump file, please run 'fanctl dump'.\n";
593 return;
594 }
595
596 auto dumpData = nlohmann::json::parse(file);
597
598 if (!dumpData.contains(dq.section))
599 {
600 std::cerr << "Error: Dump file does not contain " << dq.section
601 << " section"
602 << "\n";
603 return;
604 }
605
606 const auto& section = dumpData.at(dq.section);
607
Matt Spinler69f19ff2021-11-04 09:28:25 -0500608 if (section.is_array())
609 {
610 for (const auto& entry : section)
611 {
612 if (!entry.is_string() || dq.name.empty() ||
613 (entry.get<std::string>().find(dq.name) != std::string::npos))
614 {
615 output[dq.section].push_back(entry);
616 }
617 }
618 std::cout << std::setw(4) << output << "\n";
619 return;
620 }
621
Matt Spinlerb564e152021-10-29 13:10:18 -0500622 for (const auto& [key1, values1] : section.items())
623 {
624 if (dq.name.empty() || (key1.find(dq.name) != std::string::npos))
625 {
626 // If no properties specified, print the whole JSON value
627 if (dq.properties.empty())
628 {
629 output[key1] = values1;
630 continue;
631 }
632
633 // Look for properties both one and two levels down.
634 // Future improvement: Use recursion.
635 for (const auto& [key2, values2] : values1.items())
636 {
637 for (const auto& prop : dq.properties)
638 {
639 if (prop == key2)
640 {
641 output[key1][prop] = values2;
642 }
643 }
644
645 for (const auto& [key3, values3] : values2.items())
646 {
647 for (const auto& prop : dq.properties)
648 {
649 if (prop == key3)
650 {
651 output[key1][prop] = values3;
652 }
653 }
654 }
655 }
656 }
657 }
658
659 if (!output.empty())
660 {
661 std::cout << std::setw(4) << output << "\n";
662 }
663}
664
665/**
Mike Cappsff968232021-06-30 18:24:39 -0400666 * @function setup the CLI object to accept all options
667 */
Matt Spinlerb564e152021-10-29 13:10:18 -0500668void initCLI(CLI::App& app, uint64_t& target, std::vector<std::string>& fanList,
Mike Cappsb2e9a4f2022-06-13 10:15:42 -0400669 [[maybe_unused]] DumpQuery& dq)
Mike Cappsff968232021-06-30 18:24:39 -0400670{
671 app.set_help_flag("-h,--help", "Print this help page and exit.");
672
673 // App requires only 1 subcommand to be given
674 app.require_subcommand(1);
675
676 // This represents the command given
677 auto commands = app.add_option_group("Commands");
678
679 // status method
680 std::string strHelp("Prints fan target/tach readings, present/functional "
681 "states, and fan-monitor/BMC/Power service status");
Mike Capps49766182021-09-27 22:32:46 -0400682
Mike Cappsff968232021-06-30 18:24:39 -0400683 auto cmdStatus = commands->add_subcommand("status", strHelp);
684 cmdStatus->set_help_flag("-h, --help", strHelp);
685 cmdStatus->require_option(0);
686
687 // get method
688 strHelp = "Get the current fan target and feedback speeds for all rotors";
689 auto cmdGet = commands->add_subcommand("get", strHelp);
690 cmdGet->set_help_flag("-h, --help", strHelp);
691 cmdGet->require_option(0);
692
693 // set method
694 strHelp = "Set target (all rotors) for one-or-more fans";
695 auto cmdSet = commands->add_subcommand("set", strHelp);
696 strHelp = R"(set <TARGET> [TARGET SENSOR(S)]
697 <TARGET>
698 - RPM/PWM target to set the fans
699[TARGET SENSOR LIST]
700- list of target sensors to set)";
701 cmdSet->set_help_flag("-h, --help", strHelp);
702 cmdSet->add_option("target", target, "RPM/PWM target to set the fans");
703 cmdSet->add_option(
704 "fan list", fanList,
705 "[optional] list of 1+ fans to set target RPM/PWM (default: all)");
706 cmdSet->require_option();
707
Matthew Barthad3b6b52021-10-26 15:38:15 -0500708#ifdef CONTROL_USE_JSON
Mike Cappsff968232021-06-30 18:24:39 -0400709 strHelp = "Reload phosphor-fan configuration files";
710 auto cmdReload = commands->add_subcommand("reload", strHelp);
711 cmdReload->set_help_flag("-h, --help", strHelp);
712 cmdReload->require_option(0);
Matthew Barthad3b6b52021-10-26 15:38:15 -0500713#endif
Mike Cappsff968232021-06-30 18:24:39 -0400714
715 strHelp = "Resume running phosphor-fan-control";
716 auto cmdResume = commands->add_subcommand("resume", strHelp);
717 cmdResume->set_help_flag("-h, --help", strHelp);
718 cmdResume->require_option(0);
Mike Capps7c8b97f2021-10-06 14:04:18 -0400719
720 // Dump method
Matt Spinlerc70bce22023-03-16 10:49:49 -0500721 auto cmdDump = commands->add_subcommand("dump", "Dump debug data");
722 cmdDump->set_help_flag("-h, --help", "Dump debug data");
Mike Capps7c8b97f2021-10-06 14:04:18 -0400723 cmdDump->require_option(0);
Matt Spinlerb564e152021-10-29 13:10:18 -0500724
Matt Spinlerc70bce22023-03-16 10:49:49 -0500725#ifdef CONTROL_USE_JSON
Matthew Barth1a4dcef2021-10-26 15:35:48 -0500726 // Query dump
Patrick Williamsdfddd642024-08-16 15:21:51 -0400727 auto cmdDumpQuery =
728 commands->add_subcommand("query_dump", "Query the dump file");
Matt Spinlerb564e152021-10-29 13:10:18 -0500729
730 cmdDumpQuery->set_help_flag("-h, --help", "Query the dump file");
731 cmdDumpQuery
732 ->add_option("-s, --section", dq.section, "Dump file section name")
733 ->required();
734 cmdDumpQuery->add_option("-n, --name", dq.name,
735 "Optional dump file entry name (or substring)");
736 cmdDumpQuery->add_option("-p, --properties", dq.properties,
737 "Optional list of dump file property names");
Matt Spinler12cb6902023-05-12 08:30:08 -0500738 cmdDumpQuery->add_flag("-d, --dump", dq.dump,
739 "Force a dump before the query");
Matt Spinlerb564e152021-10-29 13:10:18 -0500740#endif
Mike Cappsff968232021-06-30 18:24:39 -0400741}
742
743/**
Mike Capps385b1982021-06-28 10:40:42 -0400744 * @function main entry point for the application
745 */
746int main(int argc, char* argv[])
747{
748 auto rc = 0;
Mike Capps16aab352021-06-30 10:17:21 -0400749 uint64_t target{0U};
750 std::vector<std::string> fanList;
Matt Spinlerb564e152021-10-29 13:10:18 -0500751 DumpQuery dq;
Mike Capps385b1982021-06-28 10:40:42 -0400752
753 try
754 {
Mike Capps49766182021-09-27 22:32:46 -0400755 CLI::App app{"Manually control, get fan tachs, view status, and resume "
756 "automatic control of all fans within a chassis. Full "
757 "documentation can be found at the readme:\n"
758 "https://github.com/openbmc/phosphor-fan-presence/tree/"
759 "master/docs/control/fanctl"};
Mike Capps385b1982021-06-28 10:40:42 -0400760
Matt Spinlerb564e152021-10-29 13:10:18 -0500761 initCLI(app, target, fanList, dq);
Mike Capps16aab352021-06-30 10:17:21 -0400762
Mike Capps385b1982021-06-28 10:40:42 -0400763 CLI11_PARSE(app, argc, argv);
764
Mike Cappsff968232021-06-30 18:24:39 -0400765 if (app.got_subcommand("get"))
Mike Capps385b1982021-06-28 10:40:42 -0400766 {
Mike Cappsff968232021-06-30 18:24:39 -0400767 get();
Mike Capps385b1982021-06-28 10:40:42 -0400768 }
Mike Capps16aab352021-06-30 10:17:21 -0400769 else if (app.got_subcommand("set"))
770 {
771 set(target, fanList);
772 }
Matthew Barthad3b6b52021-10-26 15:38:15 -0500773#ifdef CONTROL_USE_JSON
Mike Cappsff968232021-06-30 18:24:39 -0400774 else if (app.got_subcommand("reload"))
Mike Capps530c6552021-06-29 09:50:38 -0400775 {
Mike Cappsff968232021-06-30 18:24:39 -0400776 reload();
Mike Capps530c6552021-06-29 09:50:38 -0400777 }
Matthew Barthad3b6b52021-10-26 15:38:15 -0500778#endif
Mike Capps16aab352021-06-30 10:17:21 -0400779 else if (app.got_subcommand("resume"))
780 {
781 resume();
782 }
Mike Cappsff968232021-06-30 18:24:39 -0400783 else if (app.got_subcommand("status"))
784 {
785 status();
786 }
Mike Capps7c8b97f2021-10-06 14:04:18 -0400787 else if (app.got_subcommand("dump"))
788 {
Mike Capps28945d42022-01-27 11:22:40 -0500789#ifdef CONTROL_USE_JSON
Matt Spinlerb5c21a22021-10-14 16:52:12 -0500790 dumpFanControl();
Mike Capps28945d42022-01-27 11:22:40 -0500791#else
792 std::ofstream(dumpFile)
793 << "{\n\"msg\": \"Unable to create dump on "
794 "non-JSON config based system\"\n}";
795#endif
Mike Capps7c8b97f2021-10-06 14:04:18 -0400796 }
Mike Capps28945d42022-01-27 11:22:40 -0500797#ifdef CONTROL_USE_JSON
Matt Spinlerb564e152021-10-29 13:10:18 -0500798 else if (app.got_subcommand("query_dump"))
799 {
Matt Spinler12cb6902023-05-12 08:30:08 -0500800 if (dq.dump)
801 {
802 dumpFanControl();
803 }
Matt Spinlerb564e152021-10-29 13:10:18 -0500804 queryDumpFile(dq);
805 }
806#endif
Mike Capps385b1982021-06-28 10:40:42 -0400807 }
808 catch (const std::exception& e)
809 {
810 rc = -1;
811 std::cerr << argv[0] << " failed: " << e.what() << std::endl;
812 }
813
814 return rc;
815}