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