1 #include "config.h"
2 
3 #include "chassishandler.hpp"
4 
5 #include <arpa/inet.h>
6 #include <endian.h>
7 #include <limits.h>
8 #include <mapper.h>
9 #include <netinet/in.h>
10 
11 #include <array>
12 #include <chrono>
13 #include <cstring>
14 #include <filesystem>
15 #include <fstream>
16 #include <future>
17 #include <ipmid/api.hpp>
18 #include <ipmid/types.hpp>
19 #include <ipmid/utils.hpp>
20 #include <map>
21 #include <phosphor-logging/elog-errors.hpp>
22 #include <phosphor-logging/log.hpp>
23 #include <sdbusplus/bus.hpp>
24 #include <sdbusplus/message/types.hpp>
25 #include <sdbusplus/server/object.hpp>
26 #include <sdbusplus/timer.hpp>
27 #include <settings.hpp>
28 #include <sstream>
29 #include <string>
30 #include <xyz/openbmc_project/Common/error.hpp>
31 #include <xyz/openbmc_project/Control/Boot/Mode/server.hpp>
32 #include <xyz/openbmc_project/Control/Boot/Source/server.hpp>
33 #include <xyz/openbmc_project/Control/Power/RestorePolicy/server.hpp>
34 #include <xyz/openbmc_project/State/Host/server.hpp>
35 #include <xyz/openbmc_project/State/PowerOnHours/server.hpp>
36 
37 // Defines
38 #define SET_PARM_VERSION 0x01
39 #define SET_PARM_BOOT_FLAGS_PERMANENT 0x40
40 #define SET_PARM_BOOT_FLAGS_VALID_ONE_TIME 0x80
41 #define SET_PARM_BOOT_FLAGS_VALID_PERMANENT 0xC0
42 
43 std::unique_ptr<phosphor::Timer> identifyTimer
44     __attribute__((init_priority(101)));
45 
46 constexpr size_t SIZE_MAC = 18;
47 constexpr size_t SIZE_BOOT_OPTION = (uint8_t)
48     BootOptionResponseSize::OPAL_NETWORK_SETTINGS; // Maximum size of the boot
49                                                    // option parametrs
50 constexpr size_t SIZE_PREFIX = 7;
51 constexpr size_t MAX_PREFIX_VALUE = 32;
52 constexpr size_t SIZE_COOKIE = 4;
53 constexpr size_t SIZE_VERSION = 2;
54 constexpr size_t DEFAULT_IDENTIFY_TIME_OUT = 15;
55 
56 // PetiBoot-Specific
57 static constexpr uint8_t net_conf_initial_bytes[] = {0x80, 0x21, 0x70, 0x62,
58                                                      0x21, 0x00, 0x01, 0x06};
59 
60 static constexpr size_t COOKIE_OFFSET = 1;
61 static constexpr size_t VERSION_OFFSET = 5;
62 static constexpr size_t ADDR_SIZE_OFFSET = 8;
63 static constexpr size_t MAC_OFFSET = 9;
64 static constexpr size_t ADDRTYPE_OFFSET = 16;
65 static constexpr size_t IPADDR_OFFSET = 17;
66 
67 static constexpr size_t encIdentifyObjectsSize = 1;
68 static constexpr size_t chassisIdentifyReqLength = 2;
69 static constexpr size_t identifyIntervalPos = 0;
70 static constexpr size_t forceIdentifyPos = 1;
71 
72 void register_netfn_chassis_functions() __attribute__((constructor));
73 
74 // Host settings in dbus
75 // Service name should be referenced by connection name got via object mapper
76 const char* settings_object_name = "/org/openbmc/settings/host0";
77 const char* settings_intf_name = "org.freedesktop.DBus.Properties";
78 const char* identify_led_object_name =
79     "/xyz/openbmc_project/led/groups/enclosure_identify";
80 
81 constexpr auto SETTINGS_ROOT = "/";
82 constexpr auto SETTINGS_MATCH = "host0";
83 
84 constexpr auto IP_INTERFACE = "xyz.openbmc_project.Network.IP";
85 constexpr auto MAC_INTERFACE = "xyz.openbmc_project.Network.MACAddress";
86 
87 static constexpr auto chassisStateRoot = "/xyz/openbmc_project/state";
88 static constexpr auto chassisPOHStateIntf =
89     "xyz.openbmc_project.State.PowerOnHours";
90 static constexpr auto pOHCounterProperty = "POHCounter";
91 static constexpr auto match = "chassis0";
92 const static constexpr char chassisCapIntf[] =
93     "xyz.openbmc_project.Control.ChassisCapabilities";
94 const static constexpr char chassisCapFlagsProp[] = "CapabilitiesFlags";
95 const static constexpr char chassisFRUDevAddrProp[] = "FRUDeviceAddress";
96 const static constexpr char chassisSDRDevAddrProp[] = "SDRDeviceAddress";
97 const static constexpr char chassisSELDevAddrProp[] = "SELDeviceAddress";
98 const static constexpr char chassisSMDevAddrProp[] = "SMDeviceAddress";
99 const static constexpr char chassisBridgeDevAddrProp[] = "BridgeDeviceAddress";
100 static constexpr uint8_t chassisCapFlagMask = 0x0f;
101 static constexpr uint8_t chassisCapAddrMask = 0xfe;
102 static constexpr const char* powerButtonIntf =
103     "xyz.openbmc_project.Chassis.Buttons.Power";
104 static constexpr const char* powerButtonPath =
105     "/xyz/openbmc_project/Chassis/Buttons/Power0";
106 static constexpr const char* resetButtonIntf =
107     "xyz.openbmc_project.Chassis.Buttons.Reset";
108 static constexpr const char* resetButtonPath =
109     "/xyz/openbmc_project/Chassis/Buttons/Reset0";
110 
111 typedef struct
112 {
113     uint8_t cap_flags;
114     uint8_t fru_info_dev_addr;
115     uint8_t sdr_dev_addr;
116     uint8_t sel_dev_addr;
117     uint8_t system_management_dev_addr;
118     uint8_t bridge_dev_addr;
119 } __attribute__((packed)) ipmi_chassis_cap_t;
120 
121 typedef struct
122 {
123     uint8_t cur_power_state;
124     uint8_t last_power_event;
125     uint8_t misc_power_state;
126     uint8_t front_panel_button_cap_status;
127 } __attribute__((packed)) ipmi_get_chassis_status_t;
128 
129 // Phosphor Host State manager
130 namespace State = sdbusplus::xyz::openbmc_project::State::server;
131 
132 namespace fs = std::filesystem;
133 
134 using namespace phosphor::logging;
135 using namespace sdbusplus::xyz::openbmc_project::Common::Error;
136 using namespace sdbusplus::xyz::openbmc_project::Control::Boot::server;
137 
138 namespace chassis
139 {
140 namespace internal
141 {
142 
143 constexpr auto bootModeIntf = "xyz.openbmc_project.Control.Boot.Mode";
144 constexpr auto bootSourceIntf = "xyz.openbmc_project.Control.Boot.Source";
145 constexpr auto powerRestoreIntf =
146     "xyz.openbmc_project.Control.Power.RestorePolicy";
147 sdbusplus::bus::bus dbus(ipmid_get_sd_bus_connection());
148 
149 namespace cache
150 {
151 
152 settings::Objects objects(dbus,
153                           {bootModeIntf, bootSourceIntf, powerRestoreIntf});
154 
155 } // namespace cache
156 } // namespace internal
157 } // namespace chassis
158 
159 namespace poh
160 {
161 
162 constexpr auto minutesPerCount = 60;
163 
164 } // namespace poh
165 
166 struct get_sys_boot_options_t
167 {
168     uint8_t parameter;
169     uint8_t set;
170     uint8_t block;
171 } __attribute__((packed));
172 
173 struct get_sys_boot_options_response_t
174 {
175     uint8_t version;
176     uint8_t parm;
177     uint8_t data[SIZE_BOOT_OPTION];
178 } __attribute__((packed));
179 
180 struct set_sys_boot_options_t
181 {
182     uint8_t parameter;
183     uint8_t data[SIZE_BOOT_OPTION];
184 } __attribute__((packed));
185 
186 int getHostNetworkData(get_sys_boot_options_response_t* respptr)
187 {
188     ipmi::PropertyMap properties;
189     int rc = 0;
190     uint8_t addrSize = ipmi::network::IPV4_ADDRESS_SIZE_BYTE;
191 
192     try
193     {
194         // TODO There may be cases where an interface is implemented by multiple
195         // objects,to handle such cases we are interested on that object
196         //  which are on interested busname.
197         //  Currenlty mapper doesn't give the readable busname(gives busid)
198         //  so we can't match with bus name so giving some object specific info
199         //  as SETTINGS_MATCH.
200         //  Later SETTINGS_MATCH will be replaced with busname.
201 
202         sdbusplus::bus::bus bus(ipmid_get_sd_bus_connection());
203 
204         auto ipObjectInfo = ipmi::getDbusObject(bus, IP_INTERFACE,
205                                                 SETTINGS_ROOT, SETTINGS_MATCH);
206 
207         auto macObjectInfo = ipmi::getDbusObject(bus, MAC_INTERFACE,
208                                                  SETTINGS_ROOT, SETTINGS_MATCH);
209 
210         properties = ipmi::getAllDbusProperties(
211             bus, ipObjectInfo.second, ipObjectInfo.first, IP_INTERFACE);
212         auto variant = ipmi::getDbusProperty(bus, macObjectInfo.second,
213                                              macObjectInfo.first, MAC_INTERFACE,
214                                              "MACAddress");
215 
216         auto ipAddress = std::get<std::string>(properties["Address"]);
217 
218         auto gateway = std::get<std::string>(properties["Gateway"]);
219 
220         auto prefix = std::get<uint8_t>(properties["PrefixLength"]);
221 
222         uint8_t isStatic =
223             (std::get<std::string>(properties["Origin"]) ==
224              "xyz.openbmc_project.Network.IP.AddressOrigin.Static")
225                 ? 1
226                 : 0;
227 
228         auto MACAddress = std::get<std::string>(variant);
229 
230         // it is expected here that we should get the valid data
231         // but we may also get the default values.
232         // Validation of the data is done by settings.
233         //
234         // if mac address is default mac address then
235         // don't send blank override.
236         if ((MACAddress == ipmi::network::DEFAULT_MAC_ADDRESS))
237         {
238             std::memset(respptr->data, 0, SIZE_BOOT_OPTION);
239             rc = -1;
240             return rc;
241         }
242         // if addr is static then ipaddress,gateway,prefix
243         // should not be default one,don't send blank override.
244         if (isStatic)
245         {
246             if ((ipAddress == ipmi::network::DEFAULT_ADDRESS) ||
247                 (gateway == ipmi::network::DEFAULT_ADDRESS) || (!prefix))
248             {
249                 std::memset(respptr->data, 0, SIZE_BOOT_OPTION);
250                 rc = -1;
251                 return rc;
252             }
253         }
254 
255         sscanf(
256             MACAddress.c_str(), ipmi::network::MAC_ADDRESS_FORMAT,
257             (respptr->data + MAC_OFFSET), (respptr->data + MAC_OFFSET + 1),
258             (respptr->data + MAC_OFFSET + 2), (respptr->data + MAC_OFFSET + 3),
259             (respptr->data + MAC_OFFSET + 4), (respptr->data + MAC_OFFSET + 5));
260 
261         respptr->data[MAC_OFFSET + 6] = 0x00;
262 
263         std::memcpy(respptr->data + ADDRTYPE_OFFSET, &isStatic,
264                     sizeof(isStatic));
265 
266         uint8_t addressFamily = (std::get<std::string>(properties["Type"]) ==
267                                  "xyz.openbmc_project.Network.IP.Protocol.IPv4")
268                                     ? AF_INET
269                                     : AF_INET6;
270 
271         addrSize = (addressFamily == AF_INET)
272                        ? ipmi::network::IPV4_ADDRESS_SIZE_BYTE
273                        : ipmi::network::IPV6_ADDRESS_SIZE_BYTE;
274 
275         // ipaddress and gateway would be in IPv4 format
276         inet_pton(addressFamily, ipAddress.c_str(),
277                   (respptr->data + IPADDR_OFFSET));
278 
279         uint8_t prefixOffset = IPADDR_OFFSET + addrSize;
280 
281         std::memcpy(respptr->data + prefixOffset, &prefix, sizeof(prefix));
282 
283         uint8_t gatewayOffset = prefixOffset + sizeof(decltype(prefix));
284 
285         inet_pton(addressFamily, gateway.c_str(),
286                   (respptr->data + gatewayOffset));
287     }
288     catch (InternalFailure& e)
289     {
290         commit<InternalFailure>();
291         std::memset(respptr->data, 0, SIZE_BOOT_OPTION);
292         rc = -1;
293         return rc;
294     }
295 
296     // PetiBoot-Specific
297     // If success then copy the first 9 bytes to the data
298     std::memcpy(respptr->data, net_conf_initial_bytes,
299                 sizeof(net_conf_initial_bytes));
300 
301     std::memcpy(respptr->data + ADDR_SIZE_OFFSET, &addrSize, sizeof(addrSize));
302 
303 #ifdef _IPMI_DEBUG_
304     std::printf("\n===Printing the IPMI Formatted Data========\n");
305 
306     for (uint8_t pos = 0; pos < index; pos++)
307     {
308         std::printf("%02x ", respptr->data[pos]);
309     }
310 #endif
311 
312     return rc;
313 }
314 
315 /** @brief convert IPv4 and IPv6 addresses from binary to text form.
316  *  @param[in] family - IPv4/Ipv6
317  *  @param[in] data - req data pointer.
318  *  @param[in] offset - offset in the data.
319  *  @param[in] addrSize - size of the data which needs to be read from offset.
320  *  @returns address in text form.
321  */
322 
323 std::string getAddrStr(uint8_t family, uint8_t* data, uint8_t offset,
324                        uint8_t addrSize)
325 {
326     char ipAddr[INET6_ADDRSTRLEN] = {};
327 
328     switch (family)
329     {
330         case AF_INET:
331         {
332             struct sockaddr_in addr4
333             {
334             };
335             std::memcpy(&addr4.sin_addr.s_addr, &data[offset], addrSize);
336 
337             inet_ntop(AF_INET, &addr4.sin_addr, ipAddr, INET_ADDRSTRLEN);
338 
339             break;
340         }
341         case AF_INET6:
342         {
343             struct sockaddr_in6 addr6
344             {
345             };
346             std::memcpy(&addr6.sin6_addr.s6_addr, &data[offset], addrSize);
347 
348             inet_ntop(AF_INET6, &addr6.sin6_addr, ipAddr, INET6_ADDRSTRLEN);
349 
350             break;
351         }
352         default:
353         {
354             return {};
355         }
356     }
357 
358     return ipAddr;
359 }
360 
361 int setHostNetworkData(set_sys_boot_options_t* reqptr)
362 {
363     using namespace std::string_literals;
364     std::string host_network_config;
365     char mac[]{"00:00:00:00:00:00"};
366     std::string ipAddress, gateway;
367     char addrOrigin{0};
368     uint8_t addrSize{0};
369     std::string addressOrigin =
370         "xyz.openbmc_project.Network.IP.AddressOrigin.DHCP";
371     std::string addressType = "xyz.openbmc_project.Network.IP.Protocol.IPv4";
372     uint8_t prefix{0};
373     uint32_t zeroCookie = 0;
374     uint8_t family = AF_INET;
375 
376     // cookie starts from second byte
377     // version starts from sixth byte
378 
379     try
380     {
381         do
382         {
383             // cookie ==  0x21 0x70 0x62 0x21
384             if (memcmp(&(reqptr->data[COOKIE_OFFSET]),
385                        (net_conf_initial_bytes + COOKIE_OFFSET),
386                        SIZE_COOKIE) != 0)
387             {
388                 // cookie == 0
389                 if (memcmp(&(reqptr->data[COOKIE_OFFSET]), &zeroCookie,
390                            SIZE_COOKIE) == 0)
391                 {
392                     // need to zero out the network settings.
393                     break;
394                 }
395 
396                 log<level::ERR>("Invalid Cookie");
397                 elog<InternalFailure>();
398             }
399 
400             // vesion == 0x00 0x01
401             if (memcmp(&(reqptr->data[VERSION_OFFSET]),
402                        (net_conf_initial_bytes + VERSION_OFFSET),
403                        SIZE_VERSION) != 0)
404             {
405 
406                 log<level::ERR>("Invalid Version");
407                 elog<InternalFailure>();
408             }
409 
410             std::snprintf(
411                 mac, SIZE_MAC, ipmi::network::MAC_ADDRESS_FORMAT,
412                 reqptr->data[MAC_OFFSET], reqptr->data[MAC_OFFSET + 1],
413                 reqptr->data[MAC_OFFSET + 2], reqptr->data[MAC_OFFSET + 3],
414                 reqptr->data[MAC_OFFSET + 4], reqptr->data[MAC_OFFSET + 5]);
415 
416             std::memcpy(&addrOrigin, &(reqptr->data[ADDRTYPE_OFFSET]),
417                         sizeof(decltype(addrOrigin)));
418 
419             if (addrOrigin)
420             {
421                 addressOrigin =
422                     "xyz.openbmc_project.Network.IP.AddressOrigin.Static";
423             }
424 
425             // Get the address size
426             std::memcpy(&addrSize, &reqptr->data[ADDR_SIZE_OFFSET],
427                         sizeof(addrSize));
428 
429             uint8_t prefixOffset = IPADDR_OFFSET + addrSize;
430 
431             std::memcpy(&prefix, &(reqptr->data[prefixOffset]),
432                         sizeof(decltype(prefix)));
433 
434             uint8_t gatewayOffset = prefixOffset + sizeof(decltype(prefix));
435 
436             if (addrSize != ipmi::network::IPV4_ADDRESS_SIZE_BYTE)
437             {
438                 addressType = "xyz.openbmc_project.Network.IP.Protocol.IPv6";
439                 family = AF_INET6;
440             }
441 
442             ipAddress =
443                 getAddrStr(family, reqptr->data, IPADDR_OFFSET, addrSize);
444 
445             gateway = getAddrStr(family, reqptr->data, gatewayOffset, addrSize);
446 
447         } while (0);
448 
449         // Cookie == 0 or it is a valid cookie
450         host_network_config += "ipaddress="s + ipAddress + ",prefix="s +
451                                std::to_string(prefix) + ",gateway="s + gateway +
452                                ",mac="s + mac + ",addressOrigin="s +
453                                addressOrigin;
454 
455         sdbusplus::bus::bus bus(ipmid_get_sd_bus_connection());
456 
457         auto ipObjectInfo = ipmi::getDbusObject(bus, IP_INTERFACE,
458                                                 SETTINGS_ROOT, SETTINGS_MATCH);
459         auto macObjectInfo = ipmi::getDbusObject(bus, MAC_INTERFACE,
460                                                  SETTINGS_ROOT, SETTINGS_MATCH);
461         // set the dbus property
462         ipmi::setDbusProperty(bus, ipObjectInfo.second, ipObjectInfo.first,
463                               IP_INTERFACE, "Address", std::string(ipAddress));
464         ipmi::setDbusProperty(bus, ipObjectInfo.second, ipObjectInfo.first,
465                               IP_INTERFACE, "PrefixLength", prefix);
466         ipmi::setDbusProperty(bus, ipObjectInfo.second, ipObjectInfo.first,
467                               IP_INTERFACE, "Origin", addressOrigin);
468         ipmi::setDbusProperty(bus, ipObjectInfo.second, ipObjectInfo.first,
469                               IP_INTERFACE, "Gateway", std::string(gateway));
470         ipmi::setDbusProperty(
471             bus, ipObjectInfo.second, ipObjectInfo.first, IP_INTERFACE, "Type",
472             std::string("xyz.openbmc_project.Network.IP.Protocol.IPv4"));
473         ipmi::setDbusProperty(bus, macObjectInfo.second, macObjectInfo.first,
474                               MAC_INTERFACE, "MACAddress", std::string(mac));
475 
476         log<level::DEBUG>(
477             "Network configuration changed",
478             entry("NETWORKCONFIG=%s", host_network_config.c_str()));
479     }
480     catch (InternalFailure& e)
481     {
482         commit<InternalFailure>();
483         return -1;
484     }
485 
486     return 0;
487 }
488 
489 uint32_t getPOHCounter()
490 {
491     sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()};
492 
493     auto chassisStateObj =
494         ipmi::getDbusObject(bus, chassisPOHStateIntf, chassisStateRoot, match);
495 
496     auto service =
497         ipmi::getService(bus, chassisPOHStateIntf, chassisStateObj.first);
498 
499     auto propValue =
500         ipmi::getDbusProperty(bus, service, chassisStateObj.first,
501                               chassisPOHStateIntf, pOHCounterProperty);
502 
503     return std::get<uint32_t>(propValue);
504 }
505 
506 ipmi_ret_t ipmi_chassis_wildcard(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
507                                  ipmi_request_t request,
508                                  ipmi_response_t response,
509                                  ipmi_data_len_t data_len,
510                                  ipmi_context_t context)
511 {
512     // Status code.
513     ipmi_ret_t rc = IPMI_CC_INVALID;
514     *data_len = 0;
515     return rc;
516 }
517 
518 ipmi_ret_t ipmi_get_chassis_cap(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
519                                 ipmi_request_t request,
520                                 ipmi_response_t response,
521                                 ipmi_data_len_t data_len,
522                                 ipmi_context_t context)
523 {
524     // sd_bus error
525     ipmi_ret_t rc = IPMI_CC_OK;
526 
527     ipmi_chassis_cap_t chassis_cap{};
528 
529     if (*data_len != 0)
530     {
531         return IPMI_CC_REQ_DATA_LEN_INVALID;
532     }
533 
534     *data_len = sizeof(ipmi_chassis_cap_t);
535 
536     try
537     {
538         sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()};
539 
540         ipmi::DbusObjectInfo chassisCapObject =
541             ipmi::getDbusObject(bus, chassisCapIntf);
542 
543         // capabilities flags
544         // [7..4] - reserved
545         // [3] – 1b = provides power interlock  (IPM 1.5)
546         // [2] – 1b = provides Diagnostic Interrupt (FP NMI)
547         // [1] – 1b = provides “Front Panel Lockout” (indicates that the chassis
548         // has capabilities
549         //            to lock out external power control and reset button or
550         //            front panel interfaces and/or detect tampering with those
551         //            interfaces).
552         // [0] -1b = Chassis provides intrusion (physical security) sensor.
553         // set to default value 0x0.
554         ipmi::Value variant = ipmi::getDbusProperty(
555             bus, chassisCapObject.second, chassisCapObject.first,
556             chassisCapIntf, chassisCapFlagsProp);
557         chassis_cap.cap_flags = std::get<uint8_t>(variant);
558 
559         variant = ipmi::getDbusProperty(bus, chassisCapObject.second,
560                                         chassisCapObject.first, chassisCapIntf,
561                                         chassisFRUDevAddrProp);
562         // Chassis FRU info Device Address.
563         chassis_cap.fru_info_dev_addr = std::get<uint8_t>(variant);
564 
565         variant = ipmi::getDbusProperty(bus, chassisCapObject.second,
566                                         chassisCapObject.first, chassisCapIntf,
567                                         chassisSDRDevAddrProp);
568         // Chassis SDR Device Address.
569         chassis_cap.sdr_dev_addr = std::get<uint8_t>(variant);
570 
571         variant = ipmi::getDbusProperty(bus, chassisCapObject.second,
572                                         chassisCapObject.first, chassisCapIntf,
573                                         chassisSELDevAddrProp);
574         // Chassis SEL Device Address.
575         chassis_cap.sel_dev_addr = std::get<uint8_t>(variant);
576 
577         variant = ipmi::getDbusProperty(bus, chassisCapObject.second,
578                                         chassisCapObject.first, chassisCapIntf,
579                                         chassisSMDevAddrProp);
580         // Chassis System Management Device Address.
581         chassis_cap.system_management_dev_addr = std::get<uint8_t>(variant);
582 
583         variant = ipmi::getDbusProperty(bus, chassisCapObject.second,
584                                         chassisCapObject.first, chassisCapIntf,
585                                         chassisBridgeDevAddrProp);
586         // Chassis Bridge Device Address.
587         chassis_cap.bridge_dev_addr = std::get<uint8_t>(variant);
588         uint8_t* respP = reinterpret_cast<uint8_t*>(response);
589         uint8_t* chassisP = reinterpret_cast<uint8_t*>(&chassis_cap);
590         std::copy(chassisP, chassisP + *data_len, respP);
591     }
592     catch (std::exception& e)
593     {
594         log<level::ERR>(e.what());
595         rc = IPMI_CC_UNSPECIFIED_ERROR;
596         *data_len = 0;
597         return rc;
598     }
599 
600     return rc;
601 }
602 
603 ipmi_ret_t ipmi_set_chassis_cap(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
604                                 ipmi_request_t request,
605                                 ipmi_response_t response,
606                                 ipmi_data_len_t data_len,
607                                 ipmi_context_t context)
608 {
609     ipmi_ret_t rc = IPMI_CC_OK;
610 
611     if (*data_len != sizeof(ipmi_chassis_cap_t))
612     {
613         log<level::ERR>("Unsupported request length",
614                         entry("LEN=0x%x", *data_len));
615         *data_len = 0;
616         return IPMI_CC_REQ_DATA_LEN_INVALID;
617     }
618 
619     ipmi_chassis_cap_t* chassisCap = static_cast<ipmi_chassis_cap_t*>(request);
620 
621     *data_len = 0;
622 
623     // check input data
624     if (0 != (chassisCap->cap_flags & ~chassisCapFlagMask))
625     {
626         log<level::ERR>("Unsupported request parameter(CAP Flags)",
627                         entry("REQ=0x%x", chassisCap->cap_flags));
628         return IPMI_CC_INVALID_FIELD_REQUEST;
629     }
630 
631     if (0 != (chassisCap->fru_info_dev_addr & ~chassisCapAddrMask))
632     {
633         log<level::ERR>("Unsupported request parameter(FRU Addr)",
634                         entry("REQ=0x%x", chassisCap->fru_info_dev_addr));
635         return IPMI_CC_INVALID_FIELD_REQUEST;
636     }
637 
638     if (0 != (chassisCap->sdr_dev_addr & ~chassisCapAddrMask))
639     {
640         log<level::ERR>("Unsupported request parameter(SDR Addr)",
641                         entry("REQ=0x%x", chassisCap->sdr_dev_addr));
642         return IPMI_CC_INVALID_FIELD_REQUEST;
643     }
644 
645     if (0 != (chassisCap->sel_dev_addr & ~chassisCapAddrMask))
646     {
647         log<level::ERR>("Unsupported request parameter(SEL Addr)",
648                         entry("REQ=0x%x", chassisCap->sel_dev_addr));
649         return IPMI_CC_INVALID_FIELD_REQUEST;
650     }
651 
652     if (0 != (chassisCap->system_management_dev_addr & ~chassisCapAddrMask))
653     {
654         log<level::ERR>(
655             "Unsupported request parameter(SM Addr)",
656             entry("REQ=0x%x", chassisCap->system_management_dev_addr));
657         return IPMI_CC_INVALID_FIELD_REQUEST;
658     }
659 
660     if (0 != (chassisCap->bridge_dev_addr & ~chassisCapAddrMask))
661     {
662         log<level::ERR>("Unsupported request parameter(Bridge Addr)",
663                         entry("REQ=0x%x", chassisCap->bridge_dev_addr));
664         return IPMI_CC_INVALID_FIELD_REQUEST;
665     }
666 
667     try
668     {
669         sdbusplus::bus::bus bus(ipmid_get_sd_bus_connection());
670         ipmi::DbusObjectInfo chassisCapObject =
671             ipmi::getDbusObject(bus, chassisCapIntf);
672 
673         ipmi::setDbusProperty(bus, chassisCapObject.second,
674                               chassisCapObject.first, chassisCapIntf,
675                               chassisCapFlagsProp, chassisCap->cap_flags);
676 
677         ipmi::setDbusProperty(bus, chassisCapObject.second,
678                               chassisCapObject.first, chassisCapIntf,
679                               chassisFRUDevAddrProp,
680                               chassisCap->fru_info_dev_addr);
681 
682         ipmi::setDbusProperty(bus, chassisCapObject.second,
683                               chassisCapObject.first, chassisCapIntf,
684                               chassisSDRDevAddrProp, chassisCap->sdr_dev_addr);
685 
686         ipmi::setDbusProperty(bus, chassisCapObject.second,
687                               chassisCapObject.first, chassisCapIntf,
688                               chassisSELDevAddrProp, chassisCap->sel_dev_addr);
689 
690         ipmi::setDbusProperty(bus, chassisCapObject.second,
691                               chassisCapObject.first, chassisCapIntf,
692                               chassisSMDevAddrProp,
693                               chassisCap->system_management_dev_addr);
694 
695         ipmi::setDbusProperty(bus, chassisCapObject.second,
696                               chassisCapObject.first, chassisCapIntf,
697                               chassisBridgeDevAddrProp,
698                               chassisCap->bridge_dev_addr);
699     }
700     catch (std::exception& e)
701     {
702         log<level::ERR>(e.what());
703         rc = IPMI_CC_UNSPECIFIED_ERROR;
704         return rc;
705     }
706 
707     return rc;
708 }
709 
710 //------------------------------------------
711 // Calls into Host State Manager Dbus object
712 //------------------------------------------
713 int initiate_state_transition(State::Host::Transition transition)
714 {
715     // OpenBMC Host State Manager dbus framework
716     constexpr auto HOST_STATE_MANAGER_ROOT = "/xyz/openbmc_project/state/host0";
717     constexpr auto HOST_STATE_MANAGER_IFACE = "xyz.openbmc_project.State.Host";
718     constexpr auto DBUS_PROPERTY_IFACE = "org.freedesktop.DBus.Properties";
719     constexpr auto PROPERTY = "RequestedHostTransition";
720 
721     // sd_bus error
722     int rc = 0;
723     char* busname = NULL;
724 
725     // SD Bus error report mechanism.
726     sd_bus_error bus_error = SD_BUS_ERROR_NULL;
727 
728     // Gets a hook onto either a SYSTEM or SESSION bus
729     sd_bus* bus_type = ipmid_get_sd_bus_connection();
730     rc = mapper_get_service(bus_type, HOST_STATE_MANAGER_ROOT, &busname);
731     if (rc < 0)
732     {
733         log<level::ERR>(
734             "Failed to get bus name",
735             entry("ERRNO=0x%X, OBJPATH=%s", -rc, HOST_STATE_MANAGER_ROOT));
736         return rc;
737     }
738 
739     // Convert to string equivalent of the passed in transition enum.
740     auto request = State::convertForMessage(transition);
741 
742     rc = sd_bus_call_method(bus_type,                // On the system bus
743                             busname,                 // Service to contact
744                             HOST_STATE_MANAGER_ROOT, // Object path
745                             DBUS_PROPERTY_IFACE,     // Interface name
746                             "Set",                   // Method to be called
747                             &bus_error,              // object to return error
748                             nullptr,                 // Response buffer if any
749                             "ssv",                   // Takes 3 arguments
750                             HOST_STATE_MANAGER_IFACE, PROPERTY, "s",
751                             request.c_str());
752     if (rc < 0)
753     {
754         log<level::ERR>("Failed to initiate transition",
755                         entry("ERRNO=0x%X, REQUEST=%s", -rc, request.c_str()));
756     }
757     else
758     {
759         log<level::INFO>("Transition request initiated successfully");
760     }
761 
762     sd_bus_error_free(&bus_error);
763     free(busname);
764 
765     return rc;
766 }
767 
768 namespace power_policy
769 {
770 
771 using namespace sdbusplus::xyz::openbmc_project::Control::Power::server;
772 using IpmiValue = uint8_t;
773 using DbusValue = RestorePolicy::Policy;
774 
775 const std::map<DbusValue, IpmiValue> dbusToIpmi = {
776     {RestorePolicy::Policy::AlwaysOff, 0x00},
777     {RestorePolicy::Policy::Restore, 0x01},
778     {RestorePolicy::Policy::AlwaysOn, 0x02}};
779 
780 static constexpr uint8_t noChange = 0x03;
781 static constexpr uint8_t allSupport = 0x01 | 0x02 | 0x04;
782 static constexpr uint8_t policyBitMask = 0x07;
783 static constexpr uint8_t setPolicyReqLen = 1;
784 
785 /* helper function for Get Chassis Status Command
786  */
787 std::optional<uint2_t> getPowerRestorePolicy()
788 {
789     uint2_t restorePolicy = 0;
790     using namespace chassis::internal;
791 
792     try
793     {
794         const auto& powerRestoreSetting =
795             cache::objects.map.at(powerRestoreIntf).front();
796         ipmi::Value result = ipmi::getDbusProperty(
797             *getSdBus(),
798             cache::objects.service(powerRestoreSetting, powerRestoreIntf)
799                 .c_str(),
800             powerRestoreSetting.c_str(), powerRestoreIntf,
801             "PowerRestorePolicy");
802         auto powerRestore = RestorePolicy::convertPolicyFromString(
803             std::get<std::string>(result));
804         restorePolicy = dbusToIpmi.at(powerRestore);
805     }
806     catch (const std::exception& e)
807     {
808         log<level::ERR>(
809             "Failed to fetch pgood property", entry("ERROR=%s", e.what()),
810             entry("PATH=%s",
811                   cache::objects.map.at(powerRestoreIntf).front().c_str()),
812             entry("INTERFACE=%s", powerRestoreIntf));
813         return std::nullopt;
814     }
815     return std::make_optional(restorePolicy);
816 }
817 
818 /*
819  * getPowerStatus
820  * helper function for Get Chassis Status Command
821  * return - optional value for pgood (no value on error)
822  */
823 std::optional<bool> getPowerStatus()
824 {
825     constexpr const char* powerControlObj =
826         "/xyz/openbmc_project/Chassis/Control/Power0";
827     constexpr const char* powerControlIntf =
828         "xyz.openbmc_project.Chassis.Control.Power";
829     bool powerGood = false;
830     std::shared_ptr<sdbusplus::asio::connection> busp = getSdBus();
831     try
832     {
833         auto service =
834             ipmi::getService(*busp, powerControlIntf, powerControlObj);
835 
836         ipmi::Value variant = ipmi::getDbusProperty(
837             *busp, service, powerControlObj, powerControlIntf, "pgood");
838         powerGood = static_cast<bool>(std::get<int>(variant));
839     }
840     catch (const std::exception& e)
841     {
842         try
843         {
844             // FIXME: some legacy modules use the older path; try that next
845             constexpr const char* legacyPwrCtrlObj =
846                 "/org/openbmc/control/power0";
847             constexpr const char* legacyPwrCtrlIntf =
848                 "org.openbmc.control.Power";
849             auto service =
850                 ipmi::getService(*busp, legacyPwrCtrlIntf, legacyPwrCtrlObj);
851 
852             ipmi::Value variant = ipmi::getDbusProperty(
853                 *busp, service, legacyPwrCtrlObj, legacyPwrCtrlIntf, "pgood");
854             powerGood = static_cast<bool>(std::get<int>(variant));
855         }
856         catch (const std::exception& e)
857         {
858             log<level::ERR>("Failed to fetch pgood property",
859                             entry("ERROR=%s", e.what()),
860                             entry("PATH=%s", powerControlObj),
861                             entry("INTERFACE=%s", powerControlIntf));
862             return std::nullopt;
863         }
864     }
865     return std::make_optional(powerGood);
866 }
867 
868 } // namespace power_policy
869 
870 static std::optional<bool> getButtonEnabled(const std::string& buttonPath,
871                                             const std::string& buttonIntf)
872 {
873     std::shared_ptr<sdbusplus::asio::connection> busp = getSdBus();
874     bool buttonDisabled = false;
875     try
876     {
877         auto service = ipmi::getService(*busp, buttonIntf, buttonPath);
878         ipmi::Value enabled = ipmi::getDbusProperty(*busp, service, buttonPath,
879                                                     buttonIntf, "Enabled");
880         buttonDisabled = !std::get<bool>(enabled);
881     }
882     catch (sdbusplus::exception::SdBusError& e)
883     {
884         log<level::ERR>("Fail to get button Enabled property",
885                         entry("PATH=%s", buttonPath.c_str()),
886                         entry("ERROR=%s", e.what()));
887         return std::nullopt;
888     }
889     return std::make_optional(buttonDisabled);
890 }
891 
892 //----------------------------------------------------------------------
893 // Get Chassis Status commands
894 //----------------------------------------------------------------------
895 ipmi::RspType<bool,    // Power is on
896               bool,    // Power overload
897               bool,    // Interlock
898               bool,    // power fault
899               bool,    // power control fault
900               uint2_t, // power restore policy
901               bool,    // reserved
902 
903               bool, // AC failed
904               bool, // last power down caused by a Power overload
905               bool, // last power down caused by a power interlock
906               bool, // last power down caused by power fault
907               bool, // last ‘Power is on’ state was entered via IPMI command
908               uint3_t, // reserved
909 
910               bool,    // Chassis intrusion active
911               bool,    // Front Panel Lockout active
912               bool,    // Drive Fault
913               bool,    // Cooling/fan fault detected
914               uint2_t, // Chassis Identify State
915               bool,    // Chassis Identify command and state info supported
916               bool,    // reserved
917 
918               bool, // Power off button disabled
919               bool, // Reset button disabled
920               bool, // Diagnostic Interrupt button disabled
921               bool, // Standby (sleep) button disabled
922               bool, // Power off button disable allowed
923               bool, // Reset button disable allowed
924               bool, // Diagnostic Interrupt button disable allowed
925               bool  // Standby (sleep) button disable allowed
926               >
927     ipmiGetChassisStatus()
928 {
929     using namespace chassis::internal;
930     std::optional<uint2_t> restorePolicy =
931         power_policy::getPowerRestorePolicy();
932     std::optional<bool> powerGood = power_policy::getPowerStatus();
933     if (!restorePolicy || !powerGood)
934     {
935         return ipmi::responseUnspecifiedError();
936     }
937 
938     //  Front Panel Button Capabilities and disable/enable status(Optional)
939     std::optional<bool> powerButtonReading =
940         getButtonEnabled(powerButtonPath, powerButtonIntf);
941     // allow disable if the interface is present
942     bool powerButtonDisableAllow = static_cast<bool>(powerButtonReading);
943     // default return the button is enabled (not disabled)
944     bool powerButtonDisabled = false;
945     if (powerButtonDisableAllow)
946     {
947         // return the real value of the button status, if present
948         powerButtonDisabled = *powerButtonReading;
949     }
950 
951     std::optional<bool> resetButtonReading =
952         getButtonEnabled(resetButtonPath, resetButtonIntf);
953     // allow disable if the interface is present
954     bool resetButtonDisableAllow = static_cast<bool>(resetButtonReading);
955     // default return the button is enabled (not disabled)
956     bool resetButtonDisabled = false;
957     if (resetButtonDisableAllow)
958     {
959         // return the real value of the button status, if present
960         resetButtonDisabled = *resetButtonReading;
961     }
962 
963     // This response has a lot of hard-coded, unsupported fields
964     // They are set to false or 0
965     constexpr bool powerOverload = false;
966     constexpr bool chassisInterlock = false;
967     constexpr bool powerFault = false;
968     constexpr bool powerControlFault = false;
969     constexpr bool powerDownAcFailed = false;
970     constexpr bool powerDownOverload = false;
971     constexpr bool powerDownInterlock = false;
972     constexpr bool powerDownPowerFault = false;
973     constexpr bool powerStatusIPMI = false;
974     constexpr bool chassisIntrusionActive = false;
975     constexpr bool frontPanelLockoutActive = false;
976     constexpr bool driveFault = false;
977     constexpr bool coolingFanFault = false;
978     // chassisIdentifySupport set because this command is implemented
979     constexpr bool chassisIdentifySupport = true;
980     constexpr uint2_t chassisIdentifyState(0);
981     constexpr bool diagButtonDisabled = false;
982     constexpr bool sleepButtonDisabled = false;
983     constexpr bool diagButtonDisableAllow = false;
984     constexpr bool sleepButtonDisableAllow = false;
985 
986     return ipmi::responseSuccess(
987         *powerGood, powerOverload, chassisInterlock, powerFault,
988         powerControlFault, *restorePolicy,
989         false, // reserved
990 
991         powerDownAcFailed, powerDownOverload, powerDownInterlock,
992         powerDownPowerFault, powerStatusIPMI,
993         uint3_t(0), // reserved
994 
995         chassisIntrusionActive, frontPanelLockoutActive, driveFault,
996         coolingFanFault, chassisIdentifyState, chassisIdentifySupport,
997         false, // reserved
998 
999         powerButtonDisabled, resetButtonDisabled, diagButtonDisabled,
1000         sleepButtonDisabled, powerButtonDisableAllow, resetButtonDisableAllow,
1001         diagButtonDisableAllow, sleepButtonDisableAllow);
1002 }
1003 
1004 //-------------------------------------------------------------
1005 // Send a command to SoftPowerOff application to stop any timer
1006 //-------------------------------------------------------------
1007 int stop_soft_off_timer()
1008 {
1009     constexpr auto iface = "org.freedesktop.DBus.Properties";
1010     constexpr auto soft_off_iface = "xyz.openbmc_project.Ipmi.Internal."
1011                                     "SoftPowerOff";
1012 
1013     constexpr auto property = "ResponseReceived";
1014     constexpr auto value = "xyz.openbmc_project.Ipmi.Internal."
1015                            "SoftPowerOff.HostResponse.HostShutdown";
1016 
1017     // Get the system bus where most system services are provided.
1018     auto bus = ipmid_get_sd_bus_connection();
1019 
1020     // Get the service name
1021     // TODO openbmc/openbmc#1661 - Mapper refactor
1022     //
1023     // See openbmc/openbmc#1743 for some details but high level summary is that
1024     // for now the code will directly call the soft off interface due to a
1025     // race condition with mapper usage
1026     //
1027     // char *busname = nullptr;
1028     // auto r = mapper_get_service(bus, SOFTOFF_OBJPATH, &busname);
1029     // if (r < 0)
1030     //{
1031     //    fprintf(stderr, "Failed to get %s bus name: %s\n",
1032     //            SOFTOFF_OBJPATH, -r);
1033     //    return r;
1034     //}
1035 
1036     // No error object or reply expected.
1037     int rc = sd_bus_call_method(bus, SOFTOFF_BUSNAME, SOFTOFF_OBJPATH, iface,
1038                                 "Set", nullptr, nullptr, "ssv", soft_off_iface,
1039                                 property, "s", value);
1040     if (rc < 0)
1041     {
1042         log<level::ERR>("Failed to set property in SoftPowerOff object",
1043                         entry("ERRNO=0x%X", -rc));
1044     }
1045 
1046     // TODO openbmc/openbmc#1661 - Mapper refactor
1047     // free(busname);
1048     return rc;
1049 }
1050 
1051 //----------------------------------------------------------------------
1052 // Create file to indicate there is no need for softoff notification to host
1053 //----------------------------------------------------------------------
1054 void indicate_no_softoff_needed()
1055 {
1056     fs::path path{HOST_INBAND_REQUEST_DIR};
1057     if (!fs::is_directory(path))
1058     {
1059         fs::create_directory(path);
1060     }
1061 
1062     // Add the host instance (default 0 for now) to the file name
1063     std::string file{HOST_INBAND_REQUEST_FILE};
1064     auto size = std::snprintf(nullptr, 0, file.c_str(), 0);
1065     size++; // null
1066     std::unique_ptr<char[]> buf(new char[size]);
1067     std::snprintf(buf.get(), size, file.c_str(), 0);
1068 
1069     // Append file name to directory and create it
1070     path /= buf.get();
1071     std::ofstream(path.c_str());
1072 }
1073 
1074 /** @brief Implementation of chassis control command
1075  *
1076  *  @param - chassisControl command byte
1077  *
1078  *  @return  Success or InvalidFieldRequest.
1079  */
1080 ipmi::RspType<> ipmiChassisControl(uint8_t chassisControl)
1081 {
1082     int rc = 0;
1083     switch (chassisControl)
1084     {
1085         case CMD_POWER_ON:
1086             rc = initiate_state_transition(State::Host::Transition::On);
1087             break;
1088         case CMD_POWER_OFF:
1089             // This path would be hit in 2 conditions.
1090             // 1: When user asks for power off using ipmi chassis command 0x04
1091             // 2: Host asking for power off post shutting down.
1092 
1093             // If it's a host requested power off, then need to nudge Softoff
1094             // application that it needs to stop the watchdog timer if running.
1095             // If it is a user requested power off, then this is not really
1096             // needed. But then we need to differentiate between user and host
1097             // calling this same command
1098 
1099             // For now, we are going ahead with trying to nudge the soft off and
1100             // interpret the failure to do so as a non softoff case
1101             rc = stop_soft_off_timer();
1102 
1103             // Only request the Off transition if the soft power off
1104             // application is not running
1105             if (rc < 0)
1106             {
1107                 // First create a file to indicate to the soft off application
1108                 // that it should not run. Not doing this will result in State
1109                 // manager doing a default soft power off when asked for power
1110                 // off.
1111                 indicate_no_softoff_needed();
1112 
1113                 // Now request the shutdown
1114                 rc = initiate_state_transition(State::Host::Transition::Off);
1115             }
1116             else
1117             {
1118                 log<level::INFO>("Soft off is running, so let shutdown target "
1119                                  "stop the host");
1120             }
1121             break;
1122 
1123         case CMD_HARD_RESET:
1124         case CMD_POWER_CYCLE:
1125             // SPEC has a section that says certain implementations can trigger
1126             // PowerOn if power is Off when a command to power cycle is
1127             // requested
1128 
1129             // First create a file to indicate to the soft off application
1130             // that it should not run since this is a direct user initiated
1131             // power reboot request (i.e. a reboot request that is not
1132             // originating via a soft power off SMS request)
1133             indicate_no_softoff_needed();
1134 
1135             rc = initiate_state_transition(State::Host::Transition::Reboot);
1136             break;
1137 
1138         case CMD_SOFT_OFF_VIA_OVER_TEMP:
1139             // Request Host State Manager to do a soft power off
1140             rc = initiate_state_transition(State::Host::Transition::Off);
1141             break;
1142 
1143         default:
1144         {
1145             log<level::ERR>("Invalid Chassis Control command",
1146                             entry("CMD=0x%X", chassisControl));
1147             return ipmi::responseInvalidFieldRequest();
1148         }
1149     }
1150 
1151     return ((rc < 0) ? ipmi::responseUnspecifiedError()
1152                      : ipmi::responseSuccess());
1153 }
1154 
1155 /** @brief Return D-Bus connection string to enclosure identify LED object
1156  *
1157  *  @param[in, out] connection - connection to D-Bus object
1158  *  @return a IPMI return code
1159  */
1160 std::string getEnclosureIdentifyConnection()
1161 {
1162     // lookup enclosure_identify group owner(s) in mapper
1163     auto mapperCall = chassis::internal::dbus.new_method_call(
1164         ipmi::MAPPER_BUS_NAME, ipmi::MAPPER_OBJ, ipmi::MAPPER_INTF,
1165         "GetObject");
1166 
1167     mapperCall.append(identify_led_object_name);
1168     static const std::vector<std::string> interfaces = {
1169         "xyz.openbmc_project.Led.Group"};
1170     mapperCall.append(interfaces);
1171     auto mapperReply = chassis::internal::dbus.call(mapperCall);
1172     if (mapperReply.is_method_error())
1173     {
1174         log<level::ERR>("Chassis Identify: Error communicating to mapper.");
1175         elog<InternalFailure>();
1176     }
1177     std::vector<std::pair<std::string, std::vector<std::string>>> mapperResp;
1178     mapperReply.read(mapperResp);
1179 
1180     if (mapperResp.size() != encIdentifyObjectsSize)
1181     {
1182         log<level::ERR>(
1183             "Invalid number of enclosure identify objects.",
1184             entry("ENC_IDENTITY_OBJECTS_SIZE=%d", mapperResp.size()));
1185         elog<InternalFailure>();
1186     }
1187     auto pair = mapperResp[encIdentifyObjectsSize - 1];
1188     return pair.first;
1189 }
1190 
1191 /** @brief Turn On/Off enclosure identify LED
1192  *
1193  *  @param[in] flag - true to turn on LED, false to turn off
1194  *  @return a IPMI return code
1195  */
1196 void enclosureIdentifyLed(bool flag)
1197 {
1198     using namespace chassis::internal;
1199     std::string connection = std::move(getEnclosureIdentifyConnection());
1200     auto msg = std::string("enclosureIdentifyLed(") +
1201                boost::lexical_cast<std::string>(flag) + ")";
1202     log<level::DEBUG>(msg.c_str());
1203     auto led =
1204         dbus.new_method_call(connection.c_str(), identify_led_object_name,
1205                              "org.freedesktop.DBus.Properties", "Set");
1206     led.append("xyz.openbmc_project.Led.Group", "Asserted",
1207                std::variant<bool>(flag));
1208     auto ledReply = dbus.call(led);
1209     if (ledReply.is_method_error())
1210     {
1211         log<level::ERR>("Chassis Identify: Error Setting State On/Off\n",
1212                         entry("LED_STATE=%d", flag));
1213         elog<InternalFailure>();
1214     }
1215 }
1216 
1217 /** @brief Callback method to turn off LED
1218  */
1219 void enclosureIdentifyLedOff()
1220 {
1221     try
1222     {
1223         enclosureIdentifyLed(false);
1224     }
1225     catch (const InternalFailure& e)
1226     {
1227         report<InternalFailure>();
1228     }
1229 }
1230 
1231 /** @brief Create timer to turn on and off the enclosure LED
1232  */
1233 void createIdentifyTimer()
1234 {
1235     if (!identifyTimer)
1236     {
1237         identifyTimer =
1238             std::make_unique<phosphor::Timer>(enclosureIdentifyLedOff);
1239     }
1240 }
1241 
1242 ipmi::RspType<> ipmiChassisIdentify(std::optional<uint8_t> interval,
1243                                     std::optional<uint8_t> force)
1244 {
1245     uint8_t identifyInterval = interval.value_or(DEFAULT_IDENTIFY_TIME_OUT);
1246     bool forceIdentify = force.value_or(0) & 0x01;
1247 
1248     if (identifyInterval || forceIdentify)
1249     {
1250         // stop the timer if already started;
1251         // for force identify we should not turn off LED
1252         identifyTimer->stop();
1253         try
1254         {
1255             enclosureIdentifyLed(true);
1256         }
1257         catch (const InternalFailure& e)
1258         {
1259             report<InternalFailure>();
1260             return ipmi::responseResponseError();
1261         }
1262 
1263         if (forceIdentify)
1264         {
1265             return ipmi::responseSuccess();
1266         }
1267         // start the timer
1268         auto time = std::chrono::duration_cast<std::chrono::microseconds>(
1269             std::chrono::seconds(identifyInterval));
1270         identifyTimer->start(time);
1271     }
1272     else if (!identifyInterval)
1273     {
1274         identifyTimer->stop();
1275         enclosureIdentifyLedOff();
1276     }
1277     return ipmi::responseSuccess();
1278 }
1279 
1280 namespace boot_options
1281 {
1282 
1283 using namespace sdbusplus::xyz::openbmc_project::Control::Boot::server;
1284 using IpmiValue = uint8_t;
1285 constexpr auto ipmiDefault = 0;
1286 
1287 std::map<IpmiValue, Source::Sources> sourceIpmiToDbus = {
1288     {0x01, Source::Sources::Network},
1289     {0x02, Source::Sources::Disk},
1290     {0x05, Source::Sources::ExternalMedia},
1291     {ipmiDefault, Source::Sources::Default}};
1292 
1293 std::map<IpmiValue, Mode::Modes> modeIpmiToDbus = {
1294     {0x03, Mode::Modes::Safe},
1295     {0x06, Mode::Modes::Setup},
1296     {ipmiDefault, Mode::Modes::Regular}};
1297 
1298 std::map<Source::Sources, IpmiValue> sourceDbusToIpmi = {
1299     {Source::Sources::Network, 0x01},
1300     {Source::Sources::Disk, 0x02},
1301     {Source::Sources::ExternalMedia, 0x05},
1302     {Source::Sources::Default, ipmiDefault}};
1303 
1304 std::map<Mode::Modes, IpmiValue> modeDbusToIpmi = {
1305     {Mode::Modes::Safe, 0x03},
1306     {Mode::Modes::Setup, 0x06},
1307     {Mode::Modes::Regular, ipmiDefault}};
1308 
1309 } // namespace boot_options
1310 
1311 /** @brief Set the property value for boot source
1312  *  @param[in] source - boot source value
1313  *  @return On failure return IPMI error.
1314  */
1315 static ipmi_ret_t setBootSource(const Source::Sources& source)
1316 {
1317     using namespace chassis::internal;
1318     using namespace chassis::internal::cache;
1319     std::variant<std::string> property = convertForMessage(source);
1320     auto bootSetting = settings::boot::setting(objects, bootSourceIntf);
1321     const auto& bootSourceSetting = std::get<settings::Path>(bootSetting);
1322     auto method = dbus.new_method_call(
1323         objects.service(bootSourceSetting, bootSourceIntf).c_str(),
1324         bootSourceSetting.c_str(), ipmi::PROP_INTF, "Set");
1325     method.append(bootSourceIntf, "BootSource", property);
1326     auto reply = dbus.call(method);
1327     if (reply.is_method_error())
1328     {
1329         log<level::ERR>("Error in BootSource Set");
1330         report<InternalFailure>();
1331         return IPMI_CC_UNSPECIFIED_ERROR;
1332     }
1333     return IPMI_CC_OK;
1334 }
1335 
1336 /** @brief Set the property value for boot mode
1337  *  @param[in] mode - boot mode value
1338  *  @return On failure return IPMI error.
1339  */
1340 static ipmi_ret_t setBootMode(const Mode::Modes& mode)
1341 {
1342     using namespace chassis::internal;
1343     using namespace chassis::internal::cache;
1344     std::variant<std::string> property = convertForMessage(mode);
1345     auto bootSetting = settings::boot::setting(objects, bootModeIntf);
1346     const auto& bootModeSetting = std::get<settings::Path>(bootSetting);
1347     auto method = dbus.new_method_call(
1348         objects.service(bootModeSetting, bootModeIntf).c_str(),
1349         bootModeSetting.c_str(), ipmi::PROP_INTF, "Set");
1350     method.append(bootModeIntf, "BootMode", property);
1351     auto reply = dbus.call(method);
1352     if (reply.is_method_error())
1353     {
1354         log<level::ERR>("Error in BootMode Set");
1355         report<InternalFailure>();
1356         return IPMI_CC_UNSPECIFIED_ERROR;
1357     }
1358     return IPMI_CC_OK;
1359 }
1360 
1361 ipmi_ret_t ipmi_chassis_get_sys_boot_options(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
1362                                              ipmi_request_t request,
1363                                              ipmi_response_t response,
1364                                              ipmi_data_len_t data_len,
1365                                              ipmi_context_t context)
1366 {
1367     using namespace boot_options;
1368     ipmi_ret_t rc = IPMI_CC_PARM_NOT_SUPPORTED;
1369     char* p = NULL;
1370     get_sys_boot_options_response_t* resp =
1371         (get_sys_boot_options_response_t*)response;
1372     get_sys_boot_options_t* reqptr = (get_sys_boot_options_t*)request;
1373     IpmiValue bootOption = ipmiDefault;
1374 
1375     std::memset(resp, 0, sizeof(*resp));
1376     resp->version = SET_PARM_VERSION;
1377     resp->parm = 5;
1378     resp->data[0] = SET_PARM_BOOT_FLAGS_VALID_ONE_TIME;
1379 
1380     /*
1381      * Parameter #5 means boot flags. Please refer to 28.13 of ipmi doc.
1382      * This is the only parameter used by petitboot.
1383      */
1384     if (reqptr->parameter ==
1385         static_cast<uint8_t>(BootOptionParameter::BOOT_FLAGS))
1386     {
1387 
1388         *data_len = static_cast<uint8_t>(BootOptionResponseSize::BOOT_FLAGS);
1389         using namespace chassis::internal;
1390         using namespace chassis::internal::cache;
1391 
1392         try
1393         {
1394             auto bootSetting = settings::boot::setting(objects, bootSourceIntf);
1395             const auto& bootSourceSetting =
1396                 std::get<settings::Path>(bootSetting);
1397             auto oneTimeEnabled =
1398                 std::get<settings::boot::OneTimeEnabled>(bootSetting);
1399             auto method = dbus.new_method_call(
1400                 objects.service(bootSourceSetting, bootSourceIntf).c_str(),
1401                 bootSourceSetting.c_str(), ipmi::PROP_INTF, "Get");
1402             method.append(bootSourceIntf, "BootSource");
1403             auto reply = dbus.call(method);
1404             if (reply.is_method_error())
1405             {
1406                 log<level::ERR>("Error in BootSource Get");
1407                 report<InternalFailure>();
1408                 *data_len = 0;
1409                 return IPMI_CC_UNSPECIFIED_ERROR;
1410             }
1411             std::variant<std::string> result;
1412             reply.read(result);
1413             auto bootSource =
1414                 Source::convertSourcesFromString(std::get<std::string>(result));
1415 
1416             bootSetting = settings::boot::setting(objects, bootModeIntf);
1417             const auto& bootModeSetting = std::get<settings::Path>(bootSetting);
1418             method = dbus.new_method_call(
1419                 objects.service(bootModeSetting, bootModeIntf).c_str(),
1420                 bootModeSetting.c_str(), ipmi::PROP_INTF, "Get");
1421             method.append(bootModeIntf, "BootMode");
1422             reply = dbus.call(method);
1423             if (reply.is_method_error())
1424             {
1425                 log<level::ERR>("Error in BootMode Get");
1426                 report<InternalFailure>();
1427                 *data_len = 0;
1428                 return IPMI_CC_UNSPECIFIED_ERROR;
1429             }
1430             reply.read(result);
1431             auto bootMode =
1432                 Mode::convertModesFromString(std::get<std::string>(result));
1433 
1434             bootOption = sourceDbusToIpmi.at(bootSource);
1435             if ((Mode::Modes::Regular == bootMode) &&
1436                 (Source::Sources::Default == bootSource))
1437             {
1438                 bootOption = ipmiDefault;
1439             }
1440             else if (Source::Sources::Default == bootSource)
1441             {
1442                 bootOption = modeDbusToIpmi.at(bootMode);
1443             }
1444             resp->data[1] = (bootOption << 2);
1445 
1446             resp->data[0] = oneTimeEnabled
1447                                 ? SET_PARM_BOOT_FLAGS_VALID_ONE_TIME
1448                                 : SET_PARM_BOOT_FLAGS_VALID_PERMANENT;
1449 
1450             rc = IPMI_CC_OK;
1451         }
1452         catch (InternalFailure& e)
1453         {
1454             report<InternalFailure>();
1455             *data_len = 0;
1456             return IPMI_CC_UNSPECIFIED_ERROR;
1457         }
1458     }
1459     else if (reqptr->parameter ==
1460              static_cast<uint8_t>(BootOptionParameter::OPAL_NETWORK_SETTINGS))
1461     {
1462 
1463         *data_len =
1464             static_cast<uint8_t>(BootOptionResponseSize::OPAL_NETWORK_SETTINGS);
1465 
1466         resp->parm =
1467             static_cast<uint8_t>(BootOptionParameter::OPAL_NETWORK_SETTINGS);
1468 
1469         int ret = getHostNetworkData(resp);
1470 
1471         if (ret < 0)
1472         {
1473 
1474             log<level::ERR>(
1475                 "getHostNetworkData failed for get_sys_boot_options.");
1476             rc = IPMI_CC_UNSPECIFIED_ERROR;
1477         }
1478         else
1479             rc = IPMI_CC_OK;
1480     }
1481 
1482     else
1483     {
1484         log<level::ERR>("Unsupported parameter",
1485                         entry("PARAM=0x%x", reqptr->parameter));
1486     }
1487 
1488     if (p)
1489         free(p);
1490 
1491     if (rc == IPMI_CC_OK)
1492     {
1493         *data_len += 2;
1494     }
1495 
1496     return rc;
1497 }
1498 
1499 ipmi_ret_t ipmi_chassis_set_sys_boot_options(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
1500                                              ipmi_request_t request,
1501                                              ipmi_response_t response,
1502                                              ipmi_data_len_t data_len,
1503                                              ipmi_context_t context)
1504 {
1505     using namespace boot_options;
1506     ipmi_ret_t rc = IPMI_CC_OK;
1507     set_sys_boot_options_t* reqptr = (set_sys_boot_options_t*)request;
1508 
1509     std::printf("IPMI SET_SYS_BOOT_OPTIONS reqptr->parameter =[%d]\n",
1510                 reqptr->parameter);
1511 
1512     // This IPMI command does not have any resposne data
1513     *data_len = 0;
1514 
1515     /*  000101
1516      * Parameter #5 means boot flags. Please refer to 28.13 of ipmi doc.
1517      * This is the only parameter used by petitboot.
1518      */
1519 
1520     if (reqptr->parameter == (uint8_t)BootOptionParameter::BOOT_FLAGS)
1521     {
1522         IpmiValue bootOption = ((reqptr->data[1] & 0x3C) >> 2);
1523         using namespace chassis::internal;
1524         using namespace chassis::internal::cache;
1525         auto oneTimeEnabled = false;
1526         constexpr auto enabledIntf = "xyz.openbmc_project.Object.Enable";
1527         constexpr auto oneTimePath =
1528             "/xyz/openbmc_project/control/host0/boot/one_time";
1529 
1530         try
1531         {
1532             bool permanent =
1533                 (reqptr->data[0] & SET_PARM_BOOT_FLAGS_PERMANENT) ==
1534                 SET_PARM_BOOT_FLAGS_PERMANENT;
1535 
1536             auto bootSetting = settings::boot::setting(objects, bootSourceIntf);
1537 
1538             oneTimeEnabled =
1539                 std::get<settings::boot::OneTimeEnabled>(bootSetting);
1540 
1541             /*
1542              * Check if the current boot setting is onetime or permanent, if the
1543              * request in the command is otherwise, then set the "Enabled"
1544              * property in one_time object path to 'True' to indicate onetime
1545              * and 'False' to indicate permanent.
1546              *
1547              * Once the onetime/permanent setting is applied, then the bootMode
1548              * and bootSource is updated for the corresponding object.
1549              */
1550             if ((permanent && oneTimeEnabled) ||
1551                 (!permanent && !oneTimeEnabled))
1552             {
1553                 auto service = ipmi::getService(dbus, enabledIntf, oneTimePath);
1554 
1555                 ipmi::setDbusProperty(dbus, service, oneTimePath, enabledIntf,
1556                                       "Enabled", !permanent);
1557             }
1558 
1559             auto modeItr = modeIpmiToDbus.find(bootOption);
1560             auto sourceItr = sourceIpmiToDbus.find(bootOption);
1561             if (sourceIpmiToDbus.end() != sourceItr)
1562             {
1563                 rc = setBootSource(sourceItr->second);
1564                 if (rc != IPMI_CC_OK)
1565                 {
1566                     *data_len = 0;
1567                     return rc;
1568                 }
1569                 // If a set boot device is mapping to a boot source, then reset
1570                 // the boot mode D-Bus property to default.
1571                 // This way the ipmid code can determine which property is not
1572                 // at the default value
1573                 if (sourceItr->second != Source::Sources::Default)
1574                 {
1575                     setBootMode(Mode::Modes::Regular);
1576                 }
1577             }
1578             if (modeIpmiToDbus.end() != modeItr)
1579             {
1580                 rc = setBootMode(modeItr->second);
1581                 if (rc != IPMI_CC_OK)
1582                 {
1583                     *data_len = 0;
1584                     return rc;
1585                 }
1586                 // If a set boot device is mapping to a boot mode, then reset
1587                 // the boot source D-Bus property to default.
1588                 // This way the ipmid code can determine which property is not
1589                 // at the default value
1590                 if (modeItr->second != Mode::Modes::Regular)
1591                 {
1592                     setBootSource(Source::Sources::Default);
1593                 }
1594             }
1595         }
1596         catch (InternalFailure& e)
1597         {
1598             report<InternalFailure>();
1599             *data_len = 0;
1600             return IPMI_CC_UNSPECIFIED_ERROR;
1601         }
1602     }
1603     else if (reqptr->parameter ==
1604              (uint8_t)BootOptionParameter::OPAL_NETWORK_SETTINGS)
1605     {
1606 
1607         int ret = setHostNetworkData(reqptr);
1608         if (ret < 0)
1609         {
1610             log<level::ERR>(
1611                 "setHostNetworkData failed for set_sys_boot_options");
1612             rc = IPMI_CC_UNSPECIFIED_ERROR;
1613         }
1614     }
1615     else if (reqptr->parameter ==
1616              static_cast<uint8_t>(BootOptionParameter::BOOT_INFO))
1617     {
1618         // Handle parameter #4 and return command completed normally
1619         // (IPMI_CC_OK). There is no implementation in OpenBMC for this
1620         // parameter. This is added to support the ipmitool command `chassis
1621         // bootdev` which sends set on parameter #4, before setting the boot
1622         // flags.
1623         rc = IPMI_CC_OK;
1624     }
1625     else
1626     {
1627         log<level::ERR>("Unsupported parameter",
1628                         entry("PARAM=0x%x", reqptr->parameter));
1629         rc = IPMI_CC_PARM_NOT_SUPPORTED;
1630     }
1631 
1632     return rc;
1633 }
1634 
1635 /** @brief implements Get POH counter command
1636  *  @parameter
1637  *   -  none
1638  *  @returns IPMI completion code plus response data
1639  *   - minPerCount - Minutes per count
1640  *   - counterReading - counter reading
1641  */
1642 ipmi::RspType<uint8_t, // Minutes per count
1643               uint32_t // Counter reading
1644               >
1645     ipmiGetPOHCounter()
1646 {
1647     // sd_bus error
1648     try
1649     {
1650         return ipmi::responseSuccess(static_cast<uint8_t>(poh::minutesPerCount),
1651                                      getPOHCounter());
1652     }
1653     catch (std::exception& e)
1654     {
1655         log<level::ERR>(e.what());
1656         return ipmi::responseUnspecifiedError();
1657     }
1658 }
1659 
1660 ipmi::RspType<uint8_t>
1661     ipmiChassisSetPowerRestorePolicy(boost::asio::yield_context yield,
1662                                      uint8_t policy)
1663 {
1664     constexpr uint8_t ccParamNotSupported = 0x80;
1665     power_policy::DbusValue value =
1666         power_policy::RestorePolicy::Policy::AlwaysOff;
1667 
1668     if (policy & ~power_policy::policyBitMask)
1669     {
1670         phosphor::logging::log<level::ERR>(
1671             "Reserved request parameter",
1672             entry("REQ=0x%x", static_cast<int>(policy)));
1673         return ipmi::response(ccParamNotSupported);
1674     }
1675 
1676     if (policy == power_policy::noChange)
1677     {
1678         // just return the supported policy
1679         return ipmi::responseSuccess(power_policy::allSupport);
1680     }
1681 
1682     for (auto const& it : power_policy::dbusToIpmi)
1683     {
1684         if (it.second == policy)
1685         {
1686             value = it.first;
1687             break;
1688         }
1689     }
1690 
1691     try
1692     {
1693         const settings::Path& powerRestoreSetting =
1694             chassis::internal::cache::objects.map
1695                 .at(chassis::internal::powerRestoreIntf)
1696                 .front();
1697         std::variant<std::string> property = convertForMessage(value);
1698 
1699         auto sdbusp = getSdBus();
1700         boost::system::error_code ec;
1701         sdbusp->yield_method_call<void>(
1702             yield, ec,
1703             chassis::internal::cache::objects
1704                 .service(powerRestoreSetting,
1705                          chassis::internal::powerRestoreIntf)
1706                 .c_str(),
1707             powerRestoreSetting, ipmi::PROP_INTF, "Set",
1708             chassis::internal::powerRestoreIntf, "PowerRestorePolicy",
1709             property);
1710         if (ec)
1711         {
1712             phosphor::logging::log<level::ERR>("Unspecified Error");
1713             return ipmi::responseUnspecifiedError();
1714         }
1715     }
1716     catch (InternalFailure& e)
1717     {
1718         report<InternalFailure>();
1719         return ipmi::responseUnspecifiedError();
1720     }
1721 
1722     return ipmi::responseSuccess(power_policy::allSupport);
1723 }
1724 
1725 void register_netfn_chassis_functions()
1726 {
1727     createIdentifyTimer();
1728 
1729     // <Wildcard Command>
1730     ipmi_register_callback(NETFUN_CHASSIS, IPMI_CMD_WILDCARD, NULL,
1731                            ipmi_chassis_wildcard, PRIVILEGE_USER);
1732 
1733     // Get Chassis Capabilities
1734     ipmi_register_callback(NETFUN_CHASSIS, IPMI_CMD_GET_CHASSIS_CAP, NULL,
1735                            ipmi_get_chassis_cap, PRIVILEGE_USER);
1736 
1737     // Set Chassis Capabilities
1738     ipmi_register_callback(NETFUN_CHASSIS, IPMI_CMD_SET_CHASSIS_CAP, NULL,
1739                            ipmi_set_chassis_cap, PRIVILEGE_USER);
1740 
1741     // <Get System Boot Options>
1742     ipmi_register_callback(NETFUN_CHASSIS, IPMI_CMD_GET_SYS_BOOT_OPTIONS, NULL,
1743                            ipmi_chassis_get_sys_boot_options,
1744                            PRIVILEGE_OPERATOR);
1745 
1746     // <Get Chassis Status>
1747     ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnChassis,
1748                           ipmi::chassis::cmdGetChassisStatus,
1749                           ipmi::Privilege::User, ipmiGetChassisStatus);
1750 
1751     // <Chassis Control>
1752     ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnChassis,
1753                           ipmi::chassis::cmdChassisControl,
1754                           ipmi::Privilege::Operator, ipmiChassisControl);
1755 
1756     // <Chassis Identify>
1757     ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnChassis,
1758                           ipmi::chassis::cmdChassisIdentify,
1759                           ipmi::Privilege::Operator, ipmiChassisIdentify);
1760 
1761     // <Set System Boot Options>
1762     ipmi_register_callback(NETFUN_CHASSIS, IPMI_CMD_SET_SYS_BOOT_OPTIONS, NULL,
1763                            ipmi_chassis_set_sys_boot_options,
1764                            PRIVILEGE_OPERATOR);
1765     // <Get POH Counter>
1766     ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnChassis,
1767                           ipmi::chassis::cmdGetPohCounter,
1768                           ipmi::Privilege::User, ipmiGetPOHCounter);
1769 
1770     // <Set Power Restore Policy>
1771     ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnChassis,
1772                           ipmi::chassis::cmdSetPowerRestorePolicy,
1773                           ipmi::Privilege::Operator,
1774                           ipmiChassisSetPowerRestorePolicy);
1775 }
1776