1 /*
2 // Copyright (c) 2018 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 
17 #include <linux/input.h>
18 
19 #include <boost/algorithm/string.hpp>
20 #include <boost/container/flat_map.hpp>
21 #include <ipmid/api.hpp>
22 #include <manufacturingcommands.hpp>
23 #include <oemcommands.hpp>
24 #include <types.hpp>
25 
26 #include <filesystem>
27 #include <fstream>
28 
29 namespace ipmi
30 {
31 
32 Manufacturing mtm;
33 
34 static auto revertTimeOut =
35     std::chrono::duration_cast<std::chrono::microseconds>(
36         std::chrono::seconds(60)); // 1 minute timeout
37 
38 static constexpr uint8_t bbRiserMux = 0;
39 static constexpr uint8_t leftRiserMux = 1;
40 static constexpr uint8_t rightRiserMux = 2;
41 static constexpr uint8_t pcieMux = 3;
42 static constexpr uint8_t hsbpMux = 4;
43 
44 static constexpr uint8_t slotAddressTypeBus = 0;
45 static constexpr uint8_t slotAddressTypeUniqueid = 1;
46 static constexpr uint8_t slotI2CMaxReadSize = 35;
47 
48 static constexpr const char* callbackMgrService =
49     "xyz.openbmc_project.CallbackManager";
50 static constexpr const char* callbackMgrIntf =
51     "xyz.openbmc_project.CallbackManager";
52 static constexpr const char* callbackMgrObjPath =
53     "/xyz/openbmc_project/CallbackManager";
54 static constexpr const char* retriggerLedUpdate = "RetriggerLEDUpdate";
55 
56 const static constexpr char* systemDService = "org.freedesktop.systemd1";
57 const static constexpr char* systemDObjPath = "/org/freedesktop/systemd1";
58 const static constexpr char* systemDMgrIntf =
59     "org.freedesktop.systemd1.Manager";
60 const static constexpr char* pidControlService = "phosphor-pid-control.service";
61 
62 static inline Cc resetMtmTimer(ipmi::Context::ptr ctx)
63 {
64     boost::system::error_code ec;
65     ctx->bus->yield_method_call<>(ctx->yield, ec, specialModeService,
66                                   specialModeObjPath, specialModeIntf,
67                                   "ResetTimer");
68     if (ec)
69     {
70         phosphor::logging::log<phosphor::logging::level::ERR>(
71             "Failed to reset the manufacturing mode timer");
72         return ccUnspecifiedError;
73     }
74     return ccSuccess;
75 }
76 
77 int getGpioPathForSmSignal(const SmSignalGet signal, std::string& path)
78 {
79     switch (signal)
80     {
81         case SmSignalGet::smPowerButton:
82             path = "/xyz/openbmc_project/chassis/buttons/power";
83             break;
84         case SmSignalGet::smResetButton:
85             path = "/xyz/openbmc_project/chassis/buttons/reset";
86             break;
87         case SmSignalGet::smNMIButton:
88             path = "/xyz/openbmc_project/chassis/buttons/nmi";
89             break;
90         case SmSignalGet::smIdentifyButton:
91             path = "/xyz/openbmc_project/chassis/buttons/id";
92             break;
93         default:
94             return -1;
95             break;
96     }
97     return 0;
98 }
99 
100 ipmi_ret_t ledStoreAndSet(SmSignalSet signal, const std::string& setState)
101 {
102     LedProperty* ledProp = mtm.findLedProperty(signal);
103     if (ledProp == nullptr)
104     {
105         return IPMI_CC_INVALID_FIELD_REQUEST;
106     }
107 
108     std::string ledName = ledProp->getName();
109     std::string ledService = ledServicePrefix + ledName;
110     std::string ledPath = ledPathPrefix + ledName;
111     ipmi::Value presentState;
112 
113     if (false == ledProp->getLock())
114     {
115         if (mtm.getProperty(ledService.c_str(), ledPath.c_str(), ledIntf,
116                             "State", &presentState) != 0)
117         {
118             return IPMI_CC_UNSPECIFIED_ERROR;
119         }
120         ledProp->setPrevState(std::get<std::string>(presentState));
121         ledProp->setLock(true);
122         if (signal == SmSignalSet::smPowerFaultLed ||
123             signal == SmSignalSet::smSystemReadyLed)
124         {
125             mtm.revertLedCallback = true;
126         }
127     }
128     if (mtm.setProperty(ledService, ledPath, ledIntf, "State",
129                         ledStateStr + setState) != 0)
130     {
131         return IPMI_CC_UNSPECIFIED_ERROR;
132     }
133     return IPMI_CC_OK;
134 }
135 
136 ipmi_ret_t ledRevert(SmSignalSet signal)
137 {
138     LedProperty* ledProp = mtm.findLedProperty(signal);
139     if (ledProp == nullptr)
140     {
141         return IPMI_CC_INVALID_FIELD_REQUEST;
142     }
143     if (true == ledProp->getLock())
144     {
145         ledProp->setLock(false);
146         if (signal == SmSignalSet::smPowerFaultLed ||
147             signal == SmSignalSet::smSystemReadyLed)
148         {
149             try
150             {
151                 ipmi::method_no_args::callDbusMethod(
152                     *getSdBus(), callbackMgrService, callbackMgrObjPath,
153                     callbackMgrIntf, retriggerLedUpdate);
154             }
155             catch (const sdbusplus::exception_t& e)
156             {
157                 return IPMI_CC_UNSPECIFIED_ERROR;
158             }
159             mtm.revertLedCallback = false;
160         }
161         else
162         {
163             std::string ledName = ledProp->getName();
164             std::string ledService = ledServicePrefix + ledName;
165             std::string ledPath = ledPathPrefix + ledName;
166             if (mtm.setProperty(ledService, ledPath, ledIntf, "State",
167                                 ledProp->getPrevState()) != 0)
168             {
169                 return IPMI_CC_UNSPECIFIED_ERROR;
170             }
171         }
172     }
173     return IPMI_CC_OK;
174 }
175 
176 void Manufacturing::initData()
177 {
178     ledPropertyList.push_back(
179         LedProperty(SmSignalSet::smPowerFaultLed, "status_amber"));
180     ledPropertyList.push_back(
181         LedProperty(SmSignalSet::smSystemReadyLed, "status_green"));
182     ledPropertyList.push_back(
183         LedProperty(SmSignalSet::smIdentifyLed, "identify"));
184 }
185 
186 void Manufacturing::revertTimerHandler()
187 {
188 
189 #ifdef BMC_VALIDATION_UNSECURE_FEATURE
190     if (mtm.getMfgMode() == SpecialMode::valUnsecure)
191     {
192         // Don't revert the behaviour for validation unsecure mode.
193         return;
194     }
195 #endif
196     if (revertFanPWM)
197     {
198         revertFanPWM = false;
199         disablePidControlService(false);
200     }
201 
202     if (mtmTestBeepFd != -1)
203     {
204         ::close(mtmTestBeepFd);
205         mtmTestBeepFd = -1;
206     }
207 
208     for (const auto& ledProperty : ledPropertyList)
209     {
210         const std::string& ledName = ledProperty.getName();
211         if (ledName == "identify" && mtm.getMfgMode() == SpecialMode::mfg)
212         {
213             // Don't revert the behaviour for manufacturing mode
214             continue;
215         }
216         ledRevert(ledProperty.getSignal());
217     }
218 }
219 
220 Manufacturing::Manufacturing() :
221     revertTimer([&](void) { revertTimerHandler(); })
222 {
223     initData();
224 }
225 
226 int8_t Manufacturing::getProperty(const std::string& service,
227                                   const std::string& path,
228                                   const std::string& interface,
229                                   const std::string& propertyName,
230                                   ipmi::Value* reply)
231 {
232     try
233     {
234         *reply = ipmi::getDbusProperty(*getSdBus(), service, path, interface,
235                                        propertyName);
236     }
237     catch (const sdbusplus::exception_t& e)
238     {
239         phosphor::logging::log<phosphor::logging::level::INFO>(
240             "ERROR: getProperty");
241         return -1;
242     }
243 
244     return 0;
245 }
246 
247 int8_t Manufacturing::setProperty(const std::string& service,
248                                   const std::string& path,
249                                   const std::string& interface,
250                                   const std::string& propertyName,
251                                   ipmi::Value value)
252 {
253     try
254     {
255         ipmi::setDbusProperty(*getSdBus(), service, path, interface,
256                               propertyName, value);
257     }
258     catch (const sdbusplus::exception_t& e)
259     {
260         phosphor::logging::log<phosphor::logging::level::INFO>(
261             "ERROR: setProperty");
262         return -1;
263     }
264 
265     return 0;
266 }
267 
268 int8_t Manufacturing::disablePidControlService(const bool disable)
269 {
270     try
271     {
272         auto dbus = getSdBus();
273         auto method = dbus->new_method_call(systemDService, systemDObjPath,
274                                             systemDMgrIntf,
275                                             disable ? "StopUnit" : "StartUnit");
276         method.append(pidControlService, "replace");
277         auto reply = dbus->call(method);
278     }
279     catch (const sdbusplus::exception_t& e)
280     {
281         phosphor::logging::log<phosphor::logging::level::INFO>(
282             "ERROR: phosphor-pid-control service start or stop failed");
283         return -1;
284     }
285     return 0;
286 }
287 
288 static bool findPwmName(ipmi::Context::ptr& ctx, uint8_t instance,
289                         std::string& pwmName)
290 {
291     boost::system::error_code ec{};
292     ObjectValueTree obj;
293 
294     // GetAll the objects under service FruDevice
295     ec = getManagedObjects(ctx, "xyz.openbmc_project.EntityManager",
296                            "/xyz/openbmc_project/inventory", obj);
297     if (ec)
298     {
299         phosphor::logging::log<phosphor::logging::level::ERR>(
300             "GetMangagedObjects failed",
301             phosphor::logging::entry("ERROR=%s", ec.message().c_str()));
302         return false;
303     }
304     for (const auto& [path, objData] : obj)
305     {
306         for (const auto& [intf, propMap] : objData)
307         {
308             // Currently, these are the three different fan types supported.
309             if (intf == "xyz.openbmc_project.Configuration.AspeedFan" ||
310                 intf == "xyz.openbmc_project.Configuration.I2CFan" ||
311                 intf == "xyz.openbmc_project.Configuration.NuvotonFan")
312             {
313                 auto findIndex = propMap.find("Index");
314                 if (findIndex == propMap.end())
315                 {
316                     continue;
317                 }
318 
319                 auto fanIndex = std::get_if<uint64_t>(&findIndex->second);
320                 if (!fanIndex || *fanIndex != instance)
321                 {
322                     continue;
323                 }
324                 auto connector = objData.find(intf + std::string(".Connector"));
325                 if (connector == objData.end())
326                 {
327                     return false;
328                 }
329                 auto findPwmName = connector->second.find("PwmName");
330                 if (findPwmName != connector->second.end())
331                 {
332                     auto fanPwmName =
333                         std::get_if<std::string>(&findPwmName->second);
334                     if (!fanPwmName)
335                     {
336                         phosphor::logging::log<phosphor::logging::level::INFO>(
337                             "PwmName parse ERROR.");
338                         return false;
339                     }
340                     pwmName = *fanPwmName;
341                     return true;
342                 }
343                 auto findPwm = connector->second.find("Pwm");
344                 if (findPwm == connector->second.end())
345                 {
346                     return false;
347                 }
348                 auto fanPwm = std::get_if<uint64_t>(&findPwm->second);
349                 if (!fanPwm)
350                 {
351                     return false;
352                 }
353                 pwmName = "Pwm_" + std::to_string(*fanPwm + 1);
354                 return true;
355             }
356         }
357     }
358     return false;
359 }
360 ipmi::RspType<uint8_t,                // Signal value
361               std::optional<uint16_t> // Fan tach value
362               >
363     appMTMGetSignal(ipmi::Context::ptr ctx, uint8_t signalTypeByte,
364                     uint8_t instance, uint8_t actionByte)
365 {
366     // mfg filter logic is used to allow MTM get signal command only in
367     // manfacturing mode.
368 
369     SmSignalGet signalType = static_cast<SmSignalGet>(signalTypeByte);
370     SmActionGet action = static_cast<SmActionGet>(actionByte);
371 
372     switch (signalType)
373     {
374         case SmSignalGet::smChassisIntrusion:
375         {
376             ipmi::Value reply;
377             if (mtm.getProperty(intrusionService, intrusionPath, intrusionIntf,
378                                 "Status", &reply) < 0)
379             {
380                 return ipmi::responseInvalidFieldRequest();
381             }
382             std::string* intrusionStatus = std::get_if<std::string>(&reply);
383             if (!intrusionStatus)
384             {
385                 return ipmi::responseUnspecifiedError();
386             }
387 
388             uint8_t status = 0;
389             if (!intrusionStatus->compare("Normal"))
390             {
391                 status = static_cast<uint8_t>(IntrusionStatus::normal);
392             }
393             else if (!intrusionStatus->compare("HardwareIntrusion"))
394             {
395                 status =
396                     static_cast<uint8_t>(IntrusionStatus::hardwareIntrusion);
397             }
398             else if (!intrusionStatus->compare("TamperingDetected"))
399             {
400                 status =
401                     static_cast<uint8_t>(IntrusionStatus::tamperingDetected);
402             }
403             else
404             {
405                 return ipmi::responseUnspecifiedError();
406             }
407             return ipmi::responseSuccess(status, std::nullopt);
408         }
409         case SmSignalGet::smFanPwmGet:
410         {
411             ipmi::Value reply;
412             std::string pwmName, fullPath;
413             if (!findPwmName(ctx, instance, pwmName))
414             {
415                 // The default PWM name is Pwm_#
416                 pwmName = "Pwm_" + std::to_string(instance + 1);
417             }
418             fullPath = fanPwmPath + pwmName;
419             if (mtm.getProperty(fanService, fullPath, fanIntf, "Value",
420                                 &reply) < 0)
421             {
422                 return ipmi::responseInvalidFieldRequest();
423             }
424             double* doubleVal = std::get_if<double>(&reply);
425             if (doubleVal == nullptr)
426             {
427                 return ipmi::responseUnspecifiedError();
428             }
429             uint8_t sensorVal = std::round(*doubleVal);
430             resetMtmTimer(ctx);
431             return ipmi::responseSuccess(sensorVal, std::nullopt);
432         }
433         break;
434         case SmSignalGet::smFanTachometerGet:
435         {
436             boost::system::error_code ec;
437             using objFlatMap = boost::container::flat_map<
438                 std::string, boost::container::flat_map<
439                                  std::string, std::vector<std::string>>>;
440 
441             auto flatMap = ctx->bus->yield_method_call<objFlatMap>(
442                 ctx->yield, ec, "xyz.openbmc_project.ObjectMapper",
443                 "/xyz/openbmc_project/object_mapper",
444                 "xyz.openbmc_project.ObjectMapper", "GetSubTree",
445                 fanTachBasePath, 0, std::array<const char*, 1>{fanIntf});
446             if (ec)
447             {
448                 phosphor::logging::log<phosphor::logging::level::ERR>(
449                     "Failed to query fan tach sub tree objects");
450                 return ipmi::responseUnspecifiedError();
451             }
452             if (instance >= flatMap.size())
453             {
454                 return ipmi::responseInvalidFieldRequest();
455             }
456             auto itr = flatMap.nth(instance);
457             ipmi::Value reply;
458             if (mtm.getProperty(fanService, itr->first, fanIntf, "Value",
459                                 &reply) < 0)
460             {
461                 return ipmi::responseInvalidFieldRequest();
462             }
463 
464             double* doubleVal = std::get_if<double>(&reply);
465             if (doubleVal == nullptr)
466             {
467                 return ipmi::responseUnspecifiedError();
468             }
469             uint8_t sensorVal = FAN_PRESENT | FAN_SENSOR_PRESENT;
470             std::optional<uint16_t> fanTach = std::round(*doubleVal);
471 
472             resetMtmTimer(ctx);
473             return ipmi::responseSuccess(sensorVal, fanTach);
474         }
475         break;
476         case SmSignalGet::smIdentifyButton:
477         {
478             if (action == SmActionGet::revert || action == SmActionGet::ignore)
479             {
480                 // ButtonMasked property is not supported for ID button as it is
481                 // unnecessary. Hence if requested for revert / ignore, override
482                 // it to sample action to make tools happy.
483                 action = SmActionGet::sample;
484             }
485             // fall-through
486         }
487         case SmSignalGet::smResetButton:
488         case SmSignalGet::smPowerButton:
489         case SmSignalGet::smNMIButton:
490         {
491             std::string path;
492             if (getGpioPathForSmSignal(signalType, path) < 0)
493             {
494                 return ipmi::responseInvalidFieldRequest();
495             }
496 
497             switch (action)
498             {
499                 case SmActionGet::sample:
500                     phosphor::logging::log<phosphor::logging::level::INFO>(
501                         "case SmActionGet::sample");
502                     break;
503                 case SmActionGet::ignore:
504                 {
505                     phosphor::logging::log<phosphor::logging::level::INFO>(
506                         "case SmActionGet::ignore");
507                     if (mtm.setProperty(buttonService, path, buttonIntf,
508                                         "ButtonMasked", true) < 0)
509                     {
510                         return ipmi::responseUnspecifiedError();
511                     }
512                 }
513                 break;
514                 case SmActionGet::revert:
515                 {
516                     phosphor::logging::log<phosphor::logging::level::INFO>(
517                         "case SmActionGet::revert");
518                     if (mtm.setProperty(buttonService, path, buttonIntf,
519                                         "ButtonMasked", false) < 0)
520                     {
521                         return ipmi::responseUnspecifiedError();
522                     }
523                 }
524                 break;
525 
526                 default:
527                     return ipmi::responseInvalidFieldRequest();
528                     break;
529             }
530 
531             ipmi::Value reply;
532             if (mtm.getProperty(buttonService, path, buttonIntf,
533                                 "ButtonPressed", &reply) < 0)
534             {
535                 return ipmi::responseUnspecifiedError();
536             }
537             bool* valPtr = std::get_if<bool>(&reply);
538             if (valPtr == nullptr)
539             {
540                 return ipmi::responseUnspecifiedError();
541             }
542             resetMtmTimer(ctx);
543             uint8_t sensorVal = *valPtr;
544             return ipmi::responseSuccess(sensorVal, std::nullopt);
545         }
546         break;
547         case SmSignalGet::smNcsiDiag:
548         {
549             constexpr const char* netBasePath = "/sys/class/net/eth";
550             constexpr const char* carrierSuffix = "/carrier";
551             std::ifstream netIfs(netBasePath + std::to_string(instance) +
552                                  carrierSuffix);
553             if (!netIfs.good())
554             {
555                 return ipmi::responseInvalidFieldRequest();
556             }
557             std::string carrier;
558             netIfs >> carrier;
559             resetMtmTimer(ctx);
560             return ipmi::responseSuccess(
561                 static_cast<uint8_t>(std::stoi(carrier)), std::nullopt);
562         }
563         break;
564         default:
565             return ipmi::responseInvalidFieldRequest();
566             break;
567     }
568 }
569 
570 ipmi::RspType<> appMTMSetSignal(ipmi::Context::ptr ctx, uint8_t signalTypeByte,
571                                 uint8_t instance, uint8_t actionByte,
572                                 std::optional<uint8_t> pwmSpeed)
573 {
574     // mfg filter logic is used to allow MTM set signal command only in
575     // manfacturing mode.
576 
577     SmSignalSet signalType = static_cast<SmSignalSet>(signalTypeByte);
578     SmActionSet action = static_cast<SmActionSet>(actionByte);
579     Cc retCode = ccSuccess;
580     int8_t ret = 0;
581 
582     switch (signalType)
583     {
584         case SmSignalSet::smPowerFaultLed:
585         case SmSignalSet::smSystemReadyLed:
586         case SmSignalSet::smIdentifyLed:
587             switch (action)
588             {
589                 case SmActionSet::forceDeasserted:
590                 {
591                     phosphor::logging::log<phosphor::logging::level::INFO>(
592                         "case SmActionSet::forceDeasserted");
593 
594                     retCode = ledStoreAndSet(signalType, std::string("Off"));
595                     if (retCode != ccSuccess)
596                     {
597                         return ipmi::response(retCode);
598                     }
599                     mtm.revertTimer.start(revertTimeOut);
600                 }
601                 break;
602                 case SmActionSet::forceAsserted:
603                 {
604                     phosphor::logging::log<phosphor::logging::level::INFO>(
605                         "case SmActionSet::forceAsserted");
606 
607                     retCode = ledStoreAndSet(signalType, std::string("On"));
608                     if (retCode != ccSuccess)
609                     {
610                         return ipmi::response(retCode);
611                     }
612                     mtm.revertTimer.start(revertTimeOut);
613                     if (SmSignalSet::smPowerFaultLed == signalType)
614                     {
615                         // Deassert "system ready"
616                         retCode = ledStoreAndSet(SmSignalSet::smSystemReadyLed,
617                                                  std::string("Off"));
618                     }
619                     else if (SmSignalSet::smSystemReadyLed == signalType)
620                     {
621                         // Deassert "fault led"
622                         retCode = ledStoreAndSet(SmSignalSet::smPowerFaultLed,
623                                                  std::string("Off"));
624                     }
625                 }
626                 break;
627                 case SmActionSet::revert:
628                 {
629                     phosphor::logging::log<phosphor::logging::level::INFO>(
630                         "case SmActionSet::revert");
631                     retCode = ledRevert(signalType);
632                 }
633                 break;
634                 default:
635                 {
636                     return ipmi::responseInvalidFieldRequest();
637                 }
638             }
639             break;
640         case SmSignalSet::smFanPowerSpeed:
641         {
642             if ((action == SmActionSet::forceAsserted) && (!pwmSpeed))
643             {
644                 return ipmi::responseReqDataLenInvalid();
645             }
646 
647             if ((action == SmActionSet::forceAsserted) && (*pwmSpeed > 100))
648             {
649                 return ipmi::responseInvalidFieldRequest();
650             }
651 
652             uint8_t pwmValue = 0;
653             switch (action)
654             {
655                 case SmActionSet::revert:
656                 {
657                     if (mtm.revertFanPWM)
658                     {
659                         ret = mtm.disablePidControlService(false);
660                         if (ret < 0)
661                         {
662                             return ipmi::responseUnspecifiedError();
663                         }
664                         mtm.revertFanPWM = false;
665                     }
666                 }
667                 break;
668                 case SmActionSet::forceAsserted:
669                 {
670                     pwmValue = *pwmSpeed;
671                 } // fall-through
672                 case SmActionSet::forceDeasserted:
673                 {
674                     if (!mtm.revertFanPWM)
675                     {
676                         ret = mtm.disablePidControlService(true);
677                         if (ret < 0)
678                         {
679                             return ipmi::responseUnspecifiedError();
680                         }
681                         mtm.revertFanPWM = true;
682                     }
683                     mtm.revertTimer.start(revertTimeOut);
684                     std::string pwmName, fanPwmInstancePath;
685                     if (!findPwmName(ctx, instance, pwmName))
686                     {
687                         pwmName = "Pwm_" + std::to_string(instance + 1);
688                     }
689                     fanPwmInstancePath = fanPwmPath + pwmName;
690                     ret =
691                         mtm.setProperty(fanService, fanPwmInstancePath, fanIntf,
692                                         "Value", static_cast<double>(pwmValue));
693                     if (ret < 0)
694                     {
695                         return ipmi::responseUnspecifiedError();
696                     }
697                 }
698                 break;
699                 default:
700                 {
701                     return ipmi::responseInvalidFieldRequest();
702                 }
703             }
704         }
705         break;
706         case SmSignalSet::smSpeaker:
707         {
708             phosphor::logging::log<phosphor::logging::level::INFO>(
709                 "Performing Speaker SmActionSet",
710                 phosphor::logging::entry("ACTION=%d",
711                                          static_cast<uint8_t>(action)));
712             switch (action)
713             {
714                 case SmActionSet::forceAsserted:
715                 {
716                     char beepDevName[] = "/dev/input/event0";
717                     if (mtm.mtmTestBeepFd != -1)
718                     {
719                         phosphor::logging::log<phosphor::logging::level::INFO>(
720                             "mtm beep device is opened already!");
721                         // returning success as already beep is in progress
722                         return ipmi::response(retCode);
723                     }
724 
725                     if ((mtm.mtmTestBeepFd =
726                              ::open(beepDevName, O_RDWR | O_CLOEXEC)) < 0)
727                     {
728                         phosphor::logging::log<phosphor::logging::level::ERR>(
729                             "Failed to open input device");
730                         return ipmi::responseUnspecifiedError();
731                     }
732 
733                     struct input_event event;
734                     event.type = EV_SND;
735                     event.code = SND_TONE;
736                     event.value = 2000;
737 
738                     if (::write(mtm.mtmTestBeepFd, &event,
739                                 sizeof(struct input_event)) !=
740                         sizeof(struct input_event))
741                     {
742                         phosphor::logging::log<phosphor::logging::level::ERR>(
743                             "Failed to write a tone sound event");
744                         ::close(mtm.mtmTestBeepFd);
745                         mtm.mtmTestBeepFd = -1;
746                         return ipmi::responseUnspecifiedError();
747                     }
748                     mtm.revertTimer.start(revertTimeOut);
749                 }
750                 break;
751                 case SmActionSet::revert:
752                 case SmActionSet::forceDeasserted:
753                 {
754                     if (mtm.mtmTestBeepFd != -1)
755                     {
756                         ::close(mtm.mtmTestBeepFd);
757                         mtm.mtmTestBeepFd = -1;
758                     }
759                 }
760                 break;
761                 default:
762                 {
763                     return ipmi::responseInvalidFieldRequest();
764                 }
765             }
766         }
767         break;
768         case SmSignalSet::smDiskFaultLed:
769         {
770             boost::system::error_code ec;
771             using objPaths = std::vector<std::string>;
772             std::string driveBasePath =
773                 "/xyz/openbmc_project/inventory/item/drive/";
774             static constexpr const char* driveLedIntf =
775                 "xyz.openbmc_project.Led.Group";
776             static constexpr const char* hsbpService =
777                 "xyz.openbmc_project.HsbpManager";
778 
779             auto driveList = ctx->bus->yield_method_call<objPaths>(
780                 ctx->yield, ec, "xyz.openbmc_project.ObjectMapper",
781                 "/xyz/openbmc_project/object_mapper",
782                 "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths",
783                 driveBasePath, 0, std::array<const char*, 1>{driveLedIntf});
784             if (ec)
785             {
786                 phosphor::logging::log<phosphor::logging::level::ERR>(
787                     "Failed to query HSBP drive sub tree objects");
788                 return ipmi::responseUnspecifiedError();
789             }
790             std::string driveObjPath =
791                 driveBasePath + "Drive_" + std::to_string(instance + 1);
792             if (std::find(driveList.begin(), driveList.end(), driveObjPath) ==
793                 driveList.end())
794             {
795                 return ipmi::responseInvalidFieldRequest();
796             }
797             bool driveLedState = false;
798             switch (action)
799             {
800                 case SmActionSet::forceAsserted:
801                 {
802                     driveLedState = true;
803                 }
804                 break;
805                 case SmActionSet::revert:
806                 {
807                     driveLedState = false;
808                 }
809                 break;
810                 case SmActionSet::forceDeasserted:
811                 {
812                     driveLedState = false;
813                 }
814                 break;
815                 default:
816                 {
817                     return ipmi::responseInvalidFieldRequest();
818                 }
819             }
820             ret = mtm.setProperty(hsbpService, driveObjPath, driveLedIntf,
821                                   "Asserted", driveLedState);
822             if (ret < 0)
823             {
824                 return ipmi::responseUnspecifiedError();
825             }
826         }
827         break;
828         default:
829         {
830             return ipmi::responseInvalidFieldRequest();
831         }
832     }
833     if (retCode == ccSuccess)
834     {
835         resetMtmTimer(ctx);
836     }
837     return ipmi::response(retCode);
838 }
839 
840 ipmi::RspType<> mtmKeepAlive(ipmi::Context::ptr ctx, uint8_t reserved,
841                              const std::array<char, 5>& intentionalSignature)
842 {
843     // mfg filter logic is used to allow MTM keep alive command only in
844     // manfacturing mode
845 
846     constexpr std::array<char, 5> signatureOk = {'I', 'N', 'T', 'E', 'L'};
847     if (intentionalSignature != signatureOk || reserved != 0)
848     {
849         return ipmi::responseInvalidFieldRequest();
850     }
851     return ipmi::response(resetMtmTimer(ctx));
852 }
853 
854 static constexpr unsigned int makeCmdKey(unsigned int netFn, unsigned int cmd)
855 {
856     return (netFn << 8) | cmd;
857 }
858 
859 ipmi::Cc mfgFilterMessage(ipmi::message::Request::ptr request)
860 {
861     // Restricted commands, must be executed only in Manufacturing mode
862     switch (makeCmdKey(request->ctx->netFn, request->ctx->cmd))
863     {
864         // i2c master write read command needs additional checking
865         case makeCmdKey(ipmi::netFnApp, ipmi::app::cmdMasterWriteRead):
866             if (request->payload.size() > 4)
867             {
868                 // Allow write data count > 1 only in Special mode
869                 if (mtm.getMfgMode() == SpecialMode::none)
870                 {
871                     return ipmi::ccInsufficientPrivilege;
872                 }
873             }
874             return ipmi::ccSuccess;
875         case makeCmdKey(ipmi::netFnOemOne,
876                         ipmi::intel::general::cmdGetSmSignal):
877         case makeCmdKey(ipmi::netFnOemOne,
878                         ipmi::intel::general::cmdSetSmSignal):
879         case makeCmdKey(ipmi::netFnOemOne,
880                         ipmi::intel::general::cmdMtmKeepAlive):
881         case makeCmdKey(ipmi::netFnOemOne,
882                         ipmi::intel::general::cmdSetManufacturingData):
883         case makeCmdKey(ipmi::netFnOemOne,
884                         ipmi::intel::general::cmdGetManufacturingData):
885         case makeCmdKey(ipmi::intel::netFnGeneral,
886                         ipmi::intel::general::cmdSetFITcLayout):
887         case makeCmdKey(ipmi::netFnOemOne,
888                         ipmi::intel::general::cmdMTMBMCFeatureControl):
889         case makeCmdKey(ipmi::netFnStorage, ipmi::storage::cmdWriteFruData):
890         case makeCmdKey(ipmi::netFnOemTwo, ipmi::intel::platform::cmdClearCMOS):
891 
892             // Check for Special mode
893             if (mtm.getMfgMode() == SpecialMode::none)
894             {
895                 return ipmi::ccInvalidCommand;
896             }
897             return ipmi::ccSuccess;
898         case makeCmdKey(ipmi::netFnStorage, ipmi::storage::cmdDeleteSelEntry):
899         {
900             return ipmi::ccInvalidCommand;
901         }
902     }
903     return ipmi::ccSuccess;
904 }
905 
906 static constexpr uint8_t maxEthSize = 6;
907 static constexpr uint8_t maxSupportedEth = 3;
908 static constexpr const char* factoryEthAddrBaseFileName =
909     "/var/sofs/factory-settings/network/mac/eth";
910 
911 bool findFruDevice(ipmi::Context::ptr& ctx, uint64_t& macOffset,
912                    uint64_t& busNum, uint64_t& address)
913 {
914     boost::system::error_code ec{};
915     ObjectValueTree obj;
916 
917     // GetAll the objects under service FruDevice
918     ec = getManagedObjects(ctx, "xyz.openbmc_project.EntityManager",
919                            "/xyz/openbmc_project/inventory", obj);
920     if (ec)
921     {
922         phosphor::logging::log<phosphor::logging::level::ERR>(
923             "GetMangagedObjects failed",
924             phosphor::logging::entry("ERROR=%s", ec.message().c_str()));
925         return false;
926     }
927 
928     for (const auto& [path, fru] : obj)
929     {
930         for (const auto& [intf, propMap] : fru)
931         {
932             if (intf == "xyz.openbmc_project.Inventory.Item.Board.Motherboard")
933             {
934                 auto findBus = propMap.find("FruBus");
935                 auto findAddress = propMap.find("FruAddress");
936                 auto findMacOffset = propMap.find("MacOffset");
937                 if (findBus == propMap.end() || findAddress == propMap.end() ||
938                     findMacOffset == propMap.end())
939                 {
940                     continue;
941                 }
942 
943                 auto fruBus = std::get_if<uint64_t>(&findBus->second);
944                 auto fruAddress = std::get_if<uint64_t>(&findAddress->second);
945                 auto macFruOffset =
946                     std::get_if<uint64_t>(&findMacOffset->second);
947                 if (!fruBus || !fruAddress || !macFruOffset)
948                 {
949                     phosphor::logging::log<phosphor::logging::level::INFO>(
950                         "ERROR: MotherBoard FRU config data type invalid, not "
951                         "used");
952                     return false;
953                 }
954                 busNum = *fruBus;
955                 address = *fruAddress;
956                 macOffset = *macFruOffset;
957                 return true;
958             }
959         }
960     }
961     return false;
962 }
963 
964 static constexpr uint64_t fruEnd = 0xff;
965 // write rolls over within current page, need to keep mac within a page
966 static constexpr uint64_t fruPageSize = 0x8;
967 // MAC record struct: HEADER, MAC DATA, CheckSum
968 static constexpr uint64_t macRecordSize = maxEthSize + 2;
969 static_assert(fruPageSize >= macRecordSize,
970               "macRecordSize greater than eeprom page size");
971 static constexpr uint8_t macHeader = 0x40;
972 // Calculate new checksum for fru info area
973 static uint8_t calculateChecksum(std::vector<uint8_t>::const_iterator iter,
974                                  std::vector<uint8_t>::const_iterator end)
975 {
976     constexpr int checksumMod = 256;
977     uint8_t sum = std::accumulate(iter, end, static_cast<uint8_t>(0));
978     return (checksumMod - sum) % checksumMod;
979 }
980 
981 bool readMacFromFru(ipmi::Context::ptr ctx, uint8_t macIndex,
982                     std::array<uint8_t, maxEthSize>& ethData)
983 {
984     uint64_t macOffset = fruEnd;
985     uint64_t fruBus = 0;
986     uint64_t fruAddress = 0;
987 
988     if (findFruDevice(ctx, macOffset, fruBus, fruAddress))
989     {
990         phosphor::logging::log<phosphor::logging::level::INFO>(
991             "Found mac fru",
992             phosphor::logging::entry("BUS=%d", static_cast<uint8_t>(fruBus)),
993             phosphor::logging::entry("ADDRESS=%d",
994                                      static_cast<uint8_t>(fruAddress)));
995 
996         if (macOffset % fruPageSize)
997         {
998             macOffset = (macOffset / fruPageSize + 1) * fruPageSize;
999         }
1000         macOffset += macIndex * fruPageSize;
1001         if ((macOffset + macRecordSize) > fruEnd)
1002         {
1003             phosphor::logging::log<phosphor::logging::level::ERR>(
1004                 "ERROR: read fru mac failed, offset invalid");
1005             return false;
1006         }
1007         std::vector<uint8_t> writeData;
1008         writeData.push_back(static_cast<uint8_t>(macOffset));
1009         std::vector<uint8_t> readBuf(macRecordSize);
1010         std::string i2cBus = "/dev/i2c-" + std::to_string(fruBus);
1011         ipmi::Cc retI2C =
1012             ipmi::i2cWriteRead(i2cBus, fruAddress, writeData, readBuf);
1013         if (retI2C == ipmi::ccSuccess)
1014         {
1015             uint8_t cs = calculateChecksum(readBuf.cbegin(), readBuf.cend());
1016             if (cs == 0)
1017             {
1018                 std::copy(++readBuf.begin(), --readBuf.end(), ethData.data());
1019                 return true;
1020             }
1021         }
1022     }
1023     return false;
1024 }
1025 
1026 ipmi::Cc writeMacToFru(ipmi::Context::ptr ctx, uint8_t macIndex,
1027                        std::array<uint8_t, maxEthSize>& ethData)
1028 {
1029     uint64_t macOffset = fruEnd;
1030     uint64_t fruBus = 0;
1031     uint64_t fruAddress = 0;
1032 
1033     if (findFruDevice(ctx, macOffset, fruBus, fruAddress))
1034     {
1035         phosphor::logging::log<phosphor::logging::level::INFO>(
1036             "Found mac fru",
1037             phosphor::logging::entry("BUS=%d", static_cast<uint8_t>(fruBus)),
1038             phosphor::logging::entry("ADDRESS=%d",
1039                                      static_cast<uint8_t>(fruAddress)));
1040 
1041         if (macOffset % fruPageSize)
1042         {
1043             macOffset = (macOffset / fruPageSize + 1) * fruPageSize;
1044         }
1045         macOffset += macIndex * fruPageSize;
1046         if ((macOffset + macRecordSize) > fruEnd)
1047         {
1048             phosphor::logging::log<phosphor::logging::level::ERR>(
1049                 "ERROR: write mac fru failed, offset invalid.");
1050             return ipmi::ccParmOutOfRange;
1051         }
1052         std::vector<uint8_t> writeData;
1053         writeData.reserve(macRecordSize + 1); // include start location
1054         writeData.push_back(static_cast<uint8_t>(macOffset));
1055         writeData.push_back(macHeader);
1056         std::for_each(ethData.cbegin(), ethData.cend(),
1057                       [&](uint8_t i) { writeData.push_back(i); });
1058         uint8_t macCheckSum =
1059             calculateChecksum(++writeData.cbegin(), writeData.cend());
1060         writeData.push_back(macCheckSum);
1061 
1062         std::string i2cBus = "/dev/i2c-" + std::to_string(fruBus);
1063         std::vector<uint8_t> readBuf;
1064         ipmi::Cc ret =
1065             ipmi::i2cWriteRead(i2cBus, fruAddress, writeData, readBuf);
1066 
1067         // prepare for read to detect chip is write protected
1068         writeData.resize(1);
1069         readBuf.resize(maxEthSize + 1); // include macHeader
1070 
1071         switch (ret)
1072         {
1073             case ipmi::ccSuccess:
1074                 // Wait for internal write cycle to complete
1075                 // example: ATMEL 24c0x chip has Twr spec as 5ms
1076 
1077                 // Ideally we want yield wait, but currently following code
1078                 // crash with "thread not supported"
1079                 // boost::asio::deadline_timer timer(
1080                 //    boost::asio::get_associated_executor(ctx->yield),
1081                 //    boost::posix_time::seconds(1));
1082                 // timer.async_wait(ctx->yield);
1083                 // use usleep as temp WA
1084                 usleep(5000);
1085                 if (ipmi::i2cWriteRead(i2cBus, fruAddress, writeData,
1086                                        readBuf) == ipmi::ccSuccess)
1087                 {
1088                     if (std::equal(ethData.begin(), ethData.end(),
1089                                    ++readBuf.begin())) // skip macHeader
1090                     {
1091                         return ipmi::ccSuccess;
1092                     }
1093                     phosphor::logging::log<phosphor::logging::level::INFO>(
1094                         "INFO: write mac fru verify failed, fru may be write "
1095                         "protected.");
1096                 }
1097                 return ipmi::ccCommandNotAvailable;
1098             default:
1099                 if (ipmi::i2cWriteRead(i2cBus, fruAddress, writeData,
1100                                        readBuf) == ipmi::ccSuccess)
1101                 {
1102                     phosphor::logging::log<phosphor::logging::level::INFO>(
1103                         "INFO: write mac fru failed, but successfully read "
1104                         "from fru, fru may be write protected.");
1105                     return ipmi::ccCommandNotAvailable;
1106                 }
1107                 else // assume failure is due to no eeprom on board
1108                 {
1109                     phosphor::logging::log<phosphor::logging::level::ERR>(
1110                         "ERROR: write mac fru failed, assume no eeprom is "
1111                         "available.");
1112                 }
1113                 break;
1114         }
1115     }
1116     // no FRU eeprom found
1117     return ipmi::ccDestinationUnavailable;
1118 }
1119 
1120 ipmi::RspType<> setManufacturingData(ipmi::Context::ptr ctx, uint8_t dataType,
1121                                      std::array<uint8_t, maxEthSize> ethData)
1122 {
1123     // mfg filter logic will restrict this command executing only in mfg
1124     // mode.
1125     if (dataType >= maxSupportedEth)
1126     {
1127         return ipmi::responseParmOutOfRange();
1128     }
1129 
1130     constexpr uint8_t invalidData = 0;
1131     constexpr uint8_t validData = 1;
1132 
1133     ipmi::Cc ret = writeMacToFru(ctx, dataType, ethData);
1134     if (ret != ipmi::ccDestinationUnavailable)
1135     {
1136         resetMtmTimer(ctx);
1137         return response(ret);
1138     }
1139 
1140     constexpr uint8_t ethAddrStrSize =
1141         19; // XX:XX:XX:XX:XX:XX + \n + null termination;
1142     std::vector<uint8_t> buff(ethAddrStrSize);
1143     std::snprintf(reinterpret_cast<char*>(buff.data()), ethAddrStrSize,
1144                   "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx\n", ethData.at(0),
1145                   ethData.at(1), ethData.at(2), ethData.at(3), ethData.at(4),
1146                   ethData.at(5));
1147     std::ofstream oEthFile(factoryEthAddrBaseFileName +
1148                                std::to_string(dataType),
1149                            std::ofstream::out);
1150     if (!oEthFile.good())
1151     {
1152         return ipmi::responseUnspecifiedError();
1153     }
1154 
1155     oEthFile << reinterpret_cast<char*>(buff.data());
1156     oEthFile.flush();
1157     oEthFile.close();
1158 
1159     resetMtmTimer(ctx);
1160     return ipmi::responseSuccess();
1161 }
1162 
1163 ipmi::RspType<uint8_t, std::array<uint8_t, maxEthSize>>
1164     getManufacturingData(ipmi::Context::ptr ctx, uint8_t dataType)
1165 {
1166     // mfg filter logic will restrict this command executing only in mfg
1167     // mode.
1168     if (dataType >= maxSupportedEth)
1169     {
1170         return ipmi::responseParmOutOfRange();
1171     }
1172     std::array<uint8_t, maxEthSize> ethData{0};
1173     constexpr uint8_t invalidData = 0;
1174     constexpr uint8_t validData = 1;
1175 
1176     std::ifstream iEthFile(factoryEthAddrBaseFileName +
1177                                std::to_string(dataType),
1178                            std::ifstream::in);
1179     if (!iEthFile.good())
1180     {
1181         if (readMacFromFru(ctx, dataType, ethData))
1182         {
1183             resetMtmTimer(ctx);
1184             return ipmi::responseSuccess(validData, ethData);
1185         }
1186         return ipmi::responseSuccess(invalidData, ethData);
1187     }
1188     std::string ethStr;
1189     iEthFile >> ethStr;
1190     uint8_t* data = ethData.data();
1191     std::sscanf(ethStr.c_str(), "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx",
1192                 data, (data + 1), (data + 2), (data + 3), (data + 4),
1193                 (data + 5));
1194 
1195     resetMtmTimer(ctx);
1196     return ipmi::responseSuccess(validData, ethData);
1197 }
1198 
1199 /** @brief implements slot master write read IPMI command which can be used
1200  * for low-level I2C/SMBus write, read or write-read access for PCIE slots
1201  * @param reserved - skip 3 bit
1202  * @param muxType - mux type
1203  * @param addressType - address type
1204  * @param bbSlotNum - baseboard slot number
1205  * @param riserSlotNum - riser slot number
1206  * @param reserved2 - skip 2 bit
1207  * @param slaveAddr - slave address
1208  * @param readCount - number of bytes to be read
1209  * @param writeData - data to be written
1210  *
1211  * @returns IPMI completion code plus response data
1212  */
1213 
1214 ipmi::RspType<std::vector<uint8_t>> appSlotI2CMasterWriteRead(
1215     uint3_t reserved, uint3_t muxType, uint2_t addressType, uint3_t bbSlotNum,
1216     uint3_t riserSlotNum, uint2_t reserved2, uint8_t slaveAddr,
1217     uint8_t readCount, std::vector<uint8_t> writeData)
1218 {
1219     if (reserved || reserved2)
1220     {
1221         return ipmi::responseInvalidFieldRequest();
1222     }
1223     const size_t writeCount = writeData.size();
1224     std::string i2cBus;
1225     if (addressType == slotAddressTypeBus)
1226     {
1227         std::string path = "/dev/i2c-mux/";
1228         if (muxType == bbRiserMux)
1229         {
1230             path += "Riser_" + std::to_string(static_cast<uint8_t>(bbSlotNum)) +
1231                     "_Mux/Pcie_Slot_" +
1232                     std::to_string(static_cast<uint8_t>(riserSlotNum));
1233         }
1234         else if (muxType == leftRiserMux)
1235         {
1236             path += "Left_Riser_Mux/Slot_" +
1237                     std::to_string(static_cast<uint8_t>(riserSlotNum));
1238         }
1239         else if (muxType == rightRiserMux)
1240         {
1241             path += "Right_Riser_Mux/Slot_" +
1242                     std::to_string(static_cast<uint8_t>(riserSlotNum));
1243         }
1244         else if (muxType == pcieMux)
1245         {
1246             path += "PCIe_Mux/Slot_" +
1247                     std::to_string(static_cast<uint8_t>(riserSlotNum));
1248         }
1249         else if (muxType == hsbpMux)
1250         {
1251             path += "HSBP_Mux/Slot" +
1252                     std::to_string(static_cast<uint8_t>(riserSlotNum));
1253         }
1254         phosphor::logging::log<phosphor::logging::level::DEBUG>(
1255             ("Path is: " + path).c_str());
1256         if (std::filesystem::exists(path) && std::filesystem::is_symlink(path))
1257         {
1258             i2cBus = std::filesystem::read_symlink(path);
1259         }
1260         else
1261         {
1262             phosphor::logging::log<phosphor::logging::level::ERR>(
1263                 "Master write read command: Cannot get BusID");
1264             return ipmi::responseInvalidFieldRequest();
1265         }
1266     }
1267     else if (addressType == slotAddressTypeUniqueid)
1268     {
1269         i2cBus = "/dev/i2c-" +
1270                  std::to_string(static_cast<uint8_t>(bbSlotNum) |
1271                                 (static_cast<uint8_t>(riserSlotNum) << 3));
1272     }
1273     else
1274     {
1275         phosphor::logging::log<phosphor::logging::level::ERR>(
1276             "Master write read command: invalid request");
1277         return ipmi::responseInvalidFieldRequest();
1278     }
1279 
1280     // Allow single byte write as it is offset byte to read the data, rest
1281     // allow only in Special mode.
1282     if (writeCount > 1)
1283     {
1284         if (mtm.getMfgMode() == SpecialMode::none)
1285         {
1286             return ipmi::responseInsufficientPrivilege();
1287         }
1288     }
1289 
1290     if (readCount > slotI2CMaxReadSize)
1291     {
1292         phosphor::logging::log<phosphor::logging::level::ERR>(
1293             "Master write read command: Read count exceeds limit");
1294         return ipmi::responseParmOutOfRange();
1295     }
1296 
1297     if (!readCount && !writeCount)
1298     {
1299         phosphor::logging::log<phosphor::logging::level::ERR>(
1300             "Master write read command: Read & write count are 0");
1301         return ipmi::responseInvalidFieldRequest();
1302     }
1303 
1304     std::vector<uint8_t> readBuf(readCount);
1305 
1306     ipmi::Cc retI2C = ipmi::i2cWriteRead(i2cBus, slaveAddr, writeData, readBuf);
1307     if (retI2C != ipmi::ccSuccess)
1308     {
1309         return ipmi::response(retI2C);
1310     }
1311 
1312     return ipmi::responseSuccess(readBuf);
1313 }
1314 
1315 ipmi::RspType<> clearCMOS()
1316 {
1317     // There is an i2c device on bus 4, the slave address is 0x38. Based on
1318     // the spec, writing 0x1 to address 0x61 on this device, will trigger
1319     // the clear CMOS action.
1320     constexpr uint8_t slaveAddr = 0x38;
1321     std::string i2cBus = "/dev/i2c-4";
1322     std::vector<uint8_t> writeData = {0x61, 0x1};
1323     std::vector<uint8_t> readBuf(0);
1324 
1325     ipmi::Cc retI2C = ipmi::i2cWriteRead(i2cBus, slaveAddr, writeData, readBuf);
1326     return ipmi::response(retI2C);
1327 }
1328 
1329 ipmi::RspType<> setFITcLayout(uint32_t layout)
1330 {
1331     static constexpr const char* factoryFITcLayout =
1332         "/var/sofs/factory-settings/layout/fitc";
1333     std::filesystem::path fitcDir =
1334         std::filesystem::path(factoryFITcLayout).parent_path();
1335     std::error_code ec;
1336     std::filesystem::create_directories(fitcDir, ec);
1337     if (ec)
1338     {
1339         return ipmi::responseUnspecifiedError();
1340     }
1341     try
1342     {
1343         std::ofstream file(factoryFITcLayout);
1344         file << layout;
1345         file.flush();
1346         file.close();
1347     }
1348     catch (const std::exception& e)
1349     {
1350         return ipmi::responseUnspecifiedError();
1351     }
1352 
1353     return ipmi::responseSuccess();
1354 }
1355 
1356 static std::vector<std::string>
1357     getMCTPServiceConfigPaths(ipmi::Context::ptr& ctx)
1358 {
1359     boost::system::error_code ec;
1360     auto configPaths = ctx->bus->yield_method_call<std::vector<std::string>>(
1361         ctx->yield, ec, "xyz.openbmc_project.ObjectMapper",
1362         "/xyz/openbmc_project/object_mapper",
1363         "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths",
1364         "/xyz/openbmc_project/inventory/system/board", 2,
1365         std::array<const char*, 1>{
1366             "xyz.openbmc_project.Configuration.MctpConfiguration"});
1367     if (ec)
1368     {
1369         throw std::runtime_error(
1370             "Failed to query configuration sub tree objects");
1371     }
1372     return configPaths;
1373 }
1374 
1375 static ipmi::RspType<> startOrStopService(ipmi::Context::ptr& ctx,
1376                                           const uint8_t enable,
1377                                           const std::string& serviceName,
1378                                           bool disableOrEnableUnitFiles = true)
1379 {
1380     constexpr bool runtimeOnly = false;
1381     constexpr bool force = false;
1382 
1383     boost::system::error_code ec;
1384     switch (enable)
1385     {
1386         case ipmi::SupportedFeatureActions::stop:
1387             ctx->bus->yield_method_call(ctx->yield, ec, systemDService,
1388                                         systemDObjPath, systemDMgrIntf,
1389                                         "StopUnit", serviceName, "replace");
1390             break;
1391         case ipmi::SupportedFeatureActions::start:
1392             ctx->bus->yield_method_call(ctx->yield, ec, systemDService,
1393                                         systemDObjPath, systemDMgrIntf,
1394                                         "StartUnit", serviceName, "replace");
1395             break;
1396         case ipmi::SupportedFeatureActions::disable:
1397             if (disableOrEnableUnitFiles == true)
1398             {
1399                 ctx->bus->yield_method_call(
1400                     ctx->yield, ec, systemDService, systemDObjPath,
1401                     systemDMgrIntf, "DisableUnitFiles",
1402                     std::array<const char*, 1>{serviceName.c_str()},
1403                     runtimeOnly);
1404             }
1405             ctx->bus->yield_method_call(
1406                 ctx->yield, ec, systemDService, systemDObjPath, systemDMgrIntf,
1407                 "MaskUnitFiles",
1408                 std::array<const char*, 1>{serviceName.c_str()}, runtimeOnly,
1409                 force);
1410             break;
1411         case ipmi::SupportedFeatureActions::enable:
1412             ctx->bus->yield_method_call(
1413                 ctx->yield, ec, systemDService, systemDObjPath, systemDMgrIntf,
1414                 "UnmaskUnitFiles",
1415                 std::array<const char*, 1>{serviceName.c_str()}, runtimeOnly);
1416             if (disableOrEnableUnitFiles == true)
1417             {
1418                 ctx->bus->yield_method_call(
1419                     ctx->yield, ec, systemDService, systemDObjPath,
1420                     systemDMgrIntf, "EnableUnitFiles",
1421                     std::array<const char*, 1>{serviceName.c_str()},
1422                     runtimeOnly, force);
1423             }
1424             break;
1425         default:
1426             phosphor::logging::log<phosphor::logging::level::WARNING>(
1427                 "ERROR: Invalid feature action selected",
1428                 phosphor::logging::entry("ACTION=%d", enable));
1429             return ipmi::responseInvalidFieldRequest();
1430     }
1431     if (ec)
1432     {
1433         phosphor::logging::log<phosphor::logging::level::WARNING>(
1434             "ERROR: Service start or stop failed",
1435             phosphor::logging::entry("SERVICE=%s", serviceName.c_str()));
1436         return ipmi::responseUnspecifiedError();
1437     }
1438     return ipmi::responseSuccess();
1439 }
1440 
1441 static std::string getMCTPServiceName(const std::string& objectPath)
1442 {
1443     const auto serviceArgument = boost::algorithm::replace_all_copy(
1444         boost::algorithm::replace_first_copy(
1445             objectPath, "/xyz/openbmc_project/inventory/system/board/", ""),
1446         "/", "_2f");
1447     std::string unitName =
1448         "xyz.openbmc_project.mctpd@" + serviceArgument + ".service";
1449     return unitName;
1450 }
1451 
1452 static ipmi::RspType<> handleMCTPFeature(ipmi::Context::ptr& ctx,
1453                                          const uint8_t enable,
1454                                          const std::string& binding)
1455 {
1456     std::vector<std::string> configPaths;
1457     try
1458     {
1459         configPaths = getMCTPServiceConfigPaths(ctx);
1460     }
1461     catch (const std::exception& e)
1462     {
1463         phosphor::logging::log<phosphor::logging::level::ERR>(e.what());
1464         return ipmi::responseUnspecifiedError();
1465     }
1466 
1467     for (const auto& objectPath : configPaths)
1468     {
1469         auto const pos = objectPath.find_last_of('/');
1470         if (binding == objectPath.substr(pos + 1))
1471         {
1472             return startOrStopService(ctx, enable,
1473                                       getMCTPServiceName(objectPath), false);
1474         }
1475     }
1476     return ipmi::responseSuccess();
1477 }
1478 
1479 /** @brief implements MTM BMC Feature Control IPMI command which can be
1480  * used to enable or disable the supported BMC features.
1481  * @param yield - context object that represents the currently executing
1482  * coroutine
1483  * @param feature - feature enum to enable or disable
1484  * @param enable - enable or disable the feature
1485  * @param featureArg - custom arguments for that feature
1486  * @param reserved - reserved for future use
1487  *
1488  * @returns IPMI completion code
1489  */
1490 ipmi::RspType<> mtmBMCFeatureControl(ipmi::Context::ptr ctx,
1491                                      const uint8_t feature,
1492                                      const uint8_t enable,
1493                                      const uint8_t featureArg,
1494                                      const uint16_t reserved)
1495 {
1496     if (reserved != 0)
1497     {
1498         return ipmi::responseInvalidFieldRequest();
1499     }
1500 
1501     switch (feature)
1502     {
1503         case ipmi::SupportedFeatureControls::mctp:
1504             switch (featureArg)
1505             {
1506                 case ipmi::SupportedMCTPBindings::mctpPCIe:
1507                     return handleMCTPFeature(ctx, enable, "MCTP_PCIe");
1508                 case ipmi::SupportedMCTPBindings::mctpSMBusHSBP:
1509                     return handleMCTPFeature(ctx, enable, "MCTP_SMBus_HSBP");
1510                 case ipmi::SupportedMCTPBindings::mctpSMBusPCIeSlot:
1511                     return handleMCTPFeature(ctx, enable,
1512                                              "MCTP_SMBus_PCIe_slot");
1513                 default:
1514                     return ipmi::responseInvalidFieldRequest();
1515             }
1516             break;
1517         case ipmi::SupportedFeatureControls::pcieScan:
1518             if (featureArg != 0)
1519             {
1520                 return ipmi::responseInvalidFieldRequest();
1521             }
1522             startOrStopService(ctx, enable, "xyz.openbmc_project.PCIe.service");
1523             break;
1524         default:
1525             return ipmi::responseInvalidFieldRequest();
1526     }
1527     return ipmi::responseSuccess();
1528 }
1529 } // namespace ipmi
1530 
1531 void register_mtm_commands() __attribute__((constructor));
1532 void register_mtm_commands()
1533 {
1534     // <Get SM Signal>
1535     ipmi::registerHandler(ipmi::prioOemBase, ipmi::intel::netFnGeneral,
1536                           ipmi::intel::general::cmdGetSmSignal,
1537                           ipmi::Privilege::Admin, ipmi::appMTMGetSignal);
1538 
1539     ipmi::registerHandler(ipmi::prioOemBase, ipmi::intel::netFnGeneral,
1540                           ipmi::intel::general::cmdSetSmSignal,
1541                           ipmi::Privilege::Admin, ipmi::appMTMSetSignal);
1542 
1543     ipmi::registerHandler(ipmi::prioOemBase, ipmi::intel::netFnGeneral,
1544                           ipmi::intel::general::cmdMtmKeepAlive,
1545                           ipmi::Privilege::Admin, ipmi::mtmKeepAlive);
1546 
1547     ipmi::registerHandler(ipmi::prioOemBase, ipmi::intel::netFnGeneral,
1548                           ipmi::intel::general::cmdSetManufacturingData,
1549                           ipmi::Privilege::Admin, ipmi::setManufacturingData);
1550 
1551     ipmi::registerHandler(ipmi::prioOemBase, ipmi::intel::netFnGeneral,
1552                           ipmi::intel::general::cmdGetManufacturingData,
1553                           ipmi::Privilege::Admin, ipmi::getManufacturingData);
1554 
1555     ipmi::registerHandler(ipmi::prioOemBase, ipmi::intel::netFnGeneral,
1556                           ipmi::intel::general::cmdSetFITcLayout,
1557                           ipmi::Privilege::Admin, ipmi::setFITcLayout);
1558 
1559     ipmi::registerHandler(ipmi::prioOemBase, ipmi::intel::netFnGeneral,
1560                           ipmi::intel::general::cmdMTMBMCFeatureControl,
1561                           ipmi::Privilege::Admin, ipmi::mtmBMCFeatureControl);
1562 
1563     ipmi::registerHandler(ipmi::prioOemBase, ipmi::intel::netFnApp,
1564                           ipmi::intel::general::cmdSlotI2CMasterWriteRead,
1565                           ipmi::Privilege::Admin,
1566                           ipmi::appSlotI2CMasterWriteRead);
1567 
1568     ipmi::registerHandler(ipmi::prioOemBase, ipmi::intel::netFnPlatform,
1569                           ipmi::intel::platform::cmdClearCMOS,
1570                           ipmi::Privilege::Admin, ipmi::clearCMOS);
1571 
1572     ipmi::registerFilter(ipmi::prioOemBase,
1573                          [](ipmi::message::Request::ptr request) {
1574                              return ipmi::mfgFilterMessage(request);
1575                          });
1576 }
1577