blob: d244a8699421557027f3f58425e03d6dd7b64c91 [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
83std::map<std::string, std::vector<std::string>>
84 getPathsFromIface(const std::string& path, const std::string& iface,
85 const std::vector<std::string>& fans,
86 bool shortPath = false)
87{
88 std::map<std::string, std::vector<std::string>> dest;
89
90 for (auto& path :
91 SDBusPlus::getSubTreePathsRaw(SDBusPlus::getBus(), path, iface, 0))
92 {
93 for (auto& fan : fans)
94 {
95 if (shortPath)
96 {
97 if (fan == justFanName(path))
98 {
99 dest[fan].push_back(path);
100 }
101 }
102 else if (std::string::npos != path.find(fan + "_"))
103 {
104 dest[fan].push_back(path);
105 }
106 }
107 }
108
109 return dest;
110}
111
112/**
Mike Capps530c6552021-06-29 09:50:38 -0400113 * @function consolidated function to load dbus paths and fan names
114 */
115auto loadDBusData()
116{
117 auto& bus{SDBusPlus::getBus()};
118
119 std::vector<std::string> fanNames;
120
121 // paths by D-bus interface,fan name
122 std::map<std::string, std::map<std::string, std::vector<std::string>>>
123 pathMap;
124
125 std::string method("RPM");
126
127 std::map<const std::string, const std::string> interfaces{
128 {"FanSpeed", "xyz.openbmc_project.Control.FanSpeed"},
129 {"FanPwm", "xyz.openbmc_project.Control.FanPwm"},
130 {"SensorValue", "xyz.openbmc_project.Sensor.Value"},
131 {"Item", "xyz.openbmc_project.Inventory.Item"},
132 {"OpStatus", "xyz.openbmc_project.State.Decorator.OperationalStatus"}};
133
134 std::map<const std::string, const std::string> paths{
135 {"motherboard",
136 "/xyz/openbmc_project/inventory/system/chassis/motherboard"},
137 {"tach", "/xyz/openbmc_project/sensors/fan_tach"}};
138
139 // build a list of all fans
140 for (auto& path : SDBusPlus::getSubTreePathsRaw(bus, paths["tach"],
141 interfaces["FanSpeed"], 0))
142 {
143 // special case where we build the list of fans
144 auto fan = justFanName(path);
145 fan = fan.substr(0, fan.rfind("_"));
146 fanNames.push_back(fan);
147 }
148
149 // retry with PWM mode if none found
150 if (0 == fanNames.size())
151 {
152 method = "PWM";
153
154 for (auto& path : SDBusPlus::getSubTreePathsRaw(
155 bus, paths["tach"], interfaces["FanPwm"], 0))
156 {
157 // special case where we build the list of fans
158 auto fan = justFanName(path);
159 fan = fan.substr(0, fan.rfind("_"));
160 fanNames.push_back(fan);
161 }
162 }
163
164 // load tach sensor paths for each fan
Patrick Williams61b73292023-05-10 07:50:12 -0500165 pathMap["tach"] = getPathsFromIface(paths["tach"],
166 interfaces["SensorValue"], fanNames);
Mike Capps530c6552021-06-29 09:50:38 -0400167
168 // load inventory Item data for each fan
169 pathMap["inventory"] = getPathsFromIface(
170 paths["motherboard"], interfaces["Item"], fanNames, true);
171
172 // load operational status data for each fan
173 pathMap["opstatus"] = getPathsFromIface(
174 paths["motherboard"], interfaces["OpStatus"], fanNames, true);
175
176 return std::make_tuple(fanNames, pathMap, interfaces, method);
177}
178
179/**
Mike Capps385b1982021-06-28 10:40:42 -0400180 * @function gets the states of phosphor-fanctl. equivalent to
181 * "systemctl status phosphor-fan-control@0"
182 * @return a list of several (sub)states of fanctl (loaded,
183 * active, running) as well as D-Bus properties representing
184 * BMC states (bmc state,chassis power state, host state)
185 */
186
187std::array<std::string, 6> getStates()
188{
189 using DBusTuple =
190 std::tuple<std::string, std::string, std::string, std::string,
191 std::string, std::string, sdbusplus::message::object_path,
192 uint32_t, std::string, sdbusplus::message::object_path>;
193
194 std::array<std::string, 6> ret;
195
Mike Capps16aab352021-06-30 10:17:21 -0400196 std::vector<std::string> services{phosphorServiceName};
Mike Capps385b1982021-06-28 10:40:42 -0400197
198 try
199 {
200 auto fields{SDBusPlus::callMethodAndRead<std::vector<DBusTuple>>(
201 systemdService, systemdPath, systemdMgrIface, "ListUnitsByNames",
202 services)};
203
204 if (fields.size() > 0)
205 {
206 ret[0] = std::get<2>(fields[0]);
207 ret[1] = std::get<3>(fields[0]);
208 ret[2] = std::get<4>(fields[0]);
209 }
210 else
211 {
212 std::cout << "No units found for systemd service: " << services[0]
213 << std::endl;
214 }
215 }
Mike Capps530c6552021-06-29 09:50:38 -0400216 catch (const std::exception& e)
Mike Capps385b1982021-06-28 10:40:42 -0400217 {
218 std::cerr << "Failure retrieving phosphor-fan-control states: "
219 << e.what() << std::endl;
220 }
221
222 std::string path("/xyz/openbmc_project/state/bmc0");
223 std::string iface("xyz.openbmc_project.State.BMC");
Patrick Williams61b73292023-05-10 07:50:12 -0500224 ret[3] = SDBusPlus::getProperty<std::string>(path, iface,
225 "CurrentBMCState");
Mike Capps385b1982021-06-28 10:40:42 -0400226
227 path = "/xyz/openbmc_project/state/chassis0";
228 iface = "xyz.openbmc_project.State.Chassis";
Patrick Williams61b73292023-05-10 07:50:12 -0500229 ret[4] = SDBusPlus::getProperty<std::string>(path, iface,
230 "CurrentPowerState");
Mike Capps385b1982021-06-28 10:40:42 -0400231
232 path = "/xyz/openbmc_project/state/host0";
233 iface = "xyz.openbmc_project.State.Host";
Patrick Williams61b73292023-05-10 07:50:12 -0500234 ret[5] = SDBusPlus::getProperty<std::string>(path, iface,
235 "CurrentHostState");
Mike Capps385b1982021-06-28 10:40:42 -0400236
237 return ret;
238}
239
240/**
Mike Capps530c6552021-06-29 09:50:38 -0400241 * @function helper to determine interface type from a given control method
242 */
243std::string ifaceTypeFromMethod(const std::string& method)
244{
245 return (method == "RPM" ? "FanSpeed" : "FanPwm");
246}
247
248/**
Mike Capps385b1982021-06-28 10:40:42 -0400249 * @function performs the "status" command from the cmdline.
250 * get states and sensor data and output to the console
251 */
252void status()
253{
254 using std::cout;
255 using std::endl;
256 using std::setw;
257
Mike Capps530c6552021-06-29 09:50:38 -0400258 auto busData = loadDBusData();
Mike Capps16aab352021-06-30 10:17:21 -0400259 auto& method = std::get<METHOD>(busData);
Mike Capps385b1982021-06-28 10:40:42 -0400260
Mike Capps530c6552021-06-29 09:50:38 -0400261 std::string property;
Mike Capps385b1982021-06-28 10:40:42 -0400262
263 // get the state,substate of fan-control and obmc
264 auto states = getStates();
265
266 // print the header
267 cout << "Fan Control Service State : " << states[0] << ", " << states[1]
268 << "(" << states[2] << ")" << endl;
269 cout << endl;
270 cout << "CurrentBMCState : " << states[3] << endl;
271 cout << "CurrentPowerState : " << states[4] << endl;
272 cout << "CurrentHostState : " << states[5] << endl;
273 cout << endl;
Matthew Barth3943b542022-02-07 14:16:03 -0600274 cout << "FAN "
275 << "TARGET(" << method << ") FEEDBACKS(RPM) PRESENT"
276 << " FUNCTIONAL" << endl;
Mike Capps385b1982021-06-28 10:40:42 -0400277 cout << "==============================================================="
278 << endl;
279
Mike Capps16aab352021-06-30 10:17:21 -0400280 auto& fanNames{std::get<FAN_NAMES>(busData)};
281 auto& pathMap{std::get<PATH_MAP>(busData)};
282 auto& interfaces{std::get<IFACES>(busData)};
Mike Capps530c6552021-06-29 09:50:38 -0400283
Mike Capps385b1982021-06-28 10:40:42 -0400284 for (auto& fan : fanNames)
285 {
Matthew Barth3943b542022-02-07 14:16:03 -0600286 cout << setw(8) << std::left << fan << std::right << setw(13);
Mike Capps385b1982021-06-28 10:40:42 -0400287
288 // get the target RPM
289 property = "Target";
290 cout << SDBusPlus::getProperty<uint64_t>(
Mike Capps530c6552021-06-29 09:50:38 -0400291 pathMap["tach"][fan][0],
292 interfaces[ifaceTypeFromMethod(method)], property)
Matthew Barth3943b542022-02-07 14:16:03 -0600293 << setw(19);
Mike Capps385b1982021-06-28 10:40:42 -0400294
295 // get the sensor RPM
296 property = "Value";
Mike Capps530c6552021-06-29 09:50:38 -0400297
Mike Cappsff968232021-06-30 18:24:39 -0400298 std::ostringstream output;
Mike Capps385b1982021-06-28 10:40:42 -0400299 int numRotors = pathMap["tach"][fan].size();
300 // print tach readings for each rotor
301 for (auto& path : pathMap["tach"][fan])
302 {
Mike Cappsff968232021-06-30 18:24:39 -0400303 output << SDBusPlus::getProperty<double>(
Mike Capps385b1982021-06-28 10:40:42 -0400304 path, interfaces["SensorValue"], property);
305
306 // dont print slash on last rotor
307 if (--numRotors)
Mike Cappsff968232021-06-30 18:24:39 -0400308 output << "/";
Mike Capps385b1982021-06-28 10:40:42 -0400309 }
Matthew Barth3943b542022-02-07 14:16:03 -0600310 cout << output.str() << setw(10);
Mike Capps385b1982021-06-28 10:40:42 -0400311
Mike Capps530c6552021-06-29 09:50:38 -0400312 // print the Present property
Mike Capps385b1982021-06-28 10:40:42 -0400313 property = "Present";
Matthew Barth3943b542022-02-07 14:16:03 -0600314 auto itFan = pathMap["inventory"].find(fan);
315 if (itFan != pathMap["inventory"].end())
Mike Capps385b1982021-06-28 10:40:42 -0400316 {
Matthew Barth3943b542022-02-07 14:16:03 -0600317 for (auto& path : itFan->second)
Mike Capps385b1982021-06-28 10:40:42 -0400318 {
Matthew Barth3943b542022-02-07 14:16:03 -0600319 try
Mike Capps385b1982021-06-28 10:40:42 -0400320 {
Matthew Barth3943b542022-02-07 14:16:03 -0600321 cout << std::boolalpha
322 << SDBusPlus::getProperty<bool>(
323 path, interfaces["Item"], property);
Mike Capps385b1982021-06-28 10:40:42 -0400324 }
Matthew Barth3943b542022-02-07 14:16:03 -0600325 catch (const phosphor::fan::util::DBusError&)
Mike Capps385b1982021-06-28 10:40:42 -0400326 {
Matthew Barth3943b542022-02-07 14:16:03 -0600327 cout << "Unknown";
Mike Capps385b1982021-06-28 10:40:42 -0400328 }
329 }
Matthew Barth3943b542022-02-07 14:16:03 -0600330 }
331 else
332 {
333 cout << "Unknown";
Mike Capps385b1982021-06-28 10:40:42 -0400334 }
335
336 cout << setw(13);
337
Mike Capps530c6552021-06-29 09:50:38 -0400338 // and the functional property
Mike Capps385b1982021-06-28 10:40:42 -0400339 property = "Functional";
Matthew Barth3943b542022-02-07 14:16:03 -0600340 itFan = pathMap["opstatus"].find(fan);
341 if (itFan != pathMap["opstatus"].end())
Mike Capps385b1982021-06-28 10:40:42 -0400342 {
Matthew Barth3943b542022-02-07 14:16:03 -0600343 for (auto& path : itFan->second)
Mike Capps385b1982021-06-28 10:40:42 -0400344 {
Matthew Barth3943b542022-02-07 14:16:03 -0600345 try
Mike Capps385b1982021-06-28 10:40:42 -0400346 {
Matthew Barth3943b542022-02-07 14:16:03 -0600347 cout << std::boolalpha
348 << SDBusPlus::getProperty<bool>(
349 path, interfaces["OpStatus"], property);
Mike Capps385b1982021-06-28 10:40:42 -0400350 }
Matthew Barth3943b542022-02-07 14:16:03 -0600351 catch (const phosphor::fan::util::DBusError&)
Mike Capps385b1982021-06-28 10:40:42 -0400352 {
Matthew Barth3943b542022-02-07 14:16:03 -0600353 cout << "Unknown";
Mike Capps385b1982021-06-28 10:40:42 -0400354 }
355 }
Matthew Barth3943b542022-02-07 14:16:03 -0600356 }
357 else
358 {
359 cout << "Unknown";
Mike Capps385b1982021-06-28 10:40:42 -0400360 }
361
362 cout << endl;
363 }
364}
365
366/**
Mike Capps530c6552021-06-29 09:50:38 -0400367 * @function print target RPM/PWM and tach readings from each fan
368 */
369void get()
370{
371 using std::cout;
372 using std::endl;
373 using std::setw;
374
375 auto busData = loadDBusData();
376
Mike Capps16aab352021-06-30 10:17:21 -0400377 auto& fanNames{std::get<FAN_NAMES>(busData)};
378 auto& pathMap{std::get<PATH_MAP>(busData)};
379 auto& interfaces{std::get<IFACES>(busData)};
380 auto& method = std::get<METHOD>(busData);
Mike Capps530c6552021-06-29 09:50:38 -0400381
382 std::string property;
383
384 // print the header
385 cout << "TARGET SENSOR" << setw(11) << "TARGET(" << method
Mike Cappsff968232021-06-30 18:24:39 -0400386 << ") FEEDBACK SENSOR FEEDBACK(RPM)" << endl;
Mike Capps530c6552021-06-29 09:50:38 -0400387 cout << "==============================================================="
388 << endl;
389
390 for (auto& fan : fanNames)
391 {
392 if (pathMap["tach"][fan].size() == 0)
393 continue;
394 // print just the sensor name
395 auto shortPath = pathMap["tach"][fan][0];
396 shortPath = justFanName(shortPath);
Matthew Barth2ca90172022-02-07 14:52:10 -0600397 cout << setw(13) << std::left << shortPath << std::right << setw(15);
Mike Capps530c6552021-06-29 09:50:38 -0400398
399 // print its target RPM/PWM
400 property = "Target";
401 cout << SDBusPlus::getProperty<uint64_t>(
Matthew Barth2ca90172022-02-07 14:52:10 -0600402 pathMap["tach"][fan][0], interfaces[ifaceTypeFromMethod(method)],
403 property);
Mike Capps530c6552021-06-29 09:50:38 -0400404
405 // print readings for each rotor
406 property = "Value";
407
Matthew Barth2ca90172022-02-07 14:52:10 -0600408 auto indent = 0;
Mike Capps530c6552021-06-29 09:50:38 -0400409 for (auto& path : pathMap["tach"][fan])
410 {
Matthew Barth2ca90172022-02-07 14:52:10 -0600411 cout << setw(18 + indent) << justFanName(path) << setw(17)
Mike Capps530c6552021-06-29 09:50:38 -0400412 << SDBusPlus::getProperty<double>(
413 path, interfaces["SensorValue"], property)
414 << endl;
415
416 if (0 == indent)
Matthew Barth2ca90172022-02-07 14:52:10 -0600417 indent = 28;
Mike Capps530c6552021-06-29 09:50:38 -0400418 }
419 }
420}
421
422/**
Mike Capps16aab352021-06-30 10:17:21 -0400423 * @function set fan[s] to a target RPM
424 */
Mike Cappsff968232021-06-30 18:24:39 -0400425void set(uint64_t target, std::vector<std::string>& fanList)
Mike Capps16aab352021-06-30 10:17:21 -0400426{
427 auto busData = loadDBusData();
Mike Capps16aab352021-06-30 10:17:21 -0400428 auto& bus{SDBusPlus::getBus()};
429 auto& pathMap{std::get<PATH_MAP>(busData)};
430 auto& interfaces{std::get<IFACES>(busData)};
431 auto& method = std::get<METHOD>(busData);
432
433 std::string ifaceType(method == "RPM" ? "FanSpeed" : "FanPwm");
434
435 // stop the fan-control service
Mike Cappsff968232021-06-30 18:24:39 -0400436 SDBusPlus::callMethodAndRead<sdbusplus::message::object_path>(
Mike Capps16aab352021-06-30 10:17:21 -0400437 systemdService, systemdPath, systemdMgrIface, "StopUnit",
438 phosphorServiceName, "replace");
439
440 if (fanList.size() == 0)
441 {
442 fanList = std::get<FAN_NAMES>(busData);
443 }
444
445 for (auto& fan : fanList)
446 {
447 try
448 {
449 auto paths(pathMap["tach"].find(fan));
450
451 if (pathMap["tach"].end() == paths)
452 {
453 // try again, maybe it was a sensor name instead of a fan name
454 for (const auto& [fanName, sensors] : pathMap["tach"])
455 {
456 for (const auto& path : sensors)
457 {
458 std::string sensor(path.substr(path.rfind("/")));
459
460 if (sensor.size() > 0)
461 {
462 sensor = sensor.substr(1);
463
464 if (sensor == fan)
465 {
466 paths = pathMap["tach"].find(fanName);
467
468 break;
469 }
470 }
471 }
472 }
473 }
474
475 if (pathMap["tach"].end() == paths)
476 {
477 std::cout << "Could not find tach path for fan: " << fan
478 << std::endl;
479 continue;
480 }
481
482 // set the target RPM
483 SDBusPlus::setProperty<uint64_t>(bus, paths->second[0],
484 interfaces[ifaceType], "Target",
485 std::move(target));
486 }
487 catch (const phosphor::fan::util::DBusPropertyError& e)
488 {
489 std::cerr << "Cannot set target rpm for " << fan
490 << " caught D-Bus exception: " << e.what() << std::endl;
491 }
492 }
493}
494
495/**
496 * @function restart fan-control to allow it to manage fan speeds
497 */
498void resume()
499{
500 try
501 {
502 auto retval =
503 SDBusPlus::callMethodAndRead<sdbusplus::message::object_path>(
504 systemdService, systemdPath, systemdMgrIface, "StartUnit",
505 phosphorServiceName, "replace");
506 }
507 catch (const phosphor::fan::util::DBusMethodError& e)
508 {
509 std::cerr << "Unable to start fan control: " << e.what() << std::endl;
510 }
511}
512
513/**
Mike Cappsff968232021-06-30 18:24:39 -0400514 * @function force reload of control files by sending HUP signal
515 */
516void reload()
517{
Mike Cappsff968232021-06-30 18:24:39 -0400518 try
519 {
520 SDBusPlus::callMethod(systemdService, systemdPath, systemdMgrIface,
521 "KillUnit", phosphorServiceName, "main", SIGHUP);
522 }
523 catch (const phosphor::fan::util::DBusPropertyError& e)
524 {
525 std::cerr << "Unable to reload configuration files: " << e.what()
526 << std::endl;
527 }
Mike Cappsff968232021-06-30 18:24:39 -0400528}
529
530/**
Matt Spinlerc70bce22023-03-16 10:49:49 -0500531 * @function dump debug data
Mike Capps7c8b97f2021-10-06 14:04:18 -0400532 */
Matt Spinlerb5c21a22021-10-14 16:52:12 -0500533void dumpFanControl()
Mike Capps7c8b97f2021-10-06 14:04:18 -0400534{
Mike Capps28945d42022-01-27 11:22:40 -0500535 namespace fs = std::filesystem;
536
Mike Capps7c8b97f2021-10-06 14:04:18 -0400537 try
538 {
Mike Capps28945d42022-01-27 11:22:40 -0500539 // delete existing file
540 if (fs::exists(dumpFile))
541 {
542 std::filesystem::remove(dumpFile);
543 }
544
Mike Capps7c8b97f2021-10-06 14:04:18 -0400545 SDBusPlus::callMethod(systemdService, systemdPath, systemdMgrIface,
546 "KillUnit", phosphorServiceName, "main", SIGUSR1);
Mike Capps28945d42022-01-27 11:22:40 -0500547
548 bool done = false;
Matt Spinlerdf769522022-08-26 14:19:02 -0500549 size_t tries = 0;
550 const size_t maxTries = 30;
Mike Capps28945d42022-01-27 11:22:40 -0500551
552 do
553 {
554 // wait for file to be detected
555 sleep(1);
556
557 if (fs::exists(dumpFile))
558 {
559 try
560 {
561 auto unused{nlohmann::json::parse(std::ifstream{dumpFile})};
562 done = true;
563 }
564 catch (...)
Matt Spinlerdf769522022-08-26 14:19:02 -0500565 {}
566 }
567
568 if (++tries > maxTries)
569 {
570 std::cerr << "Timed out waiting for fan control dump.\n";
571 return;
Mike Capps28945d42022-01-27 11:22:40 -0500572 }
573 } while (!done);
574
Matt Spinlerb564e152021-10-29 13:10:18 -0500575 std::cout << "Fan control dump written to: " << dumpFile << std::endl;
Mike Capps7c8b97f2021-10-06 14:04:18 -0400576 }
577 catch (const phosphor::fan::util::DBusPropertyError& e)
578 {
Matt Spinlerb5c21a22021-10-14 16:52:12 -0500579 std::cerr << "Unable to dump fan control: " << e.what() << std::endl;
Mike Capps7c8b97f2021-10-06 14:04:18 -0400580 }
581}
582
583/**
Matt Spinlerb564e152021-10-29 13:10:18 -0500584 * @function Query items in the dump file
585 */
586void queryDumpFile(const DumpQuery& dq)
587{
588 nlohmann::json output;
589 std::ifstream file{dumpFile};
590
591 if (!file.good())
592 {
593 std::cerr << "Unable to open dump file, please run 'fanctl dump'.\n";
594 return;
595 }
596
597 auto dumpData = nlohmann::json::parse(file);
598
599 if (!dumpData.contains(dq.section))
600 {
601 std::cerr << "Error: Dump file does not contain " << dq.section
602 << " section"
603 << "\n";
604 return;
605 }
606
607 const auto& section = dumpData.at(dq.section);
608
Matt Spinler69f19ff2021-11-04 09:28:25 -0500609 if (section.is_array())
610 {
611 for (const auto& entry : section)
612 {
613 if (!entry.is_string() || dq.name.empty() ||
614 (entry.get<std::string>().find(dq.name) != std::string::npos))
615 {
616 output[dq.section].push_back(entry);
617 }
618 }
619 std::cout << std::setw(4) << output << "\n";
620 return;
621 }
622
Matt Spinlerb564e152021-10-29 13:10:18 -0500623 for (const auto& [key1, values1] : section.items())
624 {
625 if (dq.name.empty() || (key1.find(dq.name) != std::string::npos))
626 {
627 // If no properties specified, print the whole JSON value
628 if (dq.properties.empty())
629 {
630 output[key1] = values1;
631 continue;
632 }
633
634 // Look for properties both one and two levels down.
635 // Future improvement: Use recursion.
636 for (const auto& [key2, values2] : values1.items())
637 {
638 for (const auto& prop : dq.properties)
639 {
640 if (prop == key2)
641 {
642 output[key1][prop] = values2;
643 }
644 }
645
646 for (const auto& [key3, values3] : values2.items())
647 {
648 for (const auto& prop : dq.properties)
649 {
650 if (prop == key3)
651 {
652 output[key1][prop] = values3;
653 }
654 }
655 }
656 }
657 }
658 }
659
660 if (!output.empty())
661 {
662 std::cout << std::setw(4) << output << "\n";
663 }
664}
665
666/**
Mike Cappsff968232021-06-30 18:24:39 -0400667 * @function setup the CLI object to accept all options
668 */
Matt Spinlerb564e152021-10-29 13:10:18 -0500669void initCLI(CLI::App& app, uint64_t& target, std::vector<std::string>& fanList,
Mike Cappsb2e9a4f2022-06-13 10:15:42 -0400670 [[maybe_unused]] DumpQuery& dq)
Mike Cappsff968232021-06-30 18:24:39 -0400671{
672 app.set_help_flag("-h,--help", "Print this help page and exit.");
673
674 // App requires only 1 subcommand to be given
675 app.require_subcommand(1);
676
677 // This represents the command given
678 auto commands = app.add_option_group("Commands");
679
680 // status method
681 std::string strHelp("Prints fan target/tach readings, present/functional "
682 "states, and fan-monitor/BMC/Power service status");
Mike Capps49766182021-09-27 22:32:46 -0400683
Mike Cappsff968232021-06-30 18:24:39 -0400684 auto cmdStatus = commands->add_subcommand("status", strHelp);
685 cmdStatus->set_help_flag("-h, --help", strHelp);
686 cmdStatus->require_option(0);
687
688 // get method
689 strHelp = "Get the current fan target and feedback speeds for all rotors";
690 auto cmdGet = commands->add_subcommand("get", strHelp);
691 cmdGet->set_help_flag("-h, --help", strHelp);
692 cmdGet->require_option(0);
693
694 // set method
695 strHelp = "Set target (all rotors) for one-or-more fans";
696 auto cmdSet = commands->add_subcommand("set", strHelp);
697 strHelp = R"(set <TARGET> [TARGET SENSOR(S)]
698 <TARGET>
699 - RPM/PWM target to set the fans
700[TARGET SENSOR LIST]
701- list of target sensors to set)";
702 cmdSet->set_help_flag("-h, --help", strHelp);
703 cmdSet->add_option("target", target, "RPM/PWM target to set the fans");
704 cmdSet->add_option(
705 "fan list", fanList,
706 "[optional] list of 1+ fans to set target RPM/PWM (default: all)");
707 cmdSet->require_option();
708
Matthew Barthad3b6b52021-10-26 15:38:15 -0500709#ifdef CONTROL_USE_JSON
Mike Cappsff968232021-06-30 18:24:39 -0400710 strHelp = "Reload phosphor-fan configuration files";
711 auto cmdReload = commands->add_subcommand("reload", strHelp);
712 cmdReload->set_help_flag("-h, --help", strHelp);
713 cmdReload->require_option(0);
Matthew Barthad3b6b52021-10-26 15:38:15 -0500714#endif
Mike Cappsff968232021-06-30 18:24:39 -0400715
716 strHelp = "Resume running phosphor-fan-control";
717 auto cmdResume = commands->add_subcommand("resume", strHelp);
718 cmdResume->set_help_flag("-h, --help", strHelp);
719 cmdResume->require_option(0);
Mike Capps7c8b97f2021-10-06 14:04:18 -0400720
721 // Dump method
Matt Spinlerc70bce22023-03-16 10:49:49 -0500722 auto cmdDump = commands->add_subcommand("dump", "Dump debug data");
723 cmdDump->set_help_flag("-h, --help", "Dump debug data");
Mike Capps7c8b97f2021-10-06 14:04:18 -0400724 cmdDump->require_option(0);
Matt Spinlerb564e152021-10-29 13:10:18 -0500725
Matt Spinlerc70bce22023-03-16 10:49:49 -0500726#ifdef CONTROL_USE_JSON
Matthew Barth1a4dcef2021-10-26 15:35:48 -0500727 // Query dump
Patrick Williams61b73292023-05-10 07:50:12 -0500728 auto cmdDumpQuery = commands->add_subcommand("query_dump",
729 "Query the dump file");
Matt Spinlerb564e152021-10-29 13:10:18 -0500730
731 cmdDumpQuery->set_help_flag("-h, --help", "Query the dump file");
732 cmdDumpQuery
733 ->add_option("-s, --section", dq.section, "Dump file section name")
734 ->required();
735 cmdDumpQuery->add_option("-n, --name", dq.name,
736 "Optional dump file entry name (or substring)");
737 cmdDumpQuery->add_option("-p, --properties", dq.properties,
738 "Optional list of dump file property names");
Matt Spinler12cb6902023-05-12 08:30:08 -0500739 cmdDumpQuery->add_flag("-d, --dump", dq.dump,
740 "Force a dump before the query");
Matt Spinlerb564e152021-10-29 13:10:18 -0500741#endif
Mike Cappsff968232021-06-30 18:24:39 -0400742}
743
744/**
Mike Capps385b1982021-06-28 10:40:42 -0400745 * @function main entry point for the application
746 */
747int main(int argc, char* argv[])
748{
749 auto rc = 0;
Mike Capps16aab352021-06-30 10:17:21 -0400750 uint64_t target{0U};
751 std::vector<std::string> fanList;
Matt Spinlerb564e152021-10-29 13:10:18 -0500752 DumpQuery dq;
Mike Capps385b1982021-06-28 10:40:42 -0400753
754 try
755 {
Mike Capps49766182021-09-27 22:32:46 -0400756 CLI::App app{"Manually control, get fan tachs, view status, and resume "
757 "automatic control of all fans within a chassis. Full "
758 "documentation can be found at the readme:\n"
759 "https://github.com/openbmc/phosphor-fan-presence/tree/"
760 "master/docs/control/fanctl"};
Mike Capps385b1982021-06-28 10:40:42 -0400761
Matt Spinlerb564e152021-10-29 13:10:18 -0500762 initCLI(app, target, fanList, dq);
Mike Capps16aab352021-06-30 10:17:21 -0400763
Mike Capps385b1982021-06-28 10:40:42 -0400764 CLI11_PARSE(app, argc, argv);
765
Mike Cappsff968232021-06-30 18:24:39 -0400766 if (app.got_subcommand("get"))
Mike Capps385b1982021-06-28 10:40:42 -0400767 {
Mike Cappsff968232021-06-30 18:24:39 -0400768 get();
Mike Capps385b1982021-06-28 10:40:42 -0400769 }
Mike Capps16aab352021-06-30 10:17:21 -0400770 else if (app.got_subcommand("set"))
771 {
772 set(target, fanList);
773 }
Matthew Barthad3b6b52021-10-26 15:38:15 -0500774#ifdef CONTROL_USE_JSON
Mike Cappsff968232021-06-30 18:24:39 -0400775 else if (app.got_subcommand("reload"))
Mike Capps530c6552021-06-29 09:50:38 -0400776 {
Mike Cappsff968232021-06-30 18:24:39 -0400777 reload();
Mike Capps530c6552021-06-29 09:50:38 -0400778 }
Matthew Barthad3b6b52021-10-26 15:38:15 -0500779#endif
Mike Capps16aab352021-06-30 10:17:21 -0400780 else if (app.got_subcommand("resume"))
781 {
782 resume();
783 }
Mike Cappsff968232021-06-30 18:24:39 -0400784 else if (app.got_subcommand("status"))
785 {
786 status();
787 }
Mike Capps7c8b97f2021-10-06 14:04:18 -0400788 else if (app.got_subcommand("dump"))
789 {
Mike Capps28945d42022-01-27 11:22:40 -0500790#ifdef CONTROL_USE_JSON
Matt Spinlerb5c21a22021-10-14 16:52:12 -0500791 dumpFanControl();
Mike Capps28945d42022-01-27 11:22:40 -0500792#else
793 std::ofstream(dumpFile)
794 << "{\n\"msg\": \"Unable to create dump on "
795 "non-JSON config based system\"\n}";
796#endif
Mike Capps7c8b97f2021-10-06 14:04:18 -0400797 }
Mike Capps28945d42022-01-27 11:22:40 -0500798#ifdef CONTROL_USE_JSON
Matt Spinlerb564e152021-10-29 13:10:18 -0500799 else if (app.got_subcommand("query_dump"))
800 {
Matt Spinler12cb6902023-05-12 08:30:08 -0500801 if (dq.dump)
802 {
803 dumpFanControl();
804 }
Matt Spinlerb564e152021-10-29 13:10:18 -0500805 queryDumpFile(dq);
806 }
807#endif
Mike Capps385b1982021-06-28 10:40:42 -0400808 }
809 catch (const std::exception& e)
810 {
811 rc = -1;
812 std::cerr << argv[0] << " failed: " << e.what() << std::endl;
813 }
814
815 return rc;
816}