1 #include "powermode.hpp"
2 
3 #include <fcntl.h>
4 #include <fmt/core.h>
5 #include <sys/ioctl.h>
6 
7 #ifdef POWERVM_CHECK
8 #include <com/ibm/Host/Target/server.hpp>
9 #endif
10 #include <org/open_power/OCC/Device/error.hpp>
11 #include <phosphor-logging/elog-errors.hpp>
12 #include <phosphor-logging/log.hpp>
13 #include <xyz/openbmc_project/Common/error.hpp>
14 #include <xyz/openbmc_project/Control/Power/Mode/server.hpp>
15 
16 #include <cassert>
17 #include <fstream>
18 #include <regex>
19 
20 namespace open_power
21 {
22 namespace occ
23 {
24 namespace powermode
25 {
26 
27 using namespace phosphor::logging;
28 using namespace std::literals::string_literals;
29 using namespace sdbusplus::org::open_power::OCC::Device::Error;
30 
31 using Mode = sdbusplus::xyz::openbmc_project::Control::Power::server::Mode;
32 
33 using NotAllowed = sdbusplus::xyz::openbmc_project::Common::Error::NotAllowed;
34 using Reason = xyz::openbmc_project::Common::NotAllowed::REASON;
35 
36 // Set the Master OCC
37 void PowerMode::setMasterOcc(const std::string& masterOccPath)
38 {
39     if (masterOccSet)
40     {
41         if (masterOccPath != path)
42         {
43             log<level::ERR>(
44                 fmt::format(
45                     "PowerMode::setMasterOcc: Master changed (was OCC{}, {})",
46                     occInstance, masterOccPath)
47                     .c_str());
48             if (occCmd)
49             {
50                 occCmd.reset();
51             }
52         }
53     }
54     path = masterOccPath;
55     occInstance = path.back() - '0';
56     log<level::DEBUG>(fmt::format("PowerMode::setMasterOcc(OCC{}, {})",
57                                   occInstance, path.c_str())
58                           .c_str());
59     if (!occCmd)
60     {
61         occCmd = std::make_unique<open_power::occ::OccCommand>(occInstance,
62                                                                path.c_str());
63     }
64     masterOccSet = true;
65 };
66 
67 // Called when DBus power mode gets changed
68 void PowerMode::modeChanged(sdbusplus::message_t& msg)
69 {
70     std::map<std::string, std::variant<std::string>> properties{};
71     std::string interface;
72     std::string propVal;
73     msg.read(interface, properties);
74     const auto modeEntry = properties.find(POWER_MODE_PROP);
75     if (modeEntry != properties.end())
76     {
77         auto modeEntryValue = modeEntry->second;
78         propVal = std::get<std::string>(modeEntryValue);
79         SysPwrMode newMode = convertStringToMode(propVal);
80 
81         // If mode set was requested via direct dbus property change then we
82         // need to see if mode set is locked and ignore the request. We should
83         // not get to this path since the property change method should also be
84         // checking the mode lock status.
85         if (persistedData.getModeLock())
86         {
87             // Fix up the mode property since we are going to ignore this
88             // set mode request.
89             log<level::ERR>(
90                 "PowerMode::modeChanged: mode property changed while locked");
91             SysPwrMode currentMode;
92             uint16_t oemModeData;
93             getMode(currentMode, oemModeData);
94             updateDbusMode(currentMode);
95             return;
96         }
97 
98         if (newMode != SysPwrMode::NO_CHANGE)
99         {
100             // Update persisted data with new mode
101             persistedData.updateMode(newMode, 0);
102 
103             log<level::INFO>(
104                 fmt::format("DBus Power Mode Changed: {}", propVal).c_str());
105 
106             // Send mode change to OCC
107             sendModeChange();
108         }
109     }
110 }
111 
112 // Set the state of power mode lock. Writing persistent data via dbus method.
113 bool PowerMode::powerModeLock()
114 {
115     log<level::INFO>("PowerMode::powerModeLock: locking mode change");
116     persistedData.updateModeLock(true); // write persistent data
117     return true;
118 }
119 
120 // Get the state of power mode. Reading persistent data via dbus method.
121 bool PowerMode::powerModeLockStatus()
122 {
123     bool status = persistedData.getModeLock(); // read persistent data
124     log<level::INFO>(fmt::format("PowerMode::powerModeLockStatus: {}",
125                                  status ? "locked" : "unlocked")
126                          .c_str());
127     return status;
128 }
129 
130 // Called from OCC PassThrough interface (via CE login / BMC command line)
131 bool PowerMode::setMode(const SysPwrMode newMode, const uint16_t oemModeData)
132 {
133     if (persistedData.getModeLock())
134     {
135         log<level::INFO>("PowerMode::setMode: mode change blocked");
136         return false;
137     }
138 
139     if (updateDbusMode(newMode) == false)
140     {
141         // Unsupported mode
142         return false;
143     }
144 
145     // Save mode
146     persistedData.updateMode(newMode, oemModeData);
147 
148     // Send mode change to OCC
149     if (sendModeChange() != CmdStatus::SUCCESS)
150     {
151         // Mode change failed
152         return false;
153     }
154 
155     return true;
156 }
157 
158 // Convert PowerMode string to OCC SysPwrMode
159 // Returns NO_CHANGE if OEM or unsupported mode
160 SysPwrMode convertStringToMode(const std::string& i_modeString)
161 {
162     SysPwrMode pmode = SysPwrMode::NO_CHANGE;
163 
164     Mode::PowerMode mode = Mode::convertPowerModeFromString(i_modeString);
165     if (mode == Mode::PowerMode::MaximumPerformance)
166     {
167         pmode = SysPwrMode::MAX_PERF;
168     }
169     else if (mode == Mode::PowerMode::PowerSaving)
170     {
171         pmode = SysPwrMode::POWER_SAVING;
172     }
173     else if (mode == Mode::PowerMode::Static)
174     {
175         pmode = SysPwrMode::STATIC;
176     }
177     else
178     {
179         if (mode != Mode::PowerMode::OEM)
180         {
181             log<level::ERR>(
182                 fmt::format(
183                     "convertStringToMode: Invalid Power Mode specified: {}",
184                     i_modeString)
185                     .c_str());
186         }
187     }
188 
189     return pmode;
190 }
191 
192 // Check if Hypervisor target is PowerVM
193 bool isPowerVM()
194 {
195     bool powerVmTarget = true;
196 #ifdef POWERVM_CHECK
197     namespace Hyper = sdbusplus::com::ibm::Host::server;
198     constexpr auto HYPE_PATH = "/com/ibm/host0/hypervisor";
199     constexpr auto HYPE_INTERFACE = "com.ibm.Host.Target";
200     constexpr auto HYPE_PROP = "Target";
201 
202     // This will throw exception on failure
203     auto& bus = utils::getBus();
204     auto service = utils::getService(HYPE_PATH, HYPE_INTERFACE);
205     auto method = bus.new_method_call(service.c_str(), HYPE_PATH,
206                                       "org.freedesktop.DBus.Properties", "Get");
207     method.append(HYPE_INTERFACE, HYPE_PROP);
208     auto reply = bus.call(method);
209 
210     std::variant<std::string> hyperEntryValue;
211     reply.read(hyperEntryValue);
212     auto propVal = std::get<std::string>(hyperEntryValue);
213     if (Hyper::Target::convertHypervisorFromString(propVal) ==
214         Hyper::Target::Hypervisor::PowerVM)
215     {
216         powerVmTarget = true;
217     }
218 
219     log<level::DEBUG>(
220         fmt::format("isPowerVM returning {}", powerVmTarget).c_str());
221 #endif
222 
223     return powerVmTarget;
224 }
225 
226 // Initialize persistent data and return true if successful
227 bool PowerMode::initPersistentData()
228 {
229     if (!persistedData.modeAvailable())
230     {
231         // Read the default mode
232         SysPwrMode currentMode;
233         if (!getDefaultMode(currentMode))
234         {
235             // Unable to read defaults
236             return false;
237         }
238         log<level::INFO>(
239             fmt::format("PowerMode::initPersistentData: Using default mode: {}",
240                         currentMode)
241                 .c_str());
242 
243         // Save default mode as current mode
244         persistedData.updateMode(currentMode, 0);
245 
246         // Write default mode to DBus
247         updateDbusMode(currentMode);
248     }
249 
250     if (!persistedData.ipsAvailable())
251     {
252         // Read the default IPS parameters, write persistent file and update
253         // DBus
254         return useDefaultIPSParms();
255     }
256     return true;
257 }
258 
259 // Get the requested power mode and return true if successful
260 bool PowerMode::getMode(SysPwrMode& currentMode, uint16_t& oemModeData)
261 {
262     currentMode = SysPwrMode::NO_CHANGE;
263     oemModeData = 0;
264 
265     if (!persistedData.getMode(currentMode, oemModeData))
266     {
267         // Persistent data not initialized, read defaults and update DBus
268         if (!initPersistentData())
269         {
270             // Unable to read defaults from entity manager yet
271             return false;
272         }
273         return persistedData.getMode(currentMode, oemModeData);
274     }
275 
276     return true;
277 }
278 
279 // Set the power mode on DBus
280 bool PowerMode::updateDbusMode(const SysPwrMode newMode)
281 {
282     if (!VALID_POWER_MODE_SETTING(newMode) &&
283         !VALID_OEM_POWER_MODE_SETTING(newMode))
284     {
285         log<level::ERR>(
286             fmt::format(
287                 "PowerMode::updateDbusMode - Requested power mode not supported: {}",
288                 newMode)
289                 .c_str());
290         return false;
291     }
292 
293     // Convert mode for DBus
294     ModeInterface::PowerMode dBusMode;
295     switch (newMode)
296     {
297         case SysPwrMode::STATIC:
298             dBusMode = Mode::PowerMode::Static;
299             break;
300         case SysPwrMode::POWER_SAVING:
301             dBusMode = Mode::PowerMode::PowerSaving;
302             break;
303         case SysPwrMode::MAX_PERF:
304             dBusMode = Mode::PowerMode::MaximumPerformance;
305             break;
306         default:
307             dBusMode = Mode::PowerMode::OEM;
308     }
309 
310     // true = skip update signal
311     ModeInterface::setPropertyByName(POWER_MODE_PROP, dBusMode, true);
312 
313     return true;
314 }
315 
316 // Send mode change request to the master OCC
317 CmdStatus PowerMode::sendModeChange()
318 {
319     CmdStatus status;
320 
321     if (!masterActive || !masterOccSet)
322     {
323         // Nothing to do
324         log<level::DEBUG>("PowerMode::sendModeChange: OCC master not active");
325         return CmdStatus::SUCCESS;
326     }
327 
328     if (!isPowerVM())
329     {
330         // Mode change is only supported on PowerVM systems
331         log<level::DEBUG>(
332             "PowerMode::sendModeChange: MODE CHANGE does not get sent on non-PowerVM systems");
333         return CmdStatus::SUCCESS;
334     }
335 
336     SysPwrMode newMode;
337     uint16_t oemModeData = 0;
338     getMode(newMode, oemModeData);
339 
340     if (VALID_POWER_MODE_SETTING(newMode) ||
341         VALID_OEM_POWER_MODE_SETTING(newMode))
342     {
343         std::vector<std::uint8_t> cmd, rsp;
344         cmd.reserve(9);
345         cmd.push_back(uint8_t(CmdType::SET_MODE_AND_STATE));
346         cmd.push_back(0x00); // Data Length (2 bytes)
347         cmd.push_back(0x06);
348         cmd.push_back(0x30); // Data (Version)
349         cmd.push_back(uint8_t(OccState::NO_CHANGE));
350         cmd.push_back(uint8_t(newMode));
351         cmd.push_back(oemModeData >> 8);   // Mode Data (Freq Point)
352         cmd.push_back(oemModeData & 0xFF); //
353         cmd.push_back(0x00);               // reserved
354         log<level::INFO>(
355             fmt::format(
356                 "PowerMode::sendModeChange: SET_MODE({},{}) command to OCC{} ({} bytes)",
357                 newMode, oemModeData, occInstance, cmd.size())
358                 .c_str());
359         status = occCmd->send(cmd, rsp);
360         if (status == CmdStatus::SUCCESS)
361         {
362             if (rsp.size() == 5)
363             {
364                 if (RspStatus::SUCCESS != RspStatus(rsp[2]))
365                 {
366                     log<level::ERR>(
367                         fmt::format(
368                             "PowerMode::sendModeChange: SET MODE failed with status 0x{:02X}",
369                             rsp[2])
370                             .c_str());
371                     dump_hex(rsp);
372                     status = CmdStatus::FAILURE;
373                 }
374             }
375             else
376             {
377                 log<level::ERR>(
378                     "PowerMode::sendModeChange: INVALID SET MODE response");
379                 dump_hex(rsp);
380                 status = CmdStatus::FAILURE;
381             }
382         }
383         else
384         {
385             log<level::ERR>(
386                 fmt::format(
387                     "PowerMode::sendModeChange: SET_MODE FAILED with status={}",
388                     status)
389                     .c_str());
390         }
391     }
392     else
393     {
394         log<level::ERR>(
395             fmt::format(
396                 "PowerMode::sendModeChange: Unable to set power mode to {}",
397                 newMode)
398                 .c_str());
399         status = CmdStatus::FAILURE;
400     }
401 
402     return status;
403 }
404 
405 void PowerMode::ipsChanged(sdbusplus::message_t& msg)
406 {
407     bool parmsChanged = false;
408     std::string interface;
409     std::map<std::string, std::variant<bool, uint8_t, uint64_t>>
410         ipsProperties{};
411     msg.read(interface, ipsProperties);
412 
413     // Read persisted values
414     bool ipsEnabled;
415     uint8_t enterUtil, exitUtil;
416     uint16_t enterTime, exitTime;
417     getIPSParms(ipsEnabled, enterUtil, enterTime, exitUtil, exitTime);
418 
419     // Check for any changed data
420     auto ipsEntry = ipsProperties.find(IPS_ENABLED_PROP);
421     if (ipsEntry != ipsProperties.end())
422     {
423         ipsEnabled = std::get<bool>(ipsEntry->second);
424         log<level::INFO>(
425             fmt::format("Idle Power Saver change: Enabled={}", ipsEnabled)
426                 .c_str());
427         parmsChanged = true;
428     }
429     ipsEntry = ipsProperties.find(IPS_ENTER_UTIL);
430     if (ipsEntry != ipsProperties.end())
431     {
432         enterUtil = std::get<uint8_t>(ipsEntry->second);
433         log<level::INFO>(
434             fmt::format("Idle Power Saver change: Enter Util={}%", enterUtil)
435                 .c_str());
436         parmsChanged = true;
437     }
438     ipsEntry = ipsProperties.find(IPS_ENTER_TIME);
439     if (ipsEntry != ipsProperties.end())
440     {
441         std::chrono::milliseconds ms(std::get<uint64_t>(ipsEntry->second));
442         enterTime =
443             std::chrono::duration_cast<std::chrono::seconds>(ms).count();
444         log<level::INFO>(
445             fmt::format("Idle Power Saver change: Enter Time={}sec", enterTime)
446                 .c_str());
447         parmsChanged = true;
448     }
449     ipsEntry = ipsProperties.find(IPS_EXIT_UTIL);
450     if (ipsEntry != ipsProperties.end())
451     {
452         exitUtil = std::get<uint8_t>(ipsEntry->second);
453         log<level::INFO>(
454             fmt::format("Idle Power Saver change: Exit Util={}%", exitUtil)
455                 .c_str());
456         parmsChanged = true;
457     }
458     ipsEntry = ipsProperties.find(IPS_EXIT_TIME);
459     if (ipsEntry != ipsProperties.end())
460     {
461         std::chrono::milliseconds ms(std::get<uint64_t>(ipsEntry->second));
462         exitTime = std::chrono::duration_cast<std::chrono::seconds>(ms).count();
463         log<level::INFO>(
464             fmt::format("Idle Power Saver change: Exit Time={}sec", exitTime)
465                 .c_str());
466         parmsChanged = true;
467     }
468 
469     if (parmsChanged)
470     {
471         if (exitUtil == 0)
472         {
473             // Setting the exitUtil to 0 will force restoring the default IPS
474             // parmeters (0 is not valid exit utilization)
475             log<level::INFO>(
476                 "Idle Power Saver Exit Utilization is 0%. Restoring default parameters");
477             // Read the default IPS parameters, write persistent file and update
478             // DBus
479             useDefaultIPSParms();
480         }
481         else
482         {
483             // Update persistant data with new DBus values
484             persistedData.updateIPS(ipsEnabled, enterUtil, enterTime, exitUtil,
485                                     exitTime);
486         }
487 
488         // Trigger IPS data to get sent to the OCC
489         sendIpsData();
490     }
491 
492     return;
493 }
494 
495 /** @brief Get the Idle Power Saver properties from persisted data
496  * @return true if IPS parameters were read
497  */
498 bool PowerMode::getIPSParms(bool& ipsEnabled, uint8_t& enterUtil,
499                             uint16_t& enterTime, uint8_t& exitUtil,
500                             uint16_t& exitTime)
501 {
502     // Defaults:
503     ipsEnabled = true; // Enabled
504     enterUtil = 8;     // Enter Utilization (8%)
505     enterTime = 240;   // Enter Delay Time (240s)
506     exitUtil = 12;     // Exit Utilization (12%)
507     exitTime = 10;     // Exit Delay Time (10s)
508 
509     if (!persistedData.getIPS(ipsEnabled, enterUtil, enterTime, exitUtil,
510                               exitTime))
511     {
512         // Persistent data not initialized, read defaults and update DBus
513         if (!initPersistentData())
514         {
515             // Unable to read defaults from entity manager yet
516             return false;
517         }
518 
519         persistedData.getIPS(ipsEnabled, enterUtil, enterTime, exitUtil,
520                              exitTime);
521     }
522 
523     if (enterUtil > exitUtil)
524     {
525         log<level::ERR>(
526             fmt::format(
527                 "ERROR: Idle Power Saver Enter Utilization ({}%) is > Exit Utilization ({}%) - using Exit for both",
528                 enterUtil, exitUtil)
529                 .c_str());
530         enterUtil = exitUtil;
531     }
532 
533     return true;
534 }
535 
536 // Set the Idle Power Saver data on DBus
537 bool PowerMode::updateDbusIPS(const bool enabled, const uint8_t enterUtil,
538                               const uint16_t enterTime, const uint8_t exitUtil,
539                               const uint16_t exitTime)
540 {
541     // true = skip update signal
542     IpsInterface::setPropertyByName(IPS_ENABLED_PROP, enabled, true);
543     IpsInterface::setPropertyByName(IPS_ENTER_UTIL, enterUtil, true);
544     // Convert time from seconds to ms
545     uint64_t msTime = enterTime * 1000;
546     IpsInterface::setPropertyByName(IPS_ENTER_TIME, msTime, true);
547     IpsInterface::setPropertyByName(IPS_EXIT_UTIL, exitUtil, true);
548     msTime = exitTime * 1000;
549     IpsInterface::setPropertyByName(IPS_EXIT_TIME, msTime, true);
550 
551     return true;
552 }
553 
554 // Send Idle Power Saver config data to the master OCC
555 CmdStatus PowerMode::sendIpsData()
556 {
557     if (!masterActive || !masterOccSet)
558     {
559         // Nothing to do
560         return CmdStatus::SUCCESS;
561     }
562 
563     if (!isPowerVM())
564     {
565         // Idle Power Saver data is only supported on PowerVM systems
566         log<level::DEBUG>(
567             "PowerMode::sendIpsData: SET_CFG_DATA[IPS] does not get sent on non-PowerVM systems");
568         return CmdStatus::SUCCESS;
569     }
570 
571     bool ipsEnabled;
572     uint8_t enterUtil, exitUtil;
573     uint16_t enterTime, exitTime;
574     getIPSParms(ipsEnabled, enterUtil, enterTime, exitUtil, exitTime);
575 
576     log<level::INFO>(
577         fmt::format(
578             "Idle Power Saver Parameters: enabled:{}, enter:{}%/{}s, exit:{}%/{}s",
579             ipsEnabled, enterUtil, enterTime, exitUtil, exitTime)
580             .c_str());
581 
582     std::vector<std::uint8_t> cmd, rsp;
583     cmd.reserve(12);
584     cmd.push_back(uint8_t(CmdType::SET_CONFIG_DATA));
585     cmd.push_back(0x00);               // Data Length (2 bytes)
586     cmd.push_back(0x09);               //
587     cmd.push_back(0x11);               // Config Format: IPS Settings
588     cmd.push_back(0x00);               // Version
589     cmd.push_back(ipsEnabled ? 1 : 0); // IPS Enable
590     cmd.push_back(enterTime >> 8);     // Enter Delay Time
591     cmd.push_back(enterTime & 0xFF);   //
592     cmd.push_back(enterUtil);          // Enter Utilization
593     cmd.push_back(exitTime >> 8);      // Exit Delay Time
594     cmd.push_back(exitTime & 0xFF);    //
595     cmd.push_back(exitUtil);           // Exit Utilization
596     log<level::INFO>(fmt::format("PowerMode::sendIpsData: SET_CFG_DATA[IPS] "
597                                  "command to OCC{} ({} bytes)",
598                                  occInstance, cmd.size())
599                          .c_str());
600     CmdStatus status = occCmd->send(cmd, rsp);
601     if (status == CmdStatus::SUCCESS)
602     {
603         if (rsp.size() == 5)
604         {
605             if (RspStatus::SUCCESS != RspStatus(rsp[2]))
606             {
607                 log<level::ERR>(
608                     fmt::format(
609                         "PowerMode::sendIpsData: SET_CFG_DATA[IPS] failed with status 0x{:02X}",
610                         rsp[2])
611                         .c_str());
612                 dump_hex(rsp);
613                 status = CmdStatus::FAILURE;
614             }
615         }
616         else
617         {
618             log<level::ERR>(
619                 "PowerMode::sendIpsData: INVALID SET_CFG_DATA[IPS] response");
620             dump_hex(rsp);
621             status = CmdStatus::FAILURE;
622         }
623     }
624     else
625     {
626         log<level::ERR>(
627             fmt::format(
628                 "PowerMode::sendIpsData: SET_CFG_DATA[IPS] with status={}",
629                 status)
630                 .c_str());
631     }
632 
633     return status;
634 }
635 
636 // Print the current values
637 void OccPersistData::print()
638 {
639     if (modeData.modeInitialized)
640     {
641         log<level::INFO>(
642             fmt::format(
643                 "OccPersistData: Mode: 0x{:02X}, OEM Mode Data: {} (0x{:04X} Locked{})",
644                 modeData.mode, modeData.oemModeData, modeData.oemModeData,
645                 modeData.modeLocked)
646                 .c_str());
647     }
648     if (modeData.ipsInitialized)
649     {
650         log<level::INFO>(
651             fmt::format(
652                 "OccPersistData: IPS enabled:{}, enter:{}%/{}s, exit:{}%/{}s",
653                 modeData.ipsEnabled, modeData.ipsEnterUtil,
654                 modeData.ipsEnterTime, modeData.ipsExitUtil,
655                 modeData.ipsExitTime)
656                 .c_str());
657     }
658 }
659 
660 // Saves the OEM mode data in the filesystem using cereal.
661 void OccPersistData::save()
662 {
663     std::filesystem::path opath =
664         std::filesystem::path{OCC_CONTROL_PERSIST_PATH} / powerModeFilename;
665 
666     if (!std::filesystem::exists(opath.parent_path()))
667     {
668         std::filesystem::create_directory(opath.parent_path());
669     }
670 
671     log<level::DEBUG>(
672         fmt::format(
673             "OccPersistData::save: Writing Power Mode persisted data to {}",
674             opath.c_str())
675             .c_str());
676     // print();
677 
678     std::ofstream stream{opath.c_str()};
679     cereal::JSONOutputArchive oarchive{stream};
680 
681     oarchive(modeData);
682 }
683 
684 // Loads the OEM mode data in the filesystem using cereal.
685 void OccPersistData::load()
686 {
687 
688     std::filesystem::path ipath =
689         std::filesystem::path{OCC_CONTROL_PERSIST_PATH} / powerModeFilename;
690 
691     if (!std::filesystem::exists(ipath))
692     {
693         modeData.modeInitialized = false;
694         modeData.ipsInitialized = false;
695         return;
696     }
697 
698     log<level::DEBUG>(
699         fmt::format(
700             "OccPersistData::load: Reading Power Mode persisted data from {}",
701             ipath.c_str())
702             .c_str());
703     try
704     {
705         std::ifstream stream{ipath.c_str()};
706         cereal::JSONInputArchive iarchive(stream);
707         iarchive(modeData);
708     }
709     catch (const std::exception& e)
710     {
711         auto error = errno;
712         log<level::ERR>(
713             fmt::format("OccPersistData::load: failed to read {}, errno={}",
714                         ipath.c_str(), error)
715                 .c_str());
716         modeData.modeInitialized = false;
717         modeData.ipsInitialized = false;
718     }
719 
720     // print();
721 }
722 
723 // Called when PowerModeProperties defaults are available on DBus
724 void PowerMode::defaultsReady(sdbusplus::message_t& msg)
725 {
726     std::map<std::string, std::variant<std::string>> properties{};
727     std::string interface;
728     msg.read(interface, properties);
729 
730     // If persistent data exists, then don't need to read defaults
731     if ((!persistedData.modeAvailable()) || (!persistedData.ipsAvailable()))
732     {
733         log<level::INFO>(
734             fmt::format(
735                 "Default PowerModeProperties are now available (persistent modeAvail={}, ipsAvail={})",
736                 persistedData.modeAvailable() ? 'y' : 'n',
737                 persistedData.modeAvailable() ? 'y' : 'n')
738                 .c_str());
739 
740         // Read default power mode defaults and update DBus
741         initPersistentData();
742     }
743 }
744 
745 // Get the default power mode from DBus and return true if success
746 bool PowerMode::getDefaultMode(SysPwrMode& defaultMode)
747 {
748     try
749     {
750         auto& bus = utils::getBus();
751         std::string path = "/";
752         std::string service =
753             utils::getServiceUsingSubTree(PMODE_DEFAULT_INTERFACE, path);
754         auto method =
755             bus.new_method_call(service.c_str(), path.c_str(),
756                                 "org.freedesktop.DBus.Properties", "Get");
757         method.append(PMODE_DEFAULT_INTERFACE, "PowerMode");
758         auto reply = bus.call(method);
759 
760         std::variant<std::string> stateEntryValue;
761         reply.read(stateEntryValue);
762         auto propVal = std::get<std::string>(stateEntryValue);
763 
764         const std::string fullModeString =
765             PMODE_INTERFACE + ".PowerMode."s + propVal;
766         defaultMode = powermode::convertStringToMode(fullModeString);
767         if (!VALID_POWER_MODE_SETTING(defaultMode))
768         {
769             log<level::ERR>(
770                 fmt::format(
771                     "PowerMode::getDefaultMode: Invalid default power mode found: {}",
772                     defaultMode)
773                     .c_str());
774             // If default was read but not valid, use Max Performance
775             defaultMode = SysPwrMode::MAX_PERF;
776             return true;
777         }
778     }
779     catch (const sdbusplus::exception_t& e)
780     {
781         log<level::ERR>(
782             fmt::format("Unable to read Default Power Mode: {}", e.what())
783                 .c_str());
784         return false;
785     }
786 
787     return true;
788 }
789 
790 /* Get the default Idle Power Saver properties and return true if successful */
791 bool PowerMode::getDefaultIPSParms(bool& ipsEnabled, uint8_t& enterUtil,
792                                    uint16_t& enterTime, uint8_t& exitUtil,
793                                    uint16_t& exitTime)
794 {
795     // Defaults:
796     ipsEnabled = true; // Enabled
797     enterUtil = 8;     // Enter Utilization (8%)
798     enterTime = 240;   // Enter Delay Time (240s)
799     exitUtil = 12;     // Exit Utilization (12%)
800     exitTime = 10;     // Exit Delay Time (10s)
801 
802     std::map<std::string, std::variant<bool, uint8_t, uint16_t, uint64_t>>
803         ipsProperties{};
804 
805     // Get all IPS properties from DBus
806     try
807     {
808         auto& bus = utils::getBus();
809         std::string path = "/";
810         std::string service =
811             utils::getServiceUsingSubTree(PMODE_DEFAULT_INTERFACE, path);
812         auto method =
813             bus.new_method_call(service.c_str(), path.c_str(),
814                                 "org.freedesktop.DBus.Properties", "GetAll");
815         method.append(PMODE_DEFAULT_INTERFACE);
816         auto reply = bus.call(method);
817         reply.read(ipsProperties);
818     }
819     catch (const sdbusplus::exception_t& e)
820     {
821         log<level::ERR>(
822             fmt::format(
823                 "Unable to read Default Idle Power Saver parameters so it will be disabled: {}",
824                 e.what())
825                 .c_str());
826         return false;
827     }
828 
829     auto ipsEntry = ipsProperties.find("IdlePowerSaverEnabled");
830     if (ipsEntry != ipsProperties.end())
831     {
832         ipsEnabled = std::get<bool>(ipsEntry->second);
833     }
834     else
835     {
836         log<level::ERR>(
837             "PowerMode::getDefaultIPSParms could not find property: IdlePowerSaverEnabled");
838     }
839 
840     ipsEntry = ipsProperties.find("EnterUtilizationPercent");
841     if (ipsEntry != ipsProperties.end())
842     {
843         enterUtil = std::get<uint64_t>(ipsEntry->second);
844     }
845     else
846     {
847         log<level::ERR>(
848             "PowerMode::getDefaultIPSParms could not find property: EnterUtilizationPercent");
849     }
850 
851     ipsEntry = ipsProperties.find("EnterUtilizationDwellTime");
852     if (ipsEntry != ipsProperties.end())
853     {
854         enterTime = std::get<uint64_t>(ipsEntry->second);
855     }
856     else
857     {
858         log<level::ERR>(
859             "PowerMode::getDefaultIPSParms could not find property: EnterUtilizationDwellTime");
860     }
861 
862     ipsEntry = ipsProperties.find("ExitUtilizationPercent");
863     if (ipsEntry != ipsProperties.end())
864     {
865         exitUtil = std::get<uint64_t>(ipsEntry->second);
866     }
867     else
868     {
869         log<level::ERR>(
870             "PowerMode::getDefaultIPSParms could not find property: ExitUtilizationPercent");
871     }
872 
873     ipsEntry = ipsProperties.find("ExitUtilizationDwellTime");
874     if (ipsEntry != ipsProperties.end())
875     {
876         exitTime = std::get<uint64_t>(ipsEntry->second);
877     }
878     else
879     {
880         log<level::ERR>(
881             "PowerMode::getDefaultIPSParms could not find property: ExitUtilizationDwellTime");
882     }
883 
884     if (enterUtil > exitUtil)
885     {
886         log<level::ERR>(
887             fmt::format(
888                 "ERROR: Default Idle Power Saver Enter Utilization ({}%) is > Exit Utilization ({}%) - using Exit for both",
889                 enterUtil, exitUtil)
890                 .c_str());
891         enterUtil = exitUtil;
892     }
893 
894     return true;
895 }
896 
897 /* Read default IPS parameters, save them to the persistent file and update
898  DBus. Return true if successful */
899 bool PowerMode::useDefaultIPSParms()
900 {
901     // Read the default IPS parameters
902     bool ipsEnabled;
903     uint8_t enterUtil, exitUtil;
904     uint16_t enterTime, exitTime;
905     if (!getDefaultIPSParms(ipsEnabled, enterUtil, enterTime, exitUtil,
906                             exitTime))
907     {
908         // Unable to read defaults
909         return false;
910     }
911     log<level::INFO>(
912         fmt::format(
913             "PowerMode::useDefaultIPSParms: Using default IPS parms: Enabled: {}, EnterUtil: {}%, EnterTime: {}s, ExitUtil: {}%, ExitTime: {}s",
914             ipsEnabled, enterUtil, enterTime, exitUtil, exitTime)
915             .c_str());
916 
917     // Save IPS parms to the persistent file
918     persistedData.updateIPS(ipsEnabled, enterUtil, enterTime, exitUtil,
919                             exitTime);
920 
921     // Write IPS parms to DBus
922     return updateDbusIPS(ipsEnabled, enterUtil, enterTime, exitUtil, exitTime);
923 }
924 
925 #ifdef POWER10
926 
927 // Starts to watch for IPS active state changes.
928 bool PowerMode::openIpsFile()
929 {
930     bool rc = true;
931     fd = open(ipsStatusFile.c_str(), O_RDONLY | O_NONBLOCK);
932     const int open_errno = errno;
933     if (fd < 0)
934     {
935         log<level::ERR>(fmt::format("openIpsFile Error({})={} : File={}",
936                                     open_errno, strerror(open_errno),
937                                     ipsStatusFile.c_str())
938                             .c_str());
939 
940         close(fd);
941 
942         using namespace sdbusplus::org::open_power::OCC::Device::Error;
943         report<OpenFailure>(
944             phosphor::logging::org::open_power::OCC::Device::OpenFailure::
945                 CALLOUT_ERRNO(open_errno),
946             phosphor::logging::org::open_power::OCC::Device::OpenFailure::
947                 CALLOUT_DEVICE_PATH(ipsStatusFile.c_str()));
948 
949         // We are no longer watching the error
950         active(false);
951 
952         watching = false;
953         rc = false;
954         // NOTE: this will leave the system not reporting IPS active state to
955         // Fan Controls, Until an APP reload, or IPL and we will attempt again.
956     }
957     return rc;
958 }
959 
960 // Starts to watch for IPS active state changes.
961 void PowerMode::addIpsWatch(bool poll)
962 {
963     // open file and register callback on file if we are not currently watching,
964     // and if poll=true, and if we are the master.
965     if ((!watching) && poll)
966     {
967         //  Open the file
968         if (openIpsFile())
969         {
970             // register the callback handler which sets 'watching'
971             registerIpsStatusCallBack();
972         }
973     }
974 }
975 
976 // Stops watching for IPS active state changes.
977 void PowerMode::removeIpsWatch()
978 {
979     //  NOTE: we want to remove event, close file, and IPS active false no
980     //  matter what the 'watching' flags is set to.
981 
982     // We are no longer watching the error
983     active(false);
984 
985     watching = false;
986 
987     // Close file
988     close(fd);
989 
990     // clears sourcePtr in the event source.
991     eventSource.reset();
992 }
993 
994 // Attaches the FD to event loop and registers the callback handler
995 void PowerMode::registerIpsStatusCallBack()
996 {
997     decltype(eventSource.get()) sourcePtr = nullptr;
998 
999     auto r = sd_event_add_io(event.get(), &sourcePtr, fd, EPOLLPRI | EPOLLERR,
1000                              ipsStatusCallBack, this);
1001     if (r < 0)
1002     {
1003         log<level::ERR>(fmt::format("sd_event_add_io: Error({})={} : File={}",
1004                                     r, strerror(-r), ipsStatusFile.c_str())
1005                             .c_str());
1006 
1007         using InternalFailure =
1008             sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
1009         report<InternalFailure>();
1010 
1011         removeIpsWatch();
1012         // NOTE: this will leave the system not reporting IPS active state to
1013         // Fan Controls, Until an APP reload, or IPL and we will attempt again.
1014     }
1015     else
1016     {
1017         // puts sourcePtr in the event source.
1018         eventSource.reset(sourcePtr);
1019         // Set we are watching the error
1020         watching = true;
1021     }
1022 }
1023 
1024 // Static function to redirect to non static analyze event function to be
1025 // able to read file and push onto dBus.
1026 int PowerMode::ipsStatusCallBack(sd_event_source* /*es*/, int /*fd*/,
1027                                  uint32_t /*revents*/, void* userData)
1028 {
1029     auto pmode = static_cast<PowerMode*>(userData);
1030     pmode->analyzeIpsEvent();
1031     return 0;
1032 }
1033 
1034 // Function to Read SysFs file change on IPS state and push on dBus.
1035 void PowerMode::analyzeIpsEvent()
1036 {
1037     // Need to seek to START, else the poll returns immediately telling
1038     // there is data to be read. if not done this floods the system.
1039     auto r = lseek(fd, 0, SEEK_SET);
1040     const int open_errno = errno;
1041     if (r < 0)
1042     {
1043         // NOTE: upon file access error we can not just re-open file, we have to
1044         // remove and add to watch.
1045         removeIpsWatch();
1046         addIpsWatch(true);
1047     }
1048 
1049     // if we are 'watching' that is the file seek, or the re-open passed.. we
1050     // can read the data
1051     if (watching)
1052     {
1053         // This file gets created when polling OCCs. A value or length of 0 is
1054         // deemed success. That means we would disable IPS active on dbus.
1055         char data;
1056         bool ipsState = false;
1057         const auto len = read(fd, &data, sizeof(data));
1058         const int readErrno = errno;
1059         if (len <= 0)
1060         {
1061             removeIpsWatch();
1062 
1063             log<level::ERR>(
1064                 fmt::format("IPS state Read Error({})={} : File={} : len={}",
1065                             readErrno, strerror(readErrno),
1066                             ipsStatusFile.c_str(), len)
1067                     .c_str());
1068 
1069             report<ReadFailure>(
1070                 phosphor::logging::org::open_power::OCC::Device::ReadFailure::
1071                     CALLOUT_ERRNO(readErrno),
1072                 phosphor::logging::org::open_power::OCC::Device::ReadFailure::
1073                     CALLOUT_DEVICE_PATH(ipsStatusFile.c_str()));
1074 
1075             // NOTE: this will leave the system not reporting IPS active state
1076             // to Fan Controls, Until an APP reload, or IPL and we will attempt
1077             // again.
1078         }
1079         else
1080         {
1081             // Data returned in ASCII.
1082             // convert to integer. atoi()
1083             // from OCC_P10_FW_Interfaces spec
1084             //          Bit 6: IPS active   1 indicates enabled.
1085             //          Bit 7: IPS enabled. 1 indicates enabled.
1086             //      mask off bit 6 --> & 0x02
1087             // Shift left one bit and store as bool. >> 1
1088             ipsState = static_cast<bool>(((atoi(&data)) & 0x2) >> 1);
1089         }
1090 
1091         // This will only set IPS active dbus if different than current.
1092         active(ipsState);
1093     }
1094     else
1095     {
1096         removeIpsWatch();
1097 
1098         // If the Retry did not get to "watching = true" we already have an
1099         // error log, just post trace.
1100         log<level::ERR>(fmt::format("Retry on File seek Error({})={} : File={}",
1101                                     open_errno, strerror(open_errno),
1102                                     ipsStatusFile.c_str())
1103                             .c_str());
1104 
1105         // NOTE: this will leave the system not reporting IPS active state to
1106         // Fan Controls, Until an APP reload, or IPL and we will attempt again.
1107     }
1108 
1109     return;
1110 }
1111 
1112 // overrides read/write to powerMode dbus property.
1113 Mode::PowerMode PowerMode::powerMode(Mode::PowerMode value)
1114 {
1115     if (persistedData.getModeLock())
1116     {
1117         log<level::INFO>("PowerMode::powerMode: mode property change blocked");
1118         elog<NotAllowed>(xyz::openbmc_project::Common::NotAllowed::REASON(
1119             "mode change not allowed due to lock"));
1120         return value;
1121     }
1122     else
1123     {
1124         return Mode::powerMode(value);
1125     }
1126 }
1127 #endif
1128 
1129 /*  Set dbus property to SAFE mode(true) or clear(false) only if different */
1130 void PowerMode::updateDbusSafeMode(const bool safeModeReq)
1131 {
1132     log<level::DEBUG>(
1133         fmt::format("PowerMode:updateDbusSafeMode: Update dbus state ({})",
1134                     safeModeReq)
1135             .c_str());
1136 
1137     // Note; this function checks and only updates if different.
1138     Mode::safeMode(safeModeReq);
1139 }
1140 
1141 } // namespace powermode
1142 
1143 } // namespace occ
1144 
1145 } // namespace open_power
1146