blob: 80cf58bcca6c466e1f6c621478cb5f17083d88f4 [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>
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;
51};
52
Mike Capps385b1982021-06-28 10:40:42 -040053/**
54 * @function extracts fan name from dbus path string (last token where
55 * delimiter is the / character), with proper bounds checking.
56 * @param[in] path - D-Bus path
57 * @return just the fan name.
58 */
59
60std::string justFanName(std::string const& path)
61{
62 std::string fanName;
63
64 auto itr = path.rfind("/");
65 if (itr != std::string::npos && itr < path.size())
66 {
67 fanName = path.substr(1 + itr);
68 }
69
70 return fanName;
71}
72
73/**
74 * @function produces subtree paths whose names match fan token names.
75 * @param[in] path - D-Bus path to obtain subtree from
76 * @param[in] iface - interface to obtain subTreePaths from
77 * @param[in] fans - label matching tokens to filter by
78 * @param[in] shortPath - flag to shorten fan token
79 * @return map of paths by fan name
80 */
81
82std::map<std::string, std::vector<std::string>>
83 getPathsFromIface(const std::string& path, const std::string& iface,
84 const std::vector<std::string>& fans,
85 bool shortPath = false)
86{
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
164 pathMap["tach"] =
165 getPathsFromIface(paths["tach"], interfaces["SensorValue"], fanNames);
166
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");
223 ret[3] =
224 SDBusPlus::getProperty<std::string>(path, iface, "CurrentBMCState");
225
226 path = "/xyz/openbmc_project/state/chassis0";
227 iface = "xyz.openbmc_project.State.Chassis";
228 ret[4] =
229 SDBusPlus::getProperty<std::string>(path, iface, "CurrentPowerState");
230
231 path = "/xyz/openbmc_project/state/host0";
232 iface = "xyz.openbmc_project.State.Host";
233 ret[5] =
234 SDBusPlus::getProperty<std::string>(path, iface, "CurrentHostState");
235
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;
273 cout << " FAN "
Mike Cappsff968232021-06-30 18:24:39 -0400274 << "TARGET(" << method << ") FEEDBACK(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 {
Mike Cappsff968232021-06-30 18:24:39 -0400285 cout << " " << fan << setw(14);
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)
Mike Cappsff968232021-06-30 18:24:39 -0400292 << setw(12);
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 }
Mike Cappsff968232021-06-30 18:24:39 -0400309 cout << setw(18) << 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";
313 std::string val;
314 for (auto& path : pathMap["inventory"][fan])
315 {
316 try
317 {
318 if (SDBusPlus::getProperty<bool>(path, interfaces["Item"],
319 property))
320 {
321 val = "true";
322 }
323 else
324 {
325 val = "false";
326 }
327 }
Patrick Williamsddb773b2021-10-06 11:24:49 -0500328 catch (const phosphor::fan::util::DBusPropertyError&)
Mike Capps385b1982021-06-28 10:40:42 -0400329 {
330 val = "Unknown";
331 }
332 cout << val;
333 }
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";
339 for (auto& path : pathMap["opstatus"][fan])
340 {
341 try
342 {
343 if (SDBusPlus::getProperty<bool>(path, interfaces["OpStatus"],
344 property))
345 {
346 val = "true";
347 }
348 else
349 {
350 val = "false";
351 }
352 }
Patrick Williamsddb773b2021-10-06 11:24:49 -0500353 catch (const phosphor::fan::util::DBusPropertyError&)
Mike Capps385b1982021-06-28 10:40:42 -0400354 {
355 val = "Unknown";
356 }
357 cout << val;
358 }
359
360 cout << endl;
361 }
362}
363
364/**
Mike Capps530c6552021-06-29 09:50:38 -0400365 * @function print target RPM/PWM and tach readings from each fan
366 */
367void get()
368{
369 using std::cout;
370 using std::endl;
371 using std::setw;
372
373 auto busData = loadDBusData();
374
Mike Capps16aab352021-06-30 10:17:21 -0400375 auto& fanNames{std::get<FAN_NAMES>(busData)};
376 auto& pathMap{std::get<PATH_MAP>(busData)};
377 auto& interfaces{std::get<IFACES>(busData)};
378 auto& method = std::get<METHOD>(busData);
Mike Capps530c6552021-06-29 09:50:38 -0400379
380 std::string property;
381
382 // print the header
383 cout << "TARGET SENSOR" << setw(11) << "TARGET(" << method
Mike Cappsff968232021-06-30 18:24:39 -0400384 << ") FEEDBACK SENSOR FEEDBACK(RPM)" << endl;
Mike Capps530c6552021-06-29 09:50:38 -0400385 cout << "==============================================================="
386 << endl;
387
388 for (auto& fan : fanNames)
389 {
390 if (pathMap["tach"][fan].size() == 0)
391 continue;
392 // print just the sensor name
393 auto shortPath = pathMap["tach"][fan][0];
394 shortPath = justFanName(shortPath);
Mike Cappsff968232021-06-30 18:24:39 -0400395 cout << shortPath << setw(18);
Mike Capps530c6552021-06-29 09:50:38 -0400396
397 // print its target RPM/PWM
398 property = "Target";
399 cout << SDBusPlus::getProperty<uint64_t>(
400 pathMap["tach"][fan][0],
401 interfaces[ifaceTypeFromMethod(method)], property)
402 << setw(12) << " ";
403
404 // print readings for each rotor
405 property = "Value";
406
407 auto indent = 0U;
408 for (auto& path : pathMap["tach"][fan])
409 {
410 cout << setw(indent);
Mike Cappsff968232021-06-30 18:24:39 -0400411 cout << justFanName(path) << setw(16)
Mike Capps530c6552021-06-29 09:50:38 -0400412 << SDBusPlus::getProperty<double>(
413 path, interfaces["SensorValue"], property)
414 << endl;
415
416 if (0 == indent)
Mike Cappsff968232021-06-30 18:24:39 -0400417 indent = 42;
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/**
Mike Capps7c8b97f2021-10-06 14:04:18 -0400531 * @function dump the FlightRecorder log data
532 */
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;
549
550 do
551 {
552 // wait for file to be detected
553 sleep(1);
554
555 if (fs::exists(dumpFile))
556 {
557 try
558 {
559 auto unused{nlohmann::json::parse(std::ifstream{dumpFile})};
560 done = true;
561 }
562 catch (...)
563 {
564 // TODO: maybe have a max-retries counter and fail after N
565 // tries
566 }
567 }
568 } while (!done);
569
Matt Spinlerb564e152021-10-29 13:10:18 -0500570 std::cout << "Fan control dump written to: " << dumpFile << std::endl;
Mike Capps7c8b97f2021-10-06 14:04:18 -0400571 }
572 catch (const phosphor::fan::util::DBusPropertyError& e)
573 {
Matt Spinlerb5c21a22021-10-14 16:52:12 -0500574 std::cerr << "Unable to dump fan control: " << e.what() << std::endl;
Mike Capps7c8b97f2021-10-06 14:04:18 -0400575 }
576}
577
578/**
Matt Spinlerb564e152021-10-29 13:10:18 -0500579 * @function Query items in the dump file
580 */
581void queryDumpFile(const DumpQuery& dq)
582{
583 nlohmann::json output;
584 std::ifstream file{dumpFile};
585
586 if (!file.good())
587 {
588 std::cerr << "Unable to open dump file, please run 'fanctl dump'.\n";
589 return;
590 }
591
592 auto dumpData = nlohmann::json::parse(file);
593
594 if (!dumpData.contains(dq.section))
595 {
596 std::cerr << "Error: Dump file does not contain " << dq.section
597 << " section"
598 << "\n";
599 return;
600 }
601
602 const auto& section = dumpData.at(dq.section);
603
Matt Spinler69f19ff2021-11-04 09:28:25 -0500604 if (section.is_array())
605 {
606 for (const auto& entry : section)
607 {
608 if (!entry.is_string() || dq.name.empty() ||
609 (entry.get<std::string>().find(dq.name) != std::string::npos))
610 {
611 output[dq.section].push_back(entry);
612 }
613 }
614 std::cout << std::setw(4) << output << "\n";
615 return;
616 }
617
Matt Spinlerb564e152021-10-29 13:10:18 -0500618 for (const auto& [key1, values1] : section.items())
619 {
620 if (dq.name.empty() || (key1.find(dq.name) != std::string::npos))
621 {
622 // If no properties specified, print the whole JSON value
623 if (dq.properties.empty())
624 {
625 output[key1] = values1;
626 continue;
627 }
628
629 // Look for properties both one and two levels down.
630 // Future improvement: Use recursion.
631 for (const auto& [key2, values2] : values1.items())
632 {
633 for (const auto& prop : dq.properties)
634 {
635 if (prop == key2)
636 {
637 output[key1][prop] = values2;
638 }
639 }
640
641 for (const auto& [key3, values3] : values2.items())
642 {
643 for (const auto& prop : dq.properties)
644 {
645 if (prop == key3)
646 {
647 output[key1][prop] = values3;
648 }
649 }
650 }
651 }
652 }
653 }
654
655 if (!output.empty())
656 {
657 std::cout << std::setw(4) << output << "\n";
658 }
659}
660
661/**
Mike Cappsff968232021-06-30 18:24:39 -0400662 * @function setup the CLI object to accept all options
663 */
Matt Spinlerb564e152021-10-29 13:10:18 -0500664void initCLI(CLI::App& app, uint64_t& target, std::vector<std::string>& fanList,
665 DumpQuery& dq)
Mike Cappsff968232021-06-30 18:24:39 -0400666{
667 app.set_help_flag("-h,--help", "Print this help page and exit.");
668
669 // App requires only 1 subcommand to be given
670 app.require_subcommand(1);
671
672 // This represents the command given
673 auto commands = app.add_option_group("Commands");
674
675 // status method
676 std::string strHelp("Prints fan target/tach readings, present/functional "
677 "states, and fan-monitor/BMC/Power service status");
Mike Capps49766182021-09-27 22:32:46 -0400678
Mike Cappsff968232021-06-30 18:24:39 -0400679 auto cmdStatus = commands->add_subcommand("status", strHelp);
680 cmdStatus->set_help_flag("-h, --help", strHelp);
681 cmdStatus->require_option(0);
682
683 // get method
684 strHelp = "Get the current fan target and feedback speeds for all rotors";
685 auto cmdGet = commands->add_subcommand("get", strHelp);
686 cmdGet->set_help_flag("-h, --help", strHelp);
687 cmdGet->require_option(0);
688
689 // set method
690 strHelp = "Set target (all rotors) for one-or-more fans";
691 auto cmdSet = commands->add_subcommand("set", strHelp);
692 strHelp = R"(set <TARGET> [TARGET SENSOR(S)]
693 <TARGET>
694 - RPM/PWM target to set the fans
695[TARGET SENSOR LIST]
696- list of target sensors to set)";
697 cmdSet->set_help_flag("-h, --help", strHelp);
698 cmdSet->add_option("target", target, "RPM/PWM target to set the fans");
699 cmdSet->add_option(
700 "fan list", fanList,
701 "[optional] list of 1+ fans to set target RPM/PWM (default: all)");
702 cmdSet->require_option();
703
Matthew Barthad3b6b52021-10-26 15:38:15 -0500704#ifdef CONTROL_USE_JSON
Mike Cappsff968232021-06-30 18:24:39 -0400705 strHelp = "Reload phosphor-fan configuration files";
706 auto cmdReload = commands->add_subcommand("reload", strHelp);
707 cmdReload->set_help_flag("-h, --help", strHelp);
708 cmdReload->require_option(0);
Matthew Barthad3b6b52021-10-26 15:38:15 -0500709#endif
Mike Cappsff968232021-06-30 18:24:39 -0400710
711 strHelp = "Resume running phosphor-fan-control";
712 auto cmdResume = commands->add_subcommand("resume", strHelp);
713 cmdResume->set_help_flag("-h, --help", strHelp);
714 cmdResume->require_option(0);
Mike Capps7c8b97f2021-10-06 14:04:18 -0400715
Matthew Barth1a4dcef2021-10-26 15:35:48 -0500716#ifdef CONTROL_USE_JSON
Mike Capps7c8b97f2021-10-06 14:04:18 -0400717 // Dump method
718 auto cmdDump = commands->add_subcommand(
719 "dump", "Dump the FlightRecorder diagnostic log");
720 cmdDump->set_help_flag("-h, --help",
721 "Dump the FlightRecorder diagnostic log");
722 cmdDump->require_option(0);
Matt Spinlerb564e152021-10-29 13:10:18 -0500723
Matthew Barth1a4dcef2021-10-26 15:35:48 -0500724 // Query dump
Matt Spinlerb564e152021-10-29 13:10:18 -0500725 auto cmdDumpQuery =
726 commands->add_subcommand("query_dump", "Query the dump file");
727
728 cmdDumpQuery->set_help_flag("-h, --help", "Query the dump file");
729 cmdDumpQuery
730 ->add_option("-s, --section", dq.section, "Dump file section name")
731 ->required();
732 cmdDumpQuery->add_option("-n, --name", dq.name,
733 "Optional dump file entry name (or substring)");
734 cmdDumpQuery->add_option("-p, --properties", dq.properties,
735 "Optional list of dump file property names");
736#endif
Mike Cappsff968232021-06-30 18:24:39 -0400737}
738
739/**
Mike Capps385b1982021-06-28 10:40:42 -0400740 * @function main entry point for the application
741 */
742int main(int argc, char* argv[])
743{
744 auto rc = 0;
Mike Capps16aab352021-06-30 10:17:21 -0400745 uint64_t target{0U};
746 std::vector<std::string> fanList;
Matt Spinlerb564e152021-10-29 13:10:18 -0500747 DumpQuery dq;
Mike Capps385b1982021-06-28 10:40:42 -0400748
749 try
750 {
Mike Capps49766182021-09-27 22:32:46 -0400751 CLI::App app{"Manually control, get fan tachs, view status, and resume "
752 "automatic control of all fans within a chassis. Full "
753 "documentation can be found at the readme:\n"
754 "https://github.com/openbmc/phosphor-fan-presence/tree/"
755 "master/docs/control/fanctl"};
Mike Capps385b1982021-06-28 10:40:42 -0400756
Matt Spinlerb564e152021-10-29 13:10:18 -0500757 initCLI(app, target, fanList, dq);
Mike Capps16aab352021-06-30 10:17:21 -0400758
Mike Capps385b1982021-06-28 10:40:42 -0400759 CLI11_PARSE(app, argc, argv);
760
Mike Cappsff968232021-06-30 18:24:39 -0400761 if (app.got_subcommand("get"))
Mike Capps385b1982021-06-28 10:40:42 -0400762 {
Mike Cappsff968232021-06-30 18:24:39 -0400763 get();
Mike Capps385b1982021-06-28 10:40:42 -0400764 }
Mike Capps16aab352021-06-30 10:17:21 -0400765 else if (app.got_subcommand("set"))
766 {
767 set(target, fanList);
768 }
Matthew Barthad3b6b52021-10-26 15:38:15 -0500769#ifdef CONTROL_USE_JSON
Mike Cappsff968232021-06-30 18:24:39 -0400770 else if (app.got_subcommand("reload"))
Mike Capps530c6552021-06-29 09:50:38 -0400771 {
Mike Cappsff968232021-06-30 18:24:39 -0400772 reload();
Mike Capps530c6552021-06-29 09:50:38 -0400773 }
Matthew Barthad3b6b52021-10-26 15:38:15 -0500774#endif
Mike Capps16aab352021-06-30 10:17:21 -0400775 else if (app.got_subcommand("resume"))
776 {
777 resume();
778 }
Mike Cappsff968232021-06-30 18:24:39 -0400779 else if (app.got_subcommand("status"))
780 {
781 status();
782 }
Mike Capps7c8b97f2021-10-06 14:04:18 -0400783 else if (app.got_subcommand("dump"))
784 {
Mike Capps28945d42022-01-27 11:22:40 -0500785#ifdef CONTROL_USE_JSON
Matt Spinlerb5c21a22021-10-14 16:52:12 -0500786 dumpFanControl();
Mike Capps28945d42022-01-27 11:22:40 -0500787#else
788 std::ofstream(dumpFile)
789 << "{\n\"msg\": \"Unable to create dump on "
790 "non-JSON config based system\"\n}";
791#endif
Mike Capps7c8b97f2021-10-06 14:04:18 -0400792 }
Mike Capps28945d42022-01-27 11:22:40 -0500793#ifdef CONTROL_USE_JSON
Matt Spinlerb564e152021-10-29 13:10:18 -0500794 else if (app.got_subcommand("query_dump"))
795 {
796 queryDumpFile(dq);
797 }
798#endif
Mike Capps385b1982021-06-28 10:40:42 -0400799 }
800 catch (const std::exception& e)
801 {
802 rc = -1;
803 std::cerr << argv[0] << " failed: " << e.what() << std::endl;
804 }
805
806 return rc;
807}