blob: f5e77f239778d3c6a8804fa4e492c6a643134107 [file] [log] [blame]
Mike Capps385b1982021-06-28 10:40:42 -04001/**
2 * Copyright © 2021 IBM Corporation
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include "sdbusplus.hpp"
18
19#include <CLI/CLI.hpp>
20#include <sdbusplus/bus.hpp>
21
22#include <iomanip>
23#include <iostream>
24
25using SDBusPlus = phosphor::fan::util::SDBusPlus;
26
27constexpr auto phosphorServiceName = "phosphor-fan-control@0.service";
28constexpr auto systemdMgrIface = "org.freedesktop.systemd1.Manager";
29constexpr auto systemdPath = "/org/freedesktop/systemd1";
30constexpr auto systemdService = "org.freedesktop.systemd1";
31
32std::map<std::string, std::string> interfaces{
33 {"FanSpeed", "xyz.openbmc_project.Control.FanSpeed"},
34 {"FanPwm", "xyz.openbmc_project.Control.FanPwm"},
35 {"SensorValue", "xyz.openbmc_project.Sensor.Value"},
36 {"Item", "xyz.openbmc_project.Inventory.Item"},
37 {"OpStatus", "xyz.openbmc_project.State.Decorator.OperationalStatus"}};
38
39std::map<std::string, std::string> paths{
40 {"motherboard",
41 "/xyz/openbmc_project/inventory/system/chassis/motherboard"},
42 {"tach", "/xyz/openbmc_project/sensors/fan_tach"}};
43
44// paths by D-bus interface,fan name
45std::map<std::string, std::map<std::string, std::vector<std::string>>> pathMap;
46
47/**
48 * @function extracts fan name from dbus path string (last token where
49 * delimiter is the / character), with proper bounds checking.
50 * @param[in] path - D-Bus path
51 * @return just the fan name.
52 */
53
54std::string justFanName(std::string const& path)
55{
56 std::string fanName;
57
58 auto itr = path.rfind("/");
59 if (itr != std::string::npos && itr < path.size())
60 {
61 fanName = path.substr(1 + itr);
62 }
63
64 return fanName;
65}
66
67/**
68 * @function produces subtree paths whose names match fan token names.
69 * @param[in] path - D-Bus path to obtain subtree from
70 * @param[in] iface - interface to obtain subTreePaths from
71 * @param[in] fans - label matching tokens to filter by
72 * @param[in] shortPath - flag to shorten fan token
73 * @return map of paths by fan name
74 */
75
76std::map<std::string, std::vector<std::string>>
77 getPathsFromIface(const std::string& path, const std::string& iface,
78 const std::vector<std::string>& fans,
79 bool shortPath = false)
80{
81 std::map<std::string, std::vector<std::string>> dest;
82
83 for (auto& path :
84 SDBusPlus::getSubTreePathsRaw(SDBusPlus::getBus(), path, iface, 0))
85 {
86 for (auto& fan : fans)
87 {
88 if (shortPath)
89 {
90 if (fan == justFanName(path))
91 {
92 dest[fan].push_back(path);
93 }
94 }
95 else if (std::string::npos != path.find(fan + "_"))
96 {
97 dest[fan].push_back(path);
98 }
99 }
100 }
101
102 return dest;
103}
104
105/**
106 * @function gets the states of phosphor-fanctl. equivalent to
107 * "systemctl status phosphor-fan-control@0"
108 * @return a list of several (sub)states of fanctl (loaded,
109 * active, running) as well as D-Bus properties representing
110 * BMC states (bmc state,chassis power state, host state)
111 */
112
113std::array<std::string, 6> getStates()
114{
115 using DBusTuple =
116 std::tuple<std::string, std::string, std::string, std::string,
117 std::string, std::string, sdbusplus::message::object_path,
118 uint32_t, std::string, sdbusplus::message::object_path>;
119
120 std::array<std::string, 6> ret;
121
122 std::vector<std::string> services{phosphorServiceName};
123
124 try
125 {
126 auto fields{SDBusPlus::callMethodAndRead<std::vector<DBusTuple>>(
127 systemdService, systemdPath, systemdMgrIface, "ListUnitsByNames",
128 services)};
129
130 if (fields.size() > 0)
131 {
132 ret[0] = std::get<2>(fields[0]);
133 ret[1] = std::get<3>(fields[0]);
134 ret[2] = std::get<4>(fields[0]);
135 }
136 else
137 {
138 std::cout << "No units found for systemd service: " << services[0]
139 << std::endl;
140 }
141 }
142 catch (std::exception& e)
143 {
144 std::cerr << "Failure retrieving phosphor-fan-control states: "
145 << e.what() << std::endl;
146 }
147
148 std::string path("/xyz/openbmc_project/state/bmc0");
149 std::string iface("xyz.openbmc_project.State.BMC");
150 ret[3] =
151 SDBusPlus::getProperty<std::string>(path, iface, "CurrentBMCState");
152
153 path = "/xyz/openbmc_project/state/chassis0";
154 iface = "xyz.openbmc_project.State.Chassis";
155 ret[4] =
156 SDBusPlus::getProperty<std::string>(path, iface, "CurrentPowerState");
157
158 path = "/xyz/openbmc_project/state/host0";
159 iface = "xyz.openbmc_project.State.Host";
160 ret[5] =
161 SDBusPlus::getProperty<std::string>(path, iface, "CurrentHostState");
162
163 return ret;
164}
165
166/**
167 * @function performs the "status" command from the cmdline.
168 * get states and sensor data and output to the console
169 */
170void status()
171{
172 using std::cout;
173 using std::endl;
174 using std::setw;
175
176 auto& bus{SDBusPlus::getBus()};
177
178 std::string tmethod("RPM"), fmethod("RPMS"), property;
179
180 std::vector<std::string> sensorPaths, fanNames;
181
182 // build a list of all fans
183 for (auto& path : SDBusPlus::getSubTreePathsRaw(bus, paths["tach"],
184 interfaces["FanSpeed"], 0))
185 {
186 // special case where we build the list of fans
187 auto fan = justFanName(path);
188 fan = fan.substr(0, fan.rfind("_"));
189 fanNames.push_back(fan);
190 }
191
192 // retry if none found
193 if (0 == fanNames.size())
194 {
195 tmethod = "PWM";
196 fmethod = "PWM";
197
198 for (auto& path : SDBusPlus::getSubTreePathsRaw(
199 bus, paths["tach"], interfaces["FanPwm"], 0))
200 {
201 auto fan = justFanName(path);
202 fan = fan.substr(0, fan.rfind("_"));
203 fanNames.push_back(fan);
204 }
205 }
206
207 // load tach sensor paths for each fan
208 pathMap["tach"] =
209 getPathsFromIface(paths["tach"], interfaces["SensorValue"], fanNames);
210
211 // load speed sensor paths for each fan
212 pathMap["speed"] = pathMap["tach"];
213
214 // load inventory Item data for each fan
215 pathMap["inventory"] = getPathsFromIface(
216 paths["motherboard"], interfaces["Item"], fanNames, true);
217
218 // load operational status data for each fan
219 pathMap["opstatus"] = getPathsFromIface(
220 paths["motherboard"], interfaces["OpStatus"], fanNames, true);
221
222 // get the state,substate of fan-control and obmc
223 auto states = getStates();
224
225 // print the header
226 cout << "Fan Control Service State : " << states[0] << ", " << states[1]
227 << "(" << states[2] << ")" << endl;
228 cout << endl;
229 cout << "CurrentBMCState : " << states[3] << endl;
230 cout << "CurrentPowerState : " << states[4] << endl;
231 cout << "CurrentHostState : " << states[5] << endl;
232 cout << endl;
233 cout << " FAN "
234 << "TARGET(" << tmethod << ") FEEDBACKS(RPMS) PRESENT"
235 << " FUNCTIONAL" << endl;
236 cout << "==============================================================="
237 << endl;
238
239 for (auto& fan : fanNames)
240 {
241 cout << " " << fan << setw(18);
242
243 // get the target RPM
244 property = "Target";
245 cout << SDBusPlus::getProperty<uint64_t>(
246 pathMap["speed"][fan][0], interfaces["FanSpeed"], property)
247 << setw(11);
248
249 // get the sensor RPM
250 property = "Value";
251 int numRotors = pathMap["tach"][fan].size();
252 // print tach readings for each rotor
253 for (auto& path : pathMap["tach"][fan])
254 {
255 cout << SDBusPlus::getProperty<double>(
256 path, interfaces["SensorValue"], property);
257
258 // dont print slash on last rotor
259 if (--numRotors)
260 cout << "/";
261 }
262 cout << setw(10);
263
264 // get the present property
265 property = "Present";
266 std::string val;
267 for (auto& path : pathMap["inventory"][fan])
268 {
269 try
270 {
271 if (SDBusPlus::getProperty<bool>(path, interfaces["Item"],
272 property))
273 {
274 val = "true";
275 }
276 else
277 {
278 val = "false";
279 }
280 }
281 catch (phosphor::fan::util::DBusPropertyError&)
282 {
283 val = "Unknown";
284 }
285 cout << val;
286 }
287
288 cout << setw(13);
289
290 // get the functional property
291 property = "Functional";
292 for (auto& path : pathMap["opstatus"][fan])
293 {
294 try
295 {
296 if (SDBusPlus::getProperty<bool>(path, interfaces["OpStatus"],
297 property))
298 {
299 val = "true";
300 }
301 else
302 {
303 val = "false";
304 }
305 }
306 catch (phosphor::fan::util::DBusPropertyError&)
307 {
308 val = "Unknown";
309 }
310 cout << val;
311 }
312
313 cout << endl;
314 }
315}
316
317/**
318 * @function main entry point for the application
319 */
320int main(int argc, char* argv[])
321{
322 auto rc = 0;
323
324 try
325 {
326 CLI::App app{R"(Manually control, get fan tachs, view status, and resume
327 automatic control of all fans within a chassis.)"};
328
329 app.set_help_flag("-h,--help", "Print this help page and exit.");
330
331 // App requires only 1 subcommand to be given
332 app.require_subcommand(1);
333
334 // This represents the command given
335 auto commands = app.add_option_group("Commands");
336
337 // Configure method
338 auto cmdStatus = commands->add_subcommand(
339 "status",
340 "Get the fan tach targets/values and fan-control service status");
341
342 cmdStatus->set_help_flag(
343 "-h, --help", "Prints fan target/tach readings, present/functional "
344 "states, and fan-monitor/BMC/Power service status");
345 cmdStatus->require_option(0);
346
347 CLI11_PARSE(app, argc, argv);
348
349 if (app.got_subcommand("status"))
350 {
351 status();
352 }
353 }
354 catch (const std::exception& e)
355 {
356 rc = -1;
357 std::cerr << argv[0] << " failed: " << e.what() << std::endl;
358 }
359
360 return rc;
361}