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