xref: /openbmc/openpower-occ-control/occ_status.cpp (revision 2f9f9bba661dcae2f0dd05ea6ddae9eb11a909d9)
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     std::ifstream file(filename, std::ios::in);
176     const int open_errno = errno;
177     if (file)
178     {
179         file >> state;
180         if (state != lastState)
181         {
182             // Trace OCC state changes
183             log<level::INFO>(
184                 fmt::format("Status::readOccState: OCC{} state 0x{:02X}",
185                             instance, state)
186                     .c_str());
187             lastState = state;
188 
189 #ifdef POWER10
190             if ((OccState(state) == OccState::ACTIVE) && (device.master()))
191             {
192                 // Kernel detected that the master OCC went to active state
193                 occsWentActive();
194             }
195 #endif
196         }
197         file.close();
198     }
199     else
200     {
201         // If not able to read, OCC may be offline
202         log<level::DEBUG>(
203             fmt::format("Status::readOccState: open failed (errno={})",
204                         open_errno)
205                 .c_str());
206         lastState = 0;
207     }
208 }
209 
210 #ifdef POWER10
211 // Check if Hypervisor target is PowerVM
212 bool Status::isPowerVM()
213 {
214     using namespace open_power::occ::powermode;
215     namespace Hyper = sdbusplus::com::ibm::Host::server;
216     constexpr auto HYPE_PATH = "/com/ibm/host0/hypervisor";
217     constexpr auto HYPE_INTERFACE = "com.ibm.Host.Target";
218     constexpr auto HYPE_PROP = "Target";
219 
220     bool powerVmTarget = false;
221 
222     // This will throw exception on failure
223     auto& bus = utils::getBus();
224     auto service = utils::getService(HYPE_PATH, HYPE_INTERFACE);
225     auto method = bus.new_method_call(service.c_str(), HYPE_PATH,
226                                       "org.freedesktop.DBus.Properties", "Get");
227     method.append(HYPE_INTERFACE, HYPE_PROP);
228     auto reply = bus.call(method);
229 
230     std::variant<std::string> hyperEntryValue;
231     reply.read(hyperEntryValue);
232     auto propVal = std::get<std::string>(hyperEntryValue);
233     if (Hyper::Target::convertHypervisorFromString(propVal) ==
234         Hyper::Target::Hypervisor::PowerVM)
235     {
236         powerVmTarget = true;
237     }
238 
239     log<level::DEBUG>(
240         fmt::format("Status::isPowerVM returning {}", powerVmTarget).c_str());
241 
242     return powerVmTarget;
243 }
244 
245 // Get the requested power mode
246 SysPwrMode Status::getMode()
247 {
248     using namespace open_power::occ::powermode;
249     SysPwrMode pmode = SysPwrMode::NO_CHANGE;
250 
251     // This will throw exception on failure
252     auto& bus = utils::getBus();
253     auto service = utils::getService(PMODE_PATH, PMODE_INTERFACE);
254     auto method = bus.new_method_call(service.c_str(), PMODE_PATH,
255                                       "org.freedesktop.DBus.Properties", "Get");
256     method.append(PMODE_INTERFACE, POWER_MODE_PROP);
257     auto reply = bus.call(method);
258 
259     std::variant<std::string> stateEntryValue;
260     reply.read(stateEntryValue);
261     auto propVal = std::get<std::string>(stateEntryValue);
262     pmode = powermode::convertStringToMode(propVal);
263 
264     log<level::DEBUG>(
265         fmt::format("Status::getMode returning {}", pmode).c_str());
266 
267     return pmode;
268 }
269 
270 // Get the requested power mode
271 bool Status::getIPSParms(uint8_t& enterUtil, uint16_t& enterTime,
272                          uint8_t& exitUtil, uint16_t& exitTime)
273 {
274     using namespace open_power::occ::powermode;
275     // Defaults:
276     bool ipsEnabled = false; // Disabled
277     enterUtil = 8;           // Enter Utilization (8%)
278     enterTime = 240;         // Enter Delay Time (240s)
279     exitUtil = 12;           // Exit Utilization (12%)
280     exitTime = 10;           // Exit Delay Time (10s)
281 
282     std::map<std::string, std::variant<bool, uint8_t, uint64_t>>
283         ipsProperties{};
284 
285     // Get all IPS properties from DBus
286     try
287     {
288         auto& bus = utils::getBus();
289         auto service = utils::getService(PIPS_PATH, PIPS_INTERFACE);
290         auto method =
291             bus.new_method_call(service.c_str(), PIPS_PATH,
292                                 "org.freedesktop.DBus.Properties", "GetAll");
293         method.append(PIPS_INTERFACE);
294         auto reply = bus.call(method);
295         reply.read(ipsProperties);
296     }
297     catch (const sdbusplus::exception::exception& e)
298     {
299         log<level::ERR>(
300             fmt::format(
301                 "Unable to read Idle Power Saver parameters so it will be disabled: {}",
302                 e.what())
303                 .c_str());
304         return ipsEnabled;
305     }
306 
307     auto ipsEntry = ipsProperties.find(IPS_ENABLED_PROP);
308     if (ipsEntry != ipsProperties.end())
309     {
310         ipsEnabled = std::get<bool>(ipsEntry->second);
311     }
312     else
313     {
314         log<level::ERR>(
315             fmt::format("Status::getIPSParms could not find property: {}",
316                         IPS_ENABLED_PROP)
317                 .c_str());
318     }
319 
320     ipsEntry = ipsProperties.find(IPS_ENTER_UTIL);
321     if (ipsEntry != ipsProperties.end())
322     {
323         enterUtil = std::get<uint8_t>(ipsEntry->second);
324     }
325     else
326     {
327         log<level::ERR>(
328             fmt::format("Status::getIPSParms could not find property: {}",
329                         IPS_ENTER_UTIL)
330                 .c_str());
331     }
332 
333     ipsEntry = ipsProperties.find(IPS_ENTER_TIME);
334     if (ipsEntry != ipsProperties.end())
335     {
336         std::chrono::milliseconds ms(std::get<uint64_t>(ipsEntry->second));
337         enterTime =
338             std::chrono::duration_cast<std::chrono::seconds>(ms).count();
339     }
340     else
341     {
342         log<level::ERR>(
343             fmt::format("Status::getIPSParms could not find property: {}",
344                         IPS_ENTER_TIME)
345                 .c_str());
346     }
347 
348     ipsEntry = ipsProperties.find(IPS_EXIT_UTIL);
349     if (ipsEntry != ipsProperties.end())
350     {
351         exitUtil = std::get<uint8_t>(ipsEntry->second);
352     }
353     else
354     {
355         log<level::ERR>(
356             fmt::format("Status::getIPSParms could not find property: {}",
357                         IPS_EXIT_UTIL)
358                 .c_str());
359     }
360 
361     ipsEntry = ipsProperties.find(IPS_EXIT_TIME);
362     if (ipsEntry != ipsProperties.end())
363     {
364         std::chrono::milliseconds ms(std::get<uint64_t>(ipsEntry->second));
365         exitTime = std::chrono::duration_cast<std::chrono::seconds>(ms).count();
366     }
367     else
368     {
369         log<level::ERR>(
370             fmt::format("Status::getIPSParms could not find property: {}",
371                         IPS_EXIT_TIME)
372                 .c_str());
373     }
374 
375     if (enterUtil > exitUtil)
376     {
377         log<level::ERR>(
378             fmt::format(
379                 "ERROR: Idle Power Saver Enter Utilization ({}%) is > Exit Utilization ({}%) - using Exit for both",
380                 enterUtil, exitUtil)
381                 .c_str());
382         enterUtil = exitUtil;
383     }
384 
385     return ipsEnabled;
386 }
387 
388 // Special processing that needs to happen once the OCCs change to ACTIVE state
389 void Status::occsWentActive()
390 {
391     CmdStatus status = CmdStatus::SUCCESS;
392 
393     status = sendModeChange();
394     if (status != CmdStatus::SUCCESS)
395     {
396         log<level::ERR>(
397             fmt::format(
398                 "Status::occsWentActive: OCC mode change failed with status {}",
399                 status)
400                 .c_str());
401     }
402 
403     status = sendIpsData();
404     if (status != CmdStatus::SUCCESS)
405     {
406         log<level::ERR>(
407             fmt::format(
408                 "Status::occsWentActive: Sending Idle Power Save Config data failed with status {}",
409                 status)
410                 .c_str());
411     }
412 }
413 
414 // Send mode change request to the master OCC
415 CmdStatus Status::sendModeChange()
416 {
417     CmdStatus status = CmdStatus::FAILURE;
418 
419     if (!device.master())
420     {
421         log<level::ERR>(
422             fmt::format(
423                 "Status::sendModeChange: MODE CHANGE does not get sent to slave OCC{}",
424                 instance)
425                 .c_str());
426         return status;
427     }
428     if (!isPowerVM())
429     {
430         // Mode change is only supported on PowerVM systems
431         log<level::DEBUG>(
432             "Status::sendModeChange: MODE CHANGE does not get sent on non-PowerVM systems");
433         return CmdStatus::SUCCESS;
434     }
435 
436     const SysPwrMode newMode = getMode();
437 
438     if (VALID_POWER_MODE_SETTING(newMode))
439     {
440         std::vector<std::uint8_t> cmd, rsp;
441         cmd.reserve(9);
442         cmd.push_back(uint8_t(CmdType::SET_MODE_AND_STATE));
443         cmd.push_back(0x00); // Data Length (2 bytes)
444         cmd.push_back(0x06);
445         cmd.push_back(0x30); // Data (Version)
446         cmd.push_back(uint8_t(OccState::NO_CHANGE));
447         cmd.push_back(uint8_t(newMode));
448         cmd.push_back(0x00); // Mode Data (Freq Point)
449         cmd.push_back(0x00); //
450         cmd.push_back(0x00); // reserved
451         log<level::INFO>(
452             fmt::format(
453                 "Status::sendModeChange: SET_MODE({}) command to OCC{} ({} bytes)",
454                 newMode, instance, cmd.size())
455                 .c_str());
456         status = occCmd.send(cmd, rsp);
457         if (status == CmdStatus::SUCCESS)
458         {
459             if (rsp.size() == 5)
460             {
461                 if (RspStatus::SUCCESS != RspStatus(rsp[2]))
462                 {
463                     log<level::ERR>(
464                         fmt::format(
465                             "Status::sendModeChange: SET MODE failed with status 0x{:02X}",
466                             rsp[2])
467                             .c_str());
468                     dump_hex(rsp);
469                     status = CmdStatus::FAILURE;
470                 }
471             }
472             else
473             {
474                 log<level::ERR>(
475                     "Status::sendModeChange: INVALID SET MODE response");
476                 dump_hex(rsp);
477                 status = CmdStatus::FAILURE;
478             }
479         }
480         else
481         {
482             if (status == CmdStatus::OPEN_FAILURE)
483             {
484                 log<level::INFO>("Status::sendModeChange: OCC not active yet");
485                 status = CmdStatus::SUCCESS;
486             }
487             else
488             {
489                 log<level::ERR>("Status::sendModeChange: SET_MODE FAILED!");
490             }
491         }
492     }
493     else
494     {
495         log<level::ERR>(
496             fmt::format(
497                 "Status::sendModeChange: Unable to set power mode to {}",
498                 newMode)
499                 .c_str());
500         status = CmdStatus::FAILURE;
501     }
502 
503     return status;
504 }
505 
506 // Send Idle Power Saver config data to the master OCC
507 CmdStatus Status::sendIpsData()
508 {
509     CmdStatus status = CmdStatus::FAILURE;
510 
511     if (!device.master())
512     {
513         log<level::ERR>(
514             fmt::format(
515                 "Status::sendIpsData: SET_CFG_DATA[IPS] does not get sent to slave OCC{}",
516                 instance)
517                 .c_str());
518         return status;
519     }
520     if (!isPowerVM())
521     {
522         // Idle Power Saver data is only supported on PowerVM systems
523         log<level::DEBUG>(
524             "Status::sendIpsData: SET_CFG_DATA[IPS] does not get sent on non-PowerVM systems");
525         return CmdStatus::SUCCESS;
526     }
527 
528     uint8_t enterUtil, exitUtil;
529     uint16_t enterTime, exitTime;
530     const bool ipsEnabled =
531         getIPSParms(enterUtil, enterTime, exitUtil, exitTime);
532 
533     log<level::INFO>(
534         fmt::format(
535             "Idle Power Saver Parameters: enabled:{}, enter:{}%/{}s, exit:{}%/{}s",
536             ipsEnabled, enterUtil, enterTime, exitUtil, exitTime)
537             .c_str());
538 
539     std::vector<std::uint8_t> cmd, rsp;
540     cmd.reserve(12);
541     cmd.push_back(uint8_t(CmdType::SET_CONFIG_DATA));
542     cmd.push_back(0x00);               // Data Length (2 bytes)
543     cmd.push_back(0x09);               //
544     cmd.push_back(0x11);               // Config Format: IPS Settings
545     cmd.push_back(0x00);               // Version
546     cmd.push_back(ipsEnabled ? 1 : 0); // IPS Enable
547     cmd.push_back(enterTime >> 8);     // Enter Delay Time
548     cmd.push_back(enterTime & 0xFF);   //
549     cmd.push_back(enterUtil);          // Enter Utilization
550     cmd.push_back(exitTime >> 8);      // Exit Delay Time
551     cmd.push_back(exitTime & 0xFF);    //
552     cmd.push_back(exitUtil);           // Exit Utilization
553     log<level::INFO>(fmt::format("Status::sendIpsData: SET_CFG_DATA[IPS] "
554                                  "command to OCC{} ({} bytes)",
555                                  instance, cmd.size())
556                          .c_str());
557     status = occCmd.send(cmd, rsp);
558     if (status == CmdStatus::SUCCESS)
559     {
560         if (rsp.size() == 5)
561         {
562             if (RspStatus::SUCCESS != RspStatus(rsp[2]))
563             {
564                 log<level::ERR>(
565                     fmt::format(
566                         "Status::sendIpsData: SET_CFG_DATA[IPS] failed with status 0x{:02X}",
567                         rsp[2])
568                         .c_str());
569                 dump_hex(rsp);
570                 status = CmdStatus::FAILURE;
571             }
572         }
573         else
574         {
575             log<level::ERR>(
576                 "Status::sendIpsData: INVALID SET_CFG_DATA[IPS] response");
577             dump_hex(rsp);
578             status = CmdStatus::FAILURE;
579         }
580     }
581     else
582     {
583         if (status == CmdStatus::OPEN_FAILURE)
584         {
585             log<level::INFO>("Status::sendIpsData: OCC not active yet");
586             status = CmdStatus::SUCCESS;
587         }
588         else
589         {
590             log<level::ERR>("Status::sendIpsData: SET_CFG_DATA[IPS] FAILED!");
591         }
592     }
593 
594     return status;
595 }
596 
597 #endif // POWER10
598 
599 } // namespace occ
600 } // namespace open_power
601