1 #include "occ_status.hpp"
2 
3 #include "occ_sensor.hpp"
4 #include "powermode.hpp"
5 #include "utils.hpp"
6 
7 #include <fmt/core.h>
8 
9 #ifdef POWER10
10 #include <com/ibm/Host/Target/server.hpp>
11 #endif
12 #include <phosphor-logging/log.hpp>
13 
14 namespace open_power
15 {
16 namespace occ
17 {
18 
19 using namespace phosphor::logging;
20 
21 // Handles updates to occActive property
22 bool Status::occActive(bool value)
23 {
24     if (value != this->occActive())
25     {
26         log<level::INFO>(fmt::format("Status::occActive OCC{} changed to {}",
27                                      instance, value)
28                              .c_str());
29         if (value)
30         {
31             // Bind the device
32             device.bind();
33 
34             // Start watching for errors
35             addErrorWatch();
36 
37             // Reset last OCC state
38             lastState = 0;
39 
40             // Call into Manager to let know that we have bound
41             if (this->callBack)
42             {
43                 this->callBack(value);
44             }
45         }
46         else
47         {
48             // Call into Manager to let know that we will unbind.
49             if (this->callBack)
50             {
51                 this->callBack(value);
52             }
53 
54             // Stop watching for errors
55             removeErrorWatch();
56 
57             // Do the unbind.
58             device.unBind();
59         }
60     }
61     else if (value && !device.bound())
62     {
63         // Existing error watch is on a dead file descriptor.
64         removeErrorWatch();
65 
66         /*
67          * In it's constructor, Status checks Device::bound() to see if OCC is
68          * active or not.
69          * Device::bound() checks for occX-dev0 directory.
70          * We will lose occX-dev0 directories during FSI rescan.
71          * So, if we start this application (and construct Status), and then
72          * later do FSI rescan, we will end up with occActive = true and device
73          * NOT bound. Lets correct that situation here.
74          */
75         device.bind();
76 
77         // Add error watch again
78         addErrorWatch();
79     }
80     else if (!value && device.bound())
81     {
82         removeErrorWatch();
83 
84         // In the event that the application never receives the active signal
85         // even though the OCC is active (this can occur if the BMC is rebooted
86         // with the host on, since the initial OCC driver probe will discover
87         // the OCCs), this application needs to be able to unbind the device
88         // when we get the OCC inactive signal.
89         device.unBind();
90     }
91     return Base::Status::occActive(value);
92 }
93 
94 // Callback handler when a device error is reported.
95 void Status::deviceErrorHandler(bool error)
96 {
97     // Make sure we have an error
98     if (error)
99     {
100         // This would deem OCC inactive
101         this->occActive(false);
102 
103         // Reset the OCC
104         this->resetOCC();
105     }
106 }
107 
108 // Sends message to host control command handler to reset OCC
109 void Status::resetOCC()
110 {
111     log<level::INFO>(
112         fmt::format(">>Status::resetOCC() - requesting reset for OCC{}",
113                     instance)
114             .c_str());
115 #ifdef PLDM
116     if (resetCallBack)
117     {
118         this->resetCallBack(instance);
119     }
120 #else
121     constexpr auto CONTROL_HOST_PATH = "/org/open_power/control/host0";
122     constexpr auto CONTROL_HOST_INTF = "org.open_power.Control.Host";
123 
124     // This will throw exception on failure
125     auto service = utils::getService(CONTROL_HOST_PATH, CONTROL_HOST_INTF);
126 
127     auto& bus = utils::getBus();
128     auto method = bus.new_method_call(service.c_str(), CONTROL_HOST_PATH,
129                                       CONTROL_HOST_INTF, "Execute");
130     // OCC Reset control command
131     method.append(convertForMessage(Control::Host::Command::OCCReset).c_str());
132 
133     // OCC Sensor ID for callout reasons
134     method.append(std::variant<uint8_t>(std::get<0>(sensorMap.at(instance))));
135     bus.call_noreply(method);
136     return;
137 #endif
138 }
139 
140 // Handler called by Host control command handler to convey the
141 // status of the executed command
142 void Status::hostControlEvent(sdbusplus::message::message& msg)
143 {
144     std::string cmdCompleted{};
145     std::string cmdStatus{};
146 
147     msg.read(cmdCompleted, cmdStatus);
148 
149     log<level::DEBUG>("Host control signal values",
150                       entry("COMMAND=%s", cmdCompleted.c_str()),
151                       entry("STATUS=%s", cmdStatus.c_str()));
152 
153     if (Control::Host::convertResultFromString(cmdStatus) !=
154         Control::Host::Result::Success)
155     {
156         if (Control::Host::convertCommandFromString(cmdCompleted) ==
157             Control::Host::Command::OCCReset)
158         {
159             // Must be a Timeout. Log an Error trace
160             log<level::ERR>(
161                 "Error resetting the OCC.", entry("PATH=%s", path.c_str()),
162                 entry("SENSORID=0x%X", std::get<0>(sensorMap.at(instance))));
163         }
164     }
165     return;
166 }
167 
168 void Status::readOccState()
169 {
170     unsigned int state;
171     const fs::path filename =
172         fs::path(DEV_PATH) /
173         fs::path(sysfsName + "." + std::to_string(instance + 1)) / "occ_state";
174 
175     log<level::DEBUG>(
176         fmt::format("Status::readOccState: reading OCC{} state from {}",
177                     instance, filename.c_str())
178             .c_str());
179 
180     std::ifstream file(filename, std::ios::in);
181     const int open_errno = errno;
182     if (file)
183     {
184         file >> state;
185         if (state != lastState)
186         {
187             // Trace OCC state changes
188             log<level::INFO>(
189                 fmt::format("Status::readOccState: OCC{} state 0x{:02X}",
190                             instance, state)
191                     .c_str());
192             lastState = state;
193 
194 #ifdef POWER10
195             if ((OccState(state) == OccState::ACTIVE) && (device.master()))
196             {
197                 // Kernel detected that the master OCC went to active state
198                 occsWentActive();
199             }
200 #endif
201         }
202         file.close();
203     }
204     else
205     {
206         // If not able to read, OCC may be offline
207         log<level::DEBUG>(
208             fmt::format("Status::readOccState: open failed (errno={})",
209                         open_errno)
210                 .c_str());
211         lastState = 0;
212     }
213 }
214 
215 #ifdef POWER10
216 // Check if Hypervisor target is PowerVM
217 bool Status::isPowerVM()
218 {
219     using namespace open_power::occ::powermode;
220     namespace Hyper = sdbusplus::com::ibm::Host::server;
221     constexpr auto HYPE_PATH = "/com/ibm/host0/hypervisor";
222     constexpr auto HYPE_INTERFACE = "com.ibm.Host.Target";
223     constexpr auto HYPE_PROP = "Target";
224 
225     bool powerVmTarget = false;
226 
227     // This will throw exception on failure
228     auto& bus = utils::getBus();
229     auto service = utils::getService(HYPE_PATH, HYPE_INTERFACE);
230     auto method = bus.new_method_call(service.c_str(), HYPE_PATH,
231                                       "org.freedesktop.DBus.Properties", "Get");
232     method.append(HYPE_INTERFACE, HYPE_PROP);
233     auto reply = bus.call(method);
234 
235     std::variant<std::string> hyperEntryValue;
236     reply.read(hyperEntryValue);
237     auto propVal = std::get<std::string>(hyperEntryValue);
238     if (Hyper::Target::convertHypervisorFromString(propVal) ==
239         Hyper::Target::Hypervisor::PowerVM)
240     {
241         powerVmTarget = true;
242     }
243 
244     log<level::DEBUG>(
245         fmt::format("Status::isPowerVM returning {}", powerVmTarget).c_str());
246 
247     return powerVmTarget;
248 }
249 
250 // Get the requested power mode
251 SysPwrMode Status::getMode()
252 {
253     using namespace open_power::occ::powermode;
254     SysPwrMode pmode = SysPwrMode::NO_CHANGE;
255 
256     // This will throw exception on failure
257     auto& bus = utils::getBus();
258     auto service = utils::getService(PMODE_PATH, PMODE_INTERFACE);
259     auto method = bus.new_method_call(service.c_str(), PMODE_PATH,
260                                       "org.freedesktop.DBus.Properties", "Get");
261     method.append(PMODE_INTERFACE, POWER_MODE_PROP);
262     auto reply = bus.call(method);
263 
264     std::variant<std::string> stateEntryValue;
265     reply.read(stateEntryValue);
266     auto propVal = std::get<std::string>(stateEntryValue);
267     pmode = powermode::convertStringToMode(propVal);
268 
269     log<level::DEBUG>(
270         fmt::format("Status::getMode returning {}", pmode).c_str());
271 
272     return pmode;
273 }
274 
275 // Special processing that needs to happen once the OCCs change to ACTIVE state
276 void Status::occsWentActive()
277 {
278     CmdStatus status = CmdStatus::SUCCESS;
279 
280     status = sendModeChange();
281     if (status != CmdStatus::SUCCESS)
282     {
283         log<level::ERR>(
284             fmt::format(
285                 "Status::occsWentActive: OCC mode change failed with status {}",
286                 status)
287                 .c_str());
288     }
289 
290     status = sendIpsData();
291     if (status != CmdStatus::SUCCESS)
292     {
293         log<level::ERR>(
294             fmt::format(
295                 "Status::occsWentActive: Sending Idle Power Save Config data failed with status {}",
296                 status)
297                 .c_str());
298     }
299 }
300 
301 // Send mode change request to the master OCC
302 CmdStatus Status::sendModeChange()
303 {
304     CmdStatus status = CmdStatus::FAILURE;
305 
306     if (!device.master())
307     {
308         log<level::ERR>(
309             fmt::format(
310                 "Status::sendModeChange: MODE CHANGE does not get sent to slave OCC{}",
311                 instance)
312                 .c_str());
313         return status;
314     }
315     if (!isPowerVM())
316     {
317         // Mode change is only supported on PowerVM systems
318         log<level::DEBUG>(
319             "Status::sendModeChange: MODE CHANGE does not get sent on non-PowerVM systems");
320         return CmdStatus::SUCCESS;
321     }
322 
323     const SysPwrMode newMode = getMode();
324 
325     if (VALID_POWER_MODE_SETTING(newMode))
326     {
327         std::vector<std::uint8_t> cmd, rsp;
328         cmd.push_back(uint8_t(CmdType::SET_MODE_AND_STATE));
329         cmd.push_back(0x00); // Data Length (2 bytes)
330         cmd.push_back(0x06);
331         cmd.push_back(0x30); // Data (Version)
332         cmd.push_back(uint8_t(OccState::NO_CHANGE));
333         cmd.push_back(uint8_t(newMode));
334         cmd.push_back(0x00); // Mode Data (Freq Point)
335         cmd.push_back(0x00); //
336         cmd.push_back(0x00); // reserved
337         log<level::INFO>(
338             fmt::format(
339                 "Status::sendModeChange: SET_MODE({}) command to OCC{} ({} bytes)",
340                 newMode, instance, cmd.size())
341                 .c_str());
342         status = occCmd.send(cmd, rsp);
343         if (status == CmdStatus::SUCCESS)
344         {
345             if (rsp.size() == 5)
346             {
347                 if (RspStatus::SUCCESS == RspStatus(rsp[2]))
348                 {
349                     log<level::DEBUG>(
350                         "Status::sendModeChange: - Mode change completed successfully");
351                 }
352                 else
353                 {
354                     log<level::ERR>(
355                         fmt::format(
356                             "Status::sendModeChange: SET MODE failed with status 0x{:02X}",
357                             rsp[2])
358                             .c_str());
359                 }
360             }
361             else
362             {
363                 log<level::ERR>(
364                     "Status::sendModeChange: INVALID SET MODE response");
365                 dump_hex(rsp);
366             }
367         }
368         else
369         {
370             if (status == CmdStatus::OPEN_FAILURE)
371             {
372                 log<level::WARNING>(
373                     "Status::sendModeChange: OCC not active yet");
374             }
375             else
376             {
377                 log<level::ERR>("Status::sendModeChange: SET_MODE FAILED!");
378             }
379         }
380     }
381     else
382     {
383         log<level::ERR>(
384             fmt::format(
385                 "Status::sendModeChange: Unable to set power mode to {}",
386                 newMode)
387                 .c_str());
388     }
389 
390     return status;
391 }
392 
393 // Send Idle Power Saver config data to the master OCC
394 CmdStatus Status::sendIpsData()
395 {
396     CmdStatus status = CmdStatus::FAILURE;
397 
398     if (!device.master())
399     {
400         log<level::ERR>(
401             fmt::format(
402                 "Status::sendIpsData: SET_CFG_DATA[IPS] does not get sent to slave OCC{}",
403                 instance)
404                 .c_str());
405         return status;
406     }
407     if (!isPowerVM())
408     {
409         // Idle Power Saver data is only supported on PowerVM systems
410         log<level::DEBUG>(
411             "Status::sendIpsData: SET_CFG_DATA[IPS] does not get sent on non-PowerVM systems");
412         return CmdStatus::SUCCESS;
413     }
414 
415     std::vector<std::uint8_t> cmd, rsp;
416     cmd.push_back(uint8_t(CmdType::SET_CONFIG_DATA));
417     cmd.push_back(0x00); // Data Length (2 bytes)
418     cmd.push_back(0x09);
419     // Data:
420     cmd.push_back(0x11); // Config Format: IPS Settings
421     cmd.push_back(0x00); // Version
422     cmd.push_back(0x00); // IPS Enable: disabled
423     cmd.push_back(0x00); // Enter Delay Time (240s)
424     cmd.push_back(0xF0); //
425     cmd.push_back(0x08); // Enter Utilization (8%)
426     cmd.push_back(0x00); // Exit Delay Time (10s)
427     cmd.push_back(0x0A); //
428     cmd.push_back(0x0C); // Exit Utilization (12%)
429     log<level::INFO>(
430         fmt::format(
431             "Status::sendIpsData: SET_CFG_DATA[IPS] command to OCC{} ({} bytes)",
432             instance, cmd.size())
433             .c_str());
434     status = occCmd.send(cmd, rsp);
435     if (status == CmdStatus::SUCCESS)
436     {
437         if (rsp.size() == 5)
438         {
439             if (RspStatus::SUCCESS == RspStatus(rsp[2]))
440             {
441                 log<level::DEBUG>(
442                     "Status::sendIpsData: - SET_CFG_DATA[IPS] completed successfully");
443             }
444             else
445             {
446                 log<level::ERR>(
447                     fmt::format(
448                         "Status::sendIpsData: SET_CFG_DATA[IPS] failed with status 0x{:02X}",
449                         rsp[2])
450                         .c_str());
451             }
452         }
453         else
454         {
455             log<level::ERR>(
456                 "Status::sendIpsData: INVALID SET_CFG_DATA[IPS] response");
457             dump_hex(rsp);
458         }
459     }
460     else
461     {
462         if (status == CmdStatus::OPEN_FAILURE)
463         {
464             log<level::WARNING>("Status::sendIpsData: OCC not active yet");
465         }
466         else
467         {
468             log<level::ERR>("Status::sendIpsData: SET_CFG_DATA[IPS] FAILED!");
469         }
470     }
471 
472     return status;
473 }
474 
475 #endif // POWER10
476 
477 } // namespace occ
478 } // namespace open_power
479