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