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 = getDbusProperty(*bus, service, objName,
101                                               ledInterface, ledProp);
102         state = std::get<bool>(enabled);
103     }
104     catch (const sdbusplus::exception_t& 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_t& 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 = ipmi::getService(*busp, powerRestoreIntf,
231                                         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 = ipmi::getService(*busp, chassisStateIntf,
281                                         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 (const sdbusplus::exception_t& 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 (const 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(ipmi::Context::ptr& ctx, 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 
377     std::string service;
378     boost::system::error_code ec = ipmi::getService(ctx, restartCauseIntf,
379                                                     restartCausePath, service);
380 
381     if (!ec)
382     {
383         ec = ipmi::getDbusProperty(ctx, service, restartCausePath,
384                                    restartCauseIntf, "RestartCause",
385                                    restartCause);
386     }
387     if (ec)
388     {
389         log<level::ERR>("Failed to fetch RestartCause property",
390                         entry("ERROR=%s", ec.message().c_str()),
391                         entry("PATH=%s", restartCausePath),
392                         entry("INTERFACE=%s", restartCauseIntf));
393         return false;
394     }
395     return true;
396 }
397 
398 static bool checkIPMIRestartCause(ipmi::Context::ptr& ctx,
399                                   bool& ipmiRestartCause)
400 {
401     std::string restartCause;
402     if (!getRestartCause(ctx, restartCause))
403     {
404         return false;
405     }
406     ipmiRestartCause =
407         (restartCause ==
408          "xyz.openbmc_project.State.Host.RestartCause.IpmiCommand");
409     return true;
410 }
411 
412 //----------------------------------------------------------------------
413 // Get Chassis Status commands
414 //----------------------------------------------------------------------
415 ipmi::RspType<bool,    // Power is on
416               bool,    // Power overload
417               bool,    // Interlock
418               bool,    // power fault
419               bool,    // power control fault
420               uint2_t, // power restore policy
421               bool,    // reserved
422 
423               bool,    // AC failed
424               bool,    // last power down caused by a Power overload
425               bool,    // last power down caused by a power interlock
426               bool,    // last power down caused by power fault
427               bool, // last ‘Power is on’ state was entered via IPMI command
428               uint3_t, // reserved
429 
430               bool,    // Chassis intrusion active
431               bool,    // Front Panel Lockout active
432               bool,    // Drive Fault
433               bool,    // Cooling/fan fault detected
434               uint2_t, // Chassis Identify State
435               bool,    // Chassis Identify command and state info supported
436               bool,    // reserved
437 
438               bool,    // Power off button disabled
439               bool,    // Reset button disabled
440               bool,    // Diagnostic Interrupt button disabled
441               bool,    // Standby (sleep) button disabled
442               bool,    // Power off button disable allowed
443               bool,    // Reset button disable allowed
444               bool,    // Diagnostic Interrupt button disable allowed
445               bool     // Standby (sleep) button disable allowed
446               >
447     ipmiGetChassisStatus(ipmi::Context::ptr ctx)
448 {
449     std::optional<uint2_t> restorePolicy =
450         power_policy::getPowerRestorePolicy();
451     std::optional<bool> powerGood = power_policy::getPowerStatus();
452     if (!restorePolicy || !powerGood)
453     {
454         return ipmi::responseUnspecifiedError();
455     }
456 
457     //  Front Panel Button Capabilities and disable/enable status(Optional)
458     std::optional<bool> powerButtonReading = getButtonEnabled(powerButtonPath);
459     // allow disable if the interface is present
460     bool powerButtonDisableAllow = static_cast<bool>(powerButtonReading);
461     // default return the button is enabled (not disabled)
462     bool powerButtonDisabled = false;
463     if (powerButtonDisableAllow)
464     {
465         // return the real value of the button status, if present
466         powerButtonDisabled = *powerButtonReading;
467     }
468 
469     std::optional<bool> resetButtonReading = getButtonEnabled(resetButtonPath);
470     // allow disable if the interface is present
471     bool resetButtonDisableAllow = static_cast<bool>(resetButtonReading);
472     // default return the button is enabled (not disabled)
473     bool resetButtonDisabled = false;
474     if (resetButtonDisableAllow)
475     {
476         // return the real value of the button status, if present
477         resetButtonDisabled = *resetButtonReading;
478     }
479 
480     std::optional<bool> interruptButtonReading =
481         getButtonEnabled(interruptButtonPath);
482     // allow disable if the interface is present
483     bool interruptButtonDisableAllow =
484         static_cast<bool>(interruptButtonReading);
485     // default return the button is enabled (not disabled)
486     bool interruptButtonDisabled = false;
487     if (interruptButtonDisableAllow)
488     {
489         // return the real value of the button status, if present
490         interruptButtonDisabled = *interruptButtonReading;
491     }
492 
493     bool powerDownAcFailed = power_policy::getACFailStatus();
494 
495     bool powerStatusIPMI = false;
496     if (!checkIPMIRestartCause(ctx, powerStatusIPMI))
497     {
498         return ipmi::responseUnspecifiedError();
499     }
500 
501     bool chassisIntrusionActive = false;
502     try
503     {
504         constexpr const char* chassisIntrusionObj =
505             "/xyz/openbmc_project/Intrusion/Chassis_Intrusion";
506         constexpr const char* chassisIntrusionInf =
507             "xyz.openbmc_project.Chassis.Intrusion";
508 
509         std::string intrusionService;
510         boost::system::error_code ec = ipmi::getService(
511             ctx, chassisIntrusionInf, chassisIntrusionObj, intrusionService);
512 
513         chassisIntrusionActive = !intrusionService.empty();
514     }
515     catch (const std::exception& e)
516     {
517         log<level::ERR>("Failed to get Chassis Intrusion service",
518                         entry("ERROR=%s", e.what()));
519     }
520 
521     // This response has a lot of hard-coded, unsupported fields
522     // They are set to false or 0
523     constexpr bool powerOverload = false;
524     constexpr bool chassisInterlock = false;
525     constexpr bool powerFault = false;
526     constexpr bool powerControlFault = false;
527     constexpr bool powerDownOverload = false;
528     constexpr bool powerDownInterlock = false;
529     constexpr bool powerDownPowerFault = false;
530     constexpr bool frontPanelLockoutActive = false;
531     constexpr bool driveFault = false;
532     constexpr bool coolingFanFault = false;
533     // chassisIdentifySupport set because this command is implemented
534     constexpr bool chassisIdentifySupport = true;
535     uint2_t chassisIdentifyState = types::enum_cast<uint2_t>(chassisIDState);
536     constexpr bool sleepButtonDisabled = false;
537     constexpr bool sleepButtonDisableAllow = false;
538 
539     return ipmi::responseSuccess(
540         *powerGood, powerOverload, chassisInterlock, powerFault,
541         powerControlFault, *restorePolicy,
542         false, // reserved
543 
544         powerDownAcFailed, powerDownOverload, powerDownInterlock,
545         powerDownPowerFault, powerStatusIPMI,
546         uint3_t(0), // reserved
547 
548         chassisIntrusionActive, frontPanelLockoutActive, driveFault,
549         coolingFanFault, chassisIdentifyState, chassisIdentifySupport,
550         false, // reserved
551 
552         powerButtonDisabled, resetButtonDisabled, interruptButtonDisabled,
553         sleepButtonDisabled, powerButtonDisableAllow, resetButtonDisableAllow,
554         interruptButtonDisableAllow, sleepButtonDisableAllow);
555 }
556 
557 static uint4_t getRestartCauseValue(const std::string& cause)
558 {
559     uint4_t restartCauseValue = 0;
560     if (cause == "xyz.openbmc_project.State.Host.RestartCause.Unknown")
561     {
562         restartCauseValue = 0x0;
563     }
564     else if (cause == "xyz.openbmc_project.State.Host.RestartCause.IpmiCommand")
565     {
566         restartCauseValue = 0x1;
567     }
568     else if (cause == "xyz.openbmc_project.State.Host.RestartCause.ResetButton")
569     {
570         restartCauseValue = 0x2;
571     }
572     else if (cause == "xyz.openbmc_project.State.Host.RestartCause.PowerButton")
573     {
574         restartCauseValue = 0x3;
575     }
576     else if (cause ==
577              "xyz.openbmc_project.State.Host.RestartCause.WatchdogTimer")
578     {
579         restartCauseValue = 0x4;
580     }
581     else if (cause == "xyz.openbmc_project.State.Host.RestartCause.OEM")
582     {
583         restartCauseValue = 0x5;
584     }
585     else if (cause ==
586              "xyz.openbmc_project.State.Host.RestartCause.PowerPolicyAlwaysOn")
587     {
588         restartCauseValue = 0x6;
589     }
590     else if (cause == "xyz.openbmc_project.State.Host.RestartCause."
591                       "PowerPolicyPreviousState")
592     {
593         restartCauseValue = 0x7;
594     }
595     else if (cause == "xyz.openbmc_project.State.Host.RestartCause.PEFReset")
596     {
597         restartCauseValue = 0x8;
598     }
599     else if (cause ==
600              "xyz.openbmc_project.State.Host.RestartCause.PEFPowerCycle")
601     {
602         restartCauseValue = 0x9;
603     }
604     else if (cause == "xyz.openbmc_project.State.Host.RestartCause.SoftReset")
605     {
606         restartCauseValue = 0xa;
607     }
608     else if (cause == "xyz.openbmc_project.State.Host.RestartCause.RTCWakeup")
609     {
610         restartCauseValue = 0xb;
611     }
612     return restartCauseValue;
613 }
614 
615 ipmi::RspType<uint4_t, // Restart Cause
616               uint4_t, // reserved
617               uint8_t  // channel number
618               >
619     ipmiGetSystemRestartCause(ipmi::Context::ptr ctx)
620 {
621     std::string restartCauseStr;
622     if (!getRestartCause(ctx, restartCauseStr))
623     {
624         return ipmi::responseUnspecifiedError();
625     }
626     constexpr uint4_t reserved = 0;
627     auto channel = static_cast<uint8_t>(ctx->channel);
628     return ipmi::responseSuccess(getRestartCauseValue(restartCauseStr),
629                                  reserved, channel);
630 }
631 
632 ipmi::RspType<> ipmiSetFrontPanelButtonEnables(bool disablePowerButton,
633                                                bool disableResetButton,
634                                                bool disableInterruptButton,
635                                                bool disableSleepButton,
636                                                uint4_t reserved)
637 {
638     if (reserved)
639     {
640         return ipmi::responseInvalidFieldRequest();
641     }
642     bool error = false;
643 
644     error |= setButtonEnabled(powerButtonPath, disablePowerButton);
645     error |= setButtonEnabled(resetButtonPath, disableResetButton);
646     error |= setButtonEnabled(interruptButtonPath, disableInterruptButton);
647 
648     if (error)
649     {
650         return ipmi::responseUnspecifiedError();
651     }
652 
653     return ipmi::responseSuccess();
654 }
655 
656 static void registerChassisFunctions(void)
657 {
658     log<level::INFO>("Registering Chassis commands");
659 
660     createIdentifyTimer();
661 
662     if (matchPtr == nullptr)
663     {
664         using namespace sdbusplus::bus::match::rules;
665         auto bus = getSdBus();
666 
667         matchPtr = std::make_unique<sdbusplus::bus::match_t>(
668             *bus,
669             sdbusplus::bus::match::rules::propertiesChanged(idButtonPath,
670                                                             buttonIntf),
671             std::bind(idButtonPropChanged, std::placeholders::_1));
672     }
673 
674     // <Chassis Identify>
675     ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnChassis,
676                           ipmi::chassis::cmdChassisIdentify,
677                           ipmi::Privilege::Operator, ipmiChassisIdentify);
678     // <Get Chassis Status>
679     ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnChassis,
680                           ipmi::chassis::cmdGetChassisStatus,
681                           ipmi::Privilege::User, ipmiGetChassisStatus);
682     // <Get System Restart Cause>
683     ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnChassis,
684                           ipmi::chassis::cmdGetSystemRestartCause,
685                           ipmi::Privilege::User, ipmiGetSystemRestartCause);
686     // <Set Front Panel Enables>
687     ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnChassis,
688                           ipmi::chassis::cmdSetFrontPanelButtonEnables,
689                           ipmi::Privilege::Admin,
690                           ipmiSetFrontPanelButtonEnables);
691 }
692 
693 } // namespace ipmi::chassis
694