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