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