1 /*
2 // Copyright (c) 2019 Intel 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 #include "xyz/openbmc_project/Common/error.hpp"
17 
18 #include <fstream>
19 #include <iostream>
20 #include <ipmid/api.hpp>
21 #include <ipmid/utils.hpp>
22 #include <nlohmann/json.hpp>
23 #include <phosphor-logging/elog-errors.hpp>
24 #include <phosphor-logging/log.hpp>
25 #include <regex>
26 #include <sdbusplus/timer.hpp>
27 #include <stdexcept>
28 #include <string_view>
29 #include <xyz/openbmc_project/Control/Power/RestorePolicy/server.hpp>
30 
31 using namespace phosphor::logging;
32 
33 namespace ipmi::chassis
34 {
35 static constexpr const char* buttonIntf = "xyz.openbmc_project.Chassis.Buttons";
36 
37 const static constexpr char* idButtonPath =
38     "/xyz/openbmc_project/chassis/buttons/id";
39 static constexpr const char* powerButtonPath =
40     "/xyz/openbmc_project/chassis/buttons/power";
41 static constexpr const char* resetButtonPath =
42     "/xyz/openbmc_project/chassis/buttons/reset";
43 static constexpr const char* interruptButtonPath =
44     "/xyz/openbmc_project/chassis/buttons/nmi";
45 
46 const static constexpr char* idButtonProp = "ButtonPressed";
47 
48 const static constexpr char* ledService =
49     "xyz.openbmc_project.LED.GroupManager";
50 const static constexpr char* ledIDOnObj =
51     "/xyz/openbmc_project/led/groups/enclosure_identify";
52 const static constexpr char* ledIDBlinkObj =
53     "/xyz/openbmc_project/led/groups/enclosure_identify_blink";
54 const static constexpr char* ledInterface = "xyz.openbmc_project.Led.Group";
55 const static constexpr char* ledProp = "Asserted";
56 enum class ChassisIDState
57 {
58     off = 0,
59     temporary = 1,
60     indefinite = 2,
61 };
62 static ChassisIDState chassisIDState = ChassisIDState::off;
63 
64 constexpr size_t defaultIdentifyTimeOut = 15;
65 
66 std::unique_ptr<phosphor::Timer> identifyTimer
67     __attribute__((init_priority(101)));
68 std::unique_ptr<sdbusplus::bus::match_t> matchPtr
69     __attribute__((init_priority(101)));
70 
71 static void registerChassisFunctions() __attribute__((constructor));
72 
73 static ipmi::ServiceCache LEDService(ledInterface, ledIDBlinkObj);
74 
75 void enclosureIdentifyLed(const char* objName, bool isIdLedOn)
76 {
77     auto bus = getSdBus();
78 
79     try
80     {
81         std::string service = LEDService.getService(*bus);
82         setDbusProperty(*bus, service, objName, ledInterface, ledProp,
83                         isIdLedOn);
84     }
85     catch (const std::exception& e)
86     {
87         log<level::ERR>("enclosureIdentifyLed: can't set property",
88                         entry("ERR=%s", e.what()));
89     }
90 }
91 
92 bool getIDState(const char* objName, bool& state)
93 {
94     auto bus = getSdBus();
95 
96     try
97     {
98         std::string service = LEDService.getService(*bus);
99         ipmi::Value enabled =
100             getDbusProperty(*bus, service, objName, ledInterface, ledProp);
101         state = std::get<bool>(enabled);
102     }
103     catch (sdbusplus::exception::SdBusError& e)
104     {
105         log<level::ERR>("Fail to get property", entry("PATH=%s", objName),
106                         entry("ERROR=%s", e.what()));
107         return false;
108     }
109     return true;
110 }
111 
112 void enclosureIdentifyLedBlinkOff()
113 {
114     chassisIDState = ChassisIDState::off;
115     enclosureIdentifyLed(ledIDBlinkObj, false);
116 }
117 
118 void idButtonPropChanged(sdbusplus::message::message& msg)
119 {
120     bool asserted = false;
121     bool buttonPressed = false;
122 
123     std::map<std::string, ipmi::Value> props;
124     std::vector<std::string> inval;
125     std::string iface;
126     msg.read(iface, props, inval);
127 
128     for (const auto& t : props)
129     {
130         auto key = t.first;
131         auto value = t.second;
132 
133         if (key == idButtonProp)
134         {
135             buttonPressed = std::get<bool>(value);
136         }
137         break;
138     }
139 
140     if (buttonPressed)
141     {
142         if (identifyTimer->isRunning())
143         {
144             log<level::INFO>("ID timer is running");
145         }
146 
147         // make sure timer is stopped
148         identifyTimer->stop();
149 
150         if (!getIDState(ledIDBlinkObj, asserted))
151         {
152             return;
153         }
154 
155         if (asserted)
156         {
157             // LED is blinking, turn off the LED
158             chassisIDState = ChassisIDState::off;
159             enclosureIdentifyLed(ledIDBlinkObj, false);
160             enclosureIdentifyLed(ledIDOnObj, false);
161         }
162         else
163         {
164             // toggle the IED on/off
165             if (!getIDState(ledIDOnObj, asserted))
166             {
167                 return;
168             }
169             enclosureIdentifyLed(ledIDOnObj, !asserted);
170         }
171     }
172 }
173 
174 void createIdentifyTimer()
175 {
176     if (!identifyTimer)
177     {
178         identifyTimer =
179             std::make_unique<phosphor::Timer>(enclosureIdentifyLedBlinkOff);
180     }
181 }
182 
183 ipmi::RspType<> ipmiChassisIdentify(std::optional<uint8_t> interval,
184                                     std::optional<uint8_t> force)
185 {
186     uint8_t identifyInterval = interval.value_or(defaultIdentifyTimeOut);
187     bool forceIdentify = force.value_or(0) & 0x01;
188 
189     enclosureIdentifyLed(ledIDOnObj, false);
190     identifyTimer->stop();
191 
192     if (identifyInterval || forceIdentify)
193     {
194         enclosureIdentifyLed(ledIDBlinkObj, true);
195         if (forceIdentify)
196         {
197             chassisIDState = ChassisIDState::indefinite;
198             return ipmi::responseSuccess();
199         }
200         chassisIDState = ChassisIDState::temporary;
201         // start the timer
202         auto time = std::chrono::duration_cast<std::chrono::microseconds>(
203             std::chrono::seconds(identifyInterval));
204         identifyTimer->start(time);
205     }
206     else
207     {
208         chassisIDState = ChassisIDState::off;
209         enclosureIdentifyLed(ledIDBlinkObj, false);
210     }
211     return ipmi::responseSuccess();
212 }
213 
214 namespace power_policy
215 {
216 /* helper function for Get Chassis Status Command
217  */
218 std::optional<uint2_t> getPowerRestorePolicy()
219 {
220     constexpr const char* powerRestorePath =
221         "/xyz/openbmc_project/control/host0/power_restore_policy";
222     constexpr const char* powerRestoreIntf =
223         "xyz.openbmc_project.Control.Power.RestorePolicy";
224     uint2_t restorePolicy = 0;
225     std::shared_ptr<sdbusplus::asio::connection> busp = getSdBus();
226 
227     try
228     {
229         auto service =
230             ipmi::getService(*busp, powerRestoreIntf, powerRestorePath);
231 
232         ipmi::Value result =
233             ipmi::getDbusProperty(*busp, service, powerRestorePath,
234                                   powerRestoreIntf, "PowerRestorePolicy");
235         auto powerRestore = sdbusplus::xyz::openbmc_project::Control::Power::
236             server::RestorePolicy::convertPolicyFromString(
237                 std::get<std::string>(result));
238 
239         using namespace sdbusplus::xyz::openbmc_project::Control::Power::server;
240         switch (powerRestore)
241         {
242             case RestorePolicy::Policy::AlwaysOff:
243                 restorePolicy = 0x00;
244                 break;
245             case RestorePolicy::Policy::Restore:
246                 restorePolicy = 0x01;
247                 break;
248             case RestorePolicy::Policy::AlwaysOn:
249                 restorePolicy = 0x02;
250                 break;
251         }
252     }
253     catch (const std::exception& e)
254     {
255         log<level::ERR>("Failed to fetch PowerRestorePolicy property",
256                         entry("ERROR=%s", e.what()),
257                         entry("PATH=%s", powerRestorePath),
258                         entry("INTERFACE=%s", powerRestoreIntf));
259         return std::nullopt;
260     }
261     return std::make_optional(restorePolicy);
262 }
263 
264 /*
265  * getPowerStatus
266  * helper function for Get Chassis Status Command
267  * return - optional value for pgood (no value on error)
268  */
269 std::optional<bool> getPowerStatus()
270 {
271     bool powerGood = false;
272     std::shared_ptr<sdbusplus::asio::connection> busp = getSdBus();
273     try
274     {
275         constexpr const char* chassisStatePath =
276             "/xyz/openbmc_project/state/chassis0";
277         constexpr const char* chassisStateIntf =
278             "xyz.openbmc_project.State.Chassis";
279         auto service =
280             ipmi::getService(*busp, chassisStateIntf, chassisStatePath);
281 
282         ipmi::Value variant =
283             ipmi::getDbusProperty(*busp, service, chassisStatePath,
284                                   chassisStateIntf, "CurrentPowerState");
285         std::string powerState = std::get<std::string>(variant);
286         if (powerState == "xyz.openbmc_project.State.Chassis.PowerState.On")
287         {
288             powerGood = true;
289         }
290     }
291     catch (const std::exception& e)
292     {
293         log<level::ERR>("Failed to fetch power state property",
294                         entry("ERROR=%s", e.what()));
295         return std::nullopt;
296     }
297     return std::make_optional(powerGood);
298 }
299 
300 /*
301  * getACFailStatus
302  * helper function for Get Chassis Status Command
303  * return - bool value for ACFail (false on error)
304  */
305 bool getACFailStatus()
306 {
307     constexpr const char* acBootObj =
308         "/xyz/openbmc_project/control/host0/ac_boot";
309     constexpr const char* acBootIntf = "xyz.openbmc_project.Common.ACBoot";
310     std::string acFail;
311     std::shared_ptr<sdbusplus::asio::connection> bus = getSdBus();
312     try
313     {
314         auto service = ipmi::getService(*bus, acBootIntf, acBootObj);
315 
316         ipmi::Value variant = ipmi::getDbusProperty(*bus, service, acBootObj,
317                                                     acBootIntf, "ACBoot");
318         acFail = std::get<std::string>(variant);
319     }
320     catch (const std::exception& e)
321     {
322         log<level::ERR>(
323             "Failed to fetch ACBoot property", entry("ERROR=%s", e.what()),
324             entry("PATH=%s", acBootObj), entry("INTERFACE=%s", acBootIntf));
325     }
326     return acFail == "True";
327 }
328 } // namespace power_policy
329 
330 static std::optional<bool> getButtonEnabled(const std::string& buttonPath)
331 {
332     bool buttonDisabled = false;
333     std::shared_ptr<sdbusplus::asio::connection> busp = getSdBus();
334     try
335     {
336         auto service = ipmi::getService(*getSdBus(), buttonIntf, buttonPath);
337         ipmi::Value disabled = ipmi::getDbusProperty(
338             *busp, service, buttonPath, buttonIntf, "ButtonMasked");
339         buttonDisabled = std::get<bool>(disabled);
340     }
341     catch (sdbusplus::exception::SdBusError& e)
342     {
343         log<level::ERR>("Fail to get button disabled property",
344                         entry("PATH=%s", buttonPath.c_str()),
345                         entry("ERROR=%s", e.what()));
346         return std::nullopt;
347     }
348     return std::make_optional(buttonDisabled);
349 }
350 
351 static bool setButtonEnabled(const std::string& buttonPath, const bool disabled)
352 {
353     try
354     {
355         auto service = ipmi::getService(*getSdBus(), buttonIntf, buttonPath);
356         ipmi::setDbusProperty(*getSdBus(), service, buttonPath, buttonIntf,
357                               "ButtonMasked", disabled);
358     }
359     catch (std::exception& e)
360     {
361         log<level::ERR>("Failed to set button disabled",
362                         entry("EXCEPTION=%s, REQUEST=%x", e.what(), disabled));
363         return -1;
364     }
365 
366     return 0;
367 }
368 
369 static bool getRestartCause(std::string& restartCause)
370 {
371     constexpr const char* restartCausePath =
372         "/xyz/openbmc_project/control/host0/restart_cause";
373     constexpr const char* restartCauseIntf =
374         "xyz.openbmc_project.Common.RestartCause";
375     std::shared_ptr<sdbusplus::asio::connection> busp = getSdBus();
376 
377     try
378     {
379         auto service =
380             ipmi::getService(*busp, restartCauseIntf, restartCausePath);
381 
382         ipmi::Value result = ipmi::getDbusProperty(
383             *busp, service, restartCausePath, restartCauseIntf, "RestartCause");
384         restartCause = std::get<std::string>(result);
385     }
386     catch (const std::exception& e)
387     {
388         log<level::ERR>("Failed to fetch RestartCause property",
389                         entry("ERROR=%s", e.what()),
390                         entry("PATH=%s", restartCausePath),
391                         entry("INTERFACE=%s", restartCauseIntf));
392         return false;
393     }
394     return true;
395 }
396 
397 static bool checkIPMIRestartCause(bool& ipmiRestartCause)
398 {
399     std::string restartCause;
400     if (!getRestartCause(restartCause))
401     {
402         return false;
403     }
404     ipmiRestartCause =
405         (restartCause ==
406          "xyz.openbmc_project.State.Host.RestartCause.IpmiCommand");
407     return true;
408 }
409 
410 //----------------------------------------------------------------------
411 // Get Chassis Status commands
412 //----------------------------------------------------------------------
413 ipmi::RspType<bool,    // Power is on
414               bool,    // Power overload
415               bool,    // Interlock
416               bool,    // power fault
417               bool,    // power control fault
418               uint2_t, // power restore policy
419               bool,    // reserved
420 
421               bool, // AC failed
422               bool, // last power down caused by a Power overload
423               bool, // last power down caused by a power interlock
424               bool, // last power down caused by power fault
425               bool, // last ‘Power is on’ state was entered via IPMI command
426               uint3_t, // reserved
427 
428               bool,    // Chassis intrusion active
429               bool,    // Front Panel Lockout active
430               bool,    // Drive Fault
431               bool,    // Cooling/fan fault detected
432               uint2_t, // Chassis Identify State
433               bool,    // Chassis Identify command and state info supported
434               bool,    // reserved
435 
436               bool, // Power off button disabled
437               bool, // Reset button disabled
438               bool, // Diagnostic Interrupt button disabled
439               bool, // Standby (sleep) button disabled
440               bool, // Power off button disable allowed
441               bool, // Reset button disable allowed
442               bool, // Diagnostic Interrupt button disable allowed
443               bool  // Standby (sleep) button disable allowed
444               >
445     ipmiGetChassisStatus()
446 {
447     std::optional<uint2_t> restorePolicy =
448         power_policy::getPowerRestorePolicy();
449     std::optional<bool> powerGood = power_policy::getPowerStatus();
450     if (!restorePolicy || !powerGood)
451     {
452         return ipmi::responseUnspecifiedError();
453     }
454 
455     //  Front Panel Button Capabilities and disable/enable status(Optional)
456     std::optional<bool> powerButtonReading = getButtonEnabled(powerButtonPath);
457     // allow disable if the interface is present
458     bool powerButtonDisableAllow = static_cast<bool>(powerButtonReading);
459     // default return the button is enabled (not disabled)
460     bool powerButtonDisabled = false;
461     if (powerButtonDisableAllow)
462     {
463         // return the real value of the button status, if present
464         powerButtonDisabled = *powerButtonReading;
465     }
466 
467     std::optional<bool> resetButtonReading = getButtonEnabled(resetButtonPath);
468     // allow disable if the interface is present
469     bool resetButtonDisableAllow = static_cast<bool>(resetButtonReading);
470     // default return the button is enabled (not disabled)
471     bool resetButtonDisabled = false;
472     if (resetButtonDisableAllow)
473     {
474         // return the real value of the button status, if present
475         resetButtonDisabled = *resetButtonReading;
476     }
477 
478     std::optional<bool> interruptButtonReading =
479         getButtonEnabled(interruptButtonPath);
480     // allow disable if the interface is present
481     bool interruptButtonDisableAllow =
482         static_cast<bool>(interruptButtonReading);
483     // default return the button is enabled (not disabled)
484     bool interruptButtonDisabled = false;
485     if (interruptButtonDisableAllow)
486     {
487         // return the real value of the button status, if present
488         interruptButtonDisabled = *interruptButtonReading;
489     }
490 
491     bool powerDownAcFailed = power_policy::getACFailStatus();
492 
493     bool powerStatusIPMI = false;
494     if (!checkIPMIRestartCause(powerStatusIPMI))
495     {
496         return ipmi::responseUnspecifiedError();
497     }
498 
499     // This response has a lot of hard-coded, unsupported fields
500     // They are set to false or 0
501     constexpr bool powerOverload = false;
502     constexpr bool chassisInterlock = false;
503     constexpr bool powerFault = false;
504     constexpr bool powerControlFault = false;
505     constexpr bool powerDownOverload = false;
506     constexpr bool powerDownInterlock = false;
507     constexpr bool powerDownPowerFault = false;
508     constexpr bool chassisIntrusionActive = false;
509     constexpr bool frontPanelLockoutActive = false;
510     constexpr bool driveFault = false;
511     constexpr bool coolingFanFault = false;
512     // chassisIdentifySupport set because this command is implemented
513     constexpr bool chassisIdentifySupport = true;
514     uint2_t chassisIdentifyState = chassisIDState;
515     constexpr bool sleepButtonDisabled = false;
516     constexpr bool sleepButtonDisableAllow = false;
517 
518     return ipmi::responseSuccess(
519         *powerGood, powerOverload, chassisInterlock, powerFault,
520         powerControlFault, *restorePolicy,
521         false, // reserved
522 
523         powerDownAcFailed, powerDownOverload, powerDownInterlock,
524         powerDownPowerFault, powerStatusIPMI,
525         uint3_t(0), // reserved
526 
527         chassisIntrusionActive, frontPanelLockoutActive, driveFault,
528         coolingFanFault, chassisIdentifyState, chassisIdentifySupport,
529         false, // reserved
530 
531         powerButtonDisabled, resetButtonDisabled, interruptButtonDisabled,
532         sleepButtonDisabled, powerButtonDisableAllow, resetButtonDisableAllow,
533         interruptButtonDisableAllow, sleepButtonDisableAllow);
534 }
535 
536 static uint4_t getRestartCauseValue(const std::string& cause)
537 {
538     uint4_t restartCauseValue = 0;
539     if (cause == "xyz.openbmc_project.State.Host.RestartCause.Unknown")
540     {
541         restartCauseValue = 0x0;
542     }
543     else if (cause == "xyz.openbmc_project.State.Host.RestartCause.IpmiCommand")
544     {
545         restartCauseValue = 0x1;
546     }
547     else if (cause == "xyz.openbmc_project.State.Host.RestartCause.ResetButton")
548     {
549         restartCauseValue = 0x2;
550     }
551     else if (cause == "xyz.openbmc_project.State.Host.RestartCause.PowerButton")
552     {
553         restartCauseValue = 0x3;
554     }
555     else if (cause ==
556              "xyz.openbmc_project.State.Host.RestartCause.WatchdogTimer")
557     {
558         restartCauseValue = 0x4;
559     }
560     else if (cause == "xyz.openbmc_project.State.Host.RestartCause.OEM")
561     {
562         restartCauseValue = 0x5;
563     }
564     else if (cause ==
565              "xyz.openbmc_project.State.Host.RestartCause.PowerPolicyAlwaysOn")
566     {
567         restartCauseValue = 0x6;
568     }
569     else if (cause == "xyz.openbmc_project.State.Host.RestartCause."
570                       "PowerPolicyPreviousState")
571     {
572         restartCauseValue = 0x7;
573     }
574     else if (cause == "xyz.openbmc_project.State.Host.RestartCause.PEFReset")
575     {
576         restartCauseValue = 0x8;
577     }
578     else if (cause ==
579              "xyz.openbmc_project.State.Host.RestartCause.PEFPowerCycle")
580     {
581         restartCauseValue = 0x9;
582     }
583     else if (cause == "xyz.openbmc_project.State.Host.RestartCause.SoftReset")
584     {
585         restartCauseValue = 0xa;
586     }
587     else if (cause == "xyz.openbmc_project.State.Host.RestartCause.RTCWakeup")
588     {
589         restartCauseValue = 0xb;
590     }
591     return restartCauseValue;
592 }
593 
594 ipmi::RspType<uint4_t, // Restart Cause
595               uint4_t, // reserved
596               uint8_t  // channel number (not supported)
597               >
598     ipmiGetSystemRestartCause()
599 {
600     std::string restartCauseStr;
601     if (!getRestartCause(restartCauseStr))
602     {
603         return ipmi::responseUnspecifiedError();
604     }
605 
606     return ipmi::responseSuccess(getRestartCauseValue(restartCauseStr), 0, 0);
607 }
608 
609 ipmi::RspType<> ipmiSetFrontPanelButtonEnables(bool disablePowerButton,
610                                                bool disableResetButton,
611                                                bool disableInterruptButton,
612                                                bool disableSleepButton,
613                                                uint4_t reserved)
614 {
615     bool error = false;
616 
617     error |= setButtonEnabled(powerButtonPath, disablePowerButton);
618     error |= setButtonEnabled(resetButtonPath, disableResetButton);
619     error |= setButtonEnabled(interruptButtonPath, disableInterruptButton);
620 
621     if (error)
622     {
623         return ipmi::responseUnspecifiedError();
624     }
625     return ipmi::responseSuccess();
626 }
627 
628 static void registerChassisFunctions(void)
629 {
630     log<level::INFO>("Registering Chassis commands");
631 
632     createIdentifyTimer();
633 
634     if (matchPtr == nullptr)
635     {
636         using namespace sdbusplus::bus::match::rules;
637         auto bus = getSdBus();
638 
639         matchPtr = std::make_unique<sdbusplus::bus::match_t>(
640             *bus,
641             sdbusplus::bus::match::rules::propertiesChanged(idButtonPath,
642                                                             buttonIntf),
643             std::bind(idButtonPropChanged, std::placeholders::_1));
644     }
645 
646     // <Chassis Identify>
647     ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnChassis,
648                           ipmi::chassis::cmdChassisIdentify,
649                           ipmi::Privilege::Operator, ipmiChassisIdentify);
650     // <Get Chassis Status>
651     ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnChassis,
652                           ipmi::chassis::cmdGetChassisStatus,
653                           ipmi::Privilege::User, ipmiGetChassisStatus);
654     // <Get System Restart Cause>
655     ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnChassis,
656                           ipmi::chassis::cmdGetSystemRestartCause,
657                           ipmi::Privilege::User, ipmiGetSystemRestartCause);
658     // <Set Front Panel Enables>
659     ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnChassis,
660                           ipmi::chassis::cmdSetFrontPanelButtonEnables,
661                           ipmi::Privilege::User,
662                           ipmiSetFrontPanelButtonEnables);
663 }
664 
665 } // namespace ipmi::chassis
666