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::exception& 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::exception& 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::exception& 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 ipmi::RspType<> setManufacturingData(ipmi::Context::ptr ctx, uint8_t dataType,
824                                      std::array<uint8_t, maxEthSize> ethData)
825 {
826     // mfg filter logic will restrict this command executing only in mfg mode.
827     if (dataType >= maxSupportedEth)
828     {
829         return ipmi::responseParmOutOfRange();
830     }
831 
832     constexpr uint8_t invalidData = 0;
833     constexpr uint8_t validData = 1;
834     constexpr uint8_t ethAddrStrSize =
835         19; // XX:XX:XX:XX:XX:XX + \n + null termination;
836     std::vector<uint8_t> buff(ethAddrStrSize);
837     std::snprintf(reinterpret_cast<char*>(buff.data()), ethAddrStrSize,
838                   "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx\n", ethData.at(0),
839                   ethData.at(1), ethData.at(2), ethData.at(3), ethData.at(4),
840                   ethData.at(5));
841     std::ofstream oEthFile(factoryEthAddrBaseFileName +
842                                std::to_string(dataType),
843                            std::ofstream::out);
844     if (!oEthFile.good())
845     {
846         return ipmi::responseUnspecifiedError();
847     }
848 
849     oEthFile << reinterpret_cast<char*>(buff.data());
850     oEthFile.flush();
851     oEthFile.close();
852 
853     resetMtmTimer(ctx);
854     return ipmi::responseSuccess();
855 }
856 
857 ipmi::RspType<uint8_t, std::array<uint8_t, maxEthSize>>
858     getManufacturingData(ipmi::Context::ptr ctx, uint8_t dataType)
859 {
860     // mfg filter logic will restrict this command executing only in mfg mode.
861     if (dataType >= maxSupportedEth)
862     {
863         return ipmi::responseParmOutOfRange();
864     }
865     std::array<uint8_t, maxEthSize> ethData{0};
866     constexpr uint8_t invalidData = 0;
867     constexpr uint8_t validData = 1;
868 
869     std::ifstream iEthFile(factoryEthAddrBaseFileName +
870                                std::to_string(dataType),
871                            std::ifstream::in);
872     if (!iEthFile.good())
873     {
874         return ipmi::responseSuccess(invalidData, ethData);
875     }
876     std::string ethStr;
877     iEthFile >> ethStr;
878     uint8_t* data = ethData.data();
879     std::sscanf(ethStr.c_str(), "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx",
880                 data, (data + 1), (data + 2), (data + 3), (data + 4),
881                 (data + 5));
882 
883     resetMtmTimer(ctx);
884     return ipmi::responseSuccess(validData, ethData);
885 }
886 
887 /** @brief implements slot master write read IPMI command which can be used for
888  * low-level I2C/SMBus write, read or write-read access for PCIE slots
889  * @param reserved - skip 6 bit
890  * @param addressType - address type
891  * @param bbSlotNum - baseboard slot number
892  * @param riserSlotNum - riser slot number
893  * @param reserved2 - skip 2 bit
894  * @param slaveAddr - slave address
895  * @param readCount - number of bytes to be read
896  * @param writeData - data to be written
897  *
898  * @returns IPMI completion code plus response data
899  */
900 ipmi::RspType<std::vector<uint8_t>>
901     appSlotI2CMasterWriteRead(uint6_t reserved, uint2_t addressType,
902                               uint3_t bbSlotNum, uint3_t riserSlotNum,
903                               uint2_t resvered2, uint8_t slaveAddr,
904                               uint8_t readCount, std::vector<uint8_t> writeData)
905 {
906     const size_t writeCount = writeData.size();
907     std::string i2cBus;
908     if (addressType == slotAddressTypeBus)
909     {
910         std::string path = "/dev/i2c-mux/Riser_" +
911                            std::to_string(static_cast<uint8_t>(bbSlotNum)) +
912                            "_Mux/Pcie_Slot_" +
913                            std::to_string(static_cast<uint8_t>(riserSlotNum));
914 
915         if (std::filesystem::exists(path) && std::filesystem::is_symlink(path))
916         {
917             i2cBus = std::filesystem::read_symlink(path);
918         }
919         else
920         {
921             phosphor::logging::log<phosphor::logging::level::ERR>(
922                 "Master write read command: Cannot get BusID");
923             return ipmi::responseInvalidFieldRequest();
924         }
925     }
926     else if (addressType == slotAddressTypeUniqueid)
927     {
928         i2cBus = "/dev/i2c-" +
929                  std::to_string(static_cast<uint8_t>(bbSlotNum) |
930                                 (static_cast<uint8_t>(riserSlotNum) << 3));
931     }
932     else
933     {
934         phosphor::logging::log<phosphor::logging::level::ERR>(
935             "Master write read command: invalid request");
936         return ipmi::responseInvalidFieldRequest();
937     }
938 
939     // Allow single byte write as it is offset byte to read the data, rest allow
940     // only in Special mode.
941     if (writeCount > 1)
942     {
943         if (mtm.getMfgMode() == SpecialMode::none)
944         {
945             return ipmi::responseInsufficientPrivilege();
946         }
947     }
948 
949     if (readCount > slotI2CMaxReadSize)
950     {
951         phosphor::logging::log<phosphor::logging::level::ERR>(
952             "Master write read command: Read count exceeds limit");
953         return ipmi::responseParmOutOfRange();
954     }
955 
956     if (!readCount && !writeCount)
957     {
958         phosphor::logging::log<phosphor::logging::level::ERR>(
959             "Master write read command: Read & write count are 0");
960         return ipmi::responseInvalidFieldRequest();
961     }
962 
963     std::vector<uint8_t> readBuf(readCount);
964 
965     ipmi::Cc retI2C = ipmi::i2cWriteRead(i2cBus, slaveAddr, writeData, readBuf);
966     if (retI2C != ipmi::ccSuccess)
967     {
968         return ipmi::response(retI2C);
969     }
970 
971     return ipmi::responseSuccess(readBuf);
972 }
973 
974 ipmi::RspType<> clearCMOS()
975 {
976     // There is an i2c device on bus 4, the slave address is 0x38. Based on the
977     // spec, writing 0x1 to address 0x61 on this device, will trigger the clear
978     // CMOS action.
979     constexpr uint8_t slaveAddr = 0x38;
980     std::string i2cBus = "/dev/i2c-4";
981     std::vector<uint8_t> writeData = {0x61, 0x1};
982     std::vector<uint8_t> readBuf(0);
983 
984     ipmi::Cc retI2C = ipmi::i2cWriteRead(i2cBus, slaveAddr, writeData, readBuf);
985     return ipmi::response(retI2C);
986 }
987 
988 ipmi::RspType<> setFITcLayout(uint32_t layout)
989 {
990     static constexpr const char* factoryFITcLayout =
991         "/var/sofs/factory-settings/layout/fitc";
992     std::filesystem::path fitcDir =
993         std::filesystem::path(factoryFITcLayout).parent_path();
994     std::error_code ec;
995     std::filesystem::create_directories(fitcDir, ec);
996     if (ec)
997     {
998         return ipmi::responseUnspecifiedError();
999     }
1000     try
1001     {
1002         std::ofstream file(factoryFITcLayout);
1003         file << layout;
1004         file.flush();
1005         file.close();
1006     }
1007     catch (const std::exception& e)
1008     {
1009         return ipmi::responseUnspecifiedError();
1010     }
1011 
1012     return ipmi::responseSuccess();
1013 }
1014 
1015 static std::vector<std::string>
1016     getMCTPServiceConfigPaths(ipmi::Context::ptr& ctx)
1017 {
1018     boost::system::error_code ec;
1019     auto configPaths = ctx->bus->yield_method_call<std::vector<std::string>>(
1020         ctx->yield, ec, "xyz.openbmc_project.ObjectMapper",
1021         "/xyz/openbmc_project/object_mapper",
1022         "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths",
1023         "/xyz/openbmc_project/inventory/system/board", 2,
1024         std::array<const char*, 1>{
1025             "xyz.openbmc_project.Configuration.MctpConfiguration"});
1026     if (ec)
1027     {
1028         throw std::runtime_error(
1029             "Failed to query configuration sub tree objects");
1030     }
1031     return configPaths;
1032 }
1033 
1034 static ipmi::RspType<> startOrStopService(ipmi::Context::ptr& ctx,
1035                                           const uint8_t enable,
1036                                           const std::string& serviceName,
1037                                           bool disableOrEnableUnitFiles = true)
1038 {
1039     constexpr bool runtimeOnly = false;
1040     constexpr bool force = false;
1041 
1042     boost::system::error_code ec;
1043     switch (enable)
1044     {
1045         case ipmi::SupportedFeatureActions::stop:
1046             ctx->bus->yield_method_call(ctx->yield, ec, systemDService,
1047                                         systemDObjPath, systemDMgrIntf,
1048                                         "StopUnit", serviceName, "replace");
1049             break;
1050         case ipmi::SupportedFeatureActions::start:
1051             ctx->bus->yield_method_call(ctx->yield, ec, systemDService,
1052                                         systemDObjPath, systemDMgrIntf,
1053                                         "StartUnit", serviceName, "replace");
1054             break;
1055         case ipmi::SupportedFeatureActions::disable:
1056             if (disableOrEnableUnitFiles == true)
1057             {
1058                 ctx->bus->yield_method_call(
1059                     ctx->yield, ec, systemDService, systemDObjPath,
1060                     systemDMgrIntf, "DisableUnitFiles",
1061                     std::array<const char*, 1>{serviceName.c_str()},
1062                     runtimeOnly);
1063             }
1064             ctx->bus->yield_method_call(
1065                 ctx->yield, ec, systemDService, systemDObjPath, systemDMgrIntf,
1066                 "MaskUnitFiles",
1067                 std::array<const char*, 1>{serviceName.c_str()}, runtimeOnly,
1068                 force);
1069             break;
1070         case ipmi::SupportedFeatureActions::enable:
1071             ctx->bus->yield_method_call(
1072                 ctx->yield, ec, systemDService, systemDObjPath, systemDMgrIntf,
1073                 "UnmaskUnitFiles",
1074                 std::array<const char*, 1>{serviceName.c_str()}, runtimeOnly);
1075             if (disableOrEnableUnitFiles == true)
1076             {
1077                 ctx->bus->yield_method_call(
1078                     ctx->yield, ec, systemDService, systemDObjPath,
1079                     systemDMgrIntf, "EnableUnitFiles",
1080                     std::array<const char*, 1>{serviceName.c_str()},
1081                     runtimeOnly, force);
1082             }
1083             break;
1084         default:
1085             phosphor::logging::log<phosphor::logging::level::WARNING>(
1086                 "ERROR: Invalid feature action selected",
1087                 phosphor::logging::entry("ACTION=%d", enable));
1088             return ipmi::responseInvalidFieldRequest();
1089     }
1090     if (ec)
1091     {
1092         phosphor::logging::log<phosphor::logging::level::WARNING>(
1093             "ERROR: Service start or stop failed",
1094             phosphor::logging::entry("SERVICE=%s", serviceName.c_str()));
1095         return ipmi::responseUnspecifiedError();
1096     }
1097     return ipmi::responseSuccess();
1098 }
1099 
1100 static std::string getMCTPServiceName(const std::string& objectPath)
1101 {
1102     const auto serviceArgument = boost::algorithm::replace_all_copy(
1103         boost::algorithm::replace_first_copy(
1104             objectPath, "/xyz/openbmc_project/inventory/system/board/", ""),
1105         "/", "_2f");
1106     std::string unitName =
1107         "xyz.openbmc_project.mctpd@" + serviceArgument + ".service";
1108     return unitName;
1109 }
1110 
1111 static ipmi::RspType<> handleMCTPFeature(ipmi::Context::ptr& ctx,
1112                                          const uint8_t enable,
1113                                          const std::string& binding)
1114 {
1115     std::vector<std::string> configPaths;
1116     try
1117     {
1118         configPaths = getMCTPServiceConfigPaths(ctx);
1119     }
1120     catch (const std::exception& e)
1121     {
1122         phosphor::logging::log<phosphor::logging::level::ERR>(e.what());
1123         return ipmi::responseUnspecifiedError();
1124     }
1125 
1126     for (const auto& objectPath : configPaths)
1127     {
1128         auto const pos = objectPath.find_last_of('/');
1129         if (binding == objectPath.substr(pos + 1))
1130         {
1131             return startOrStopService(ctx, enable,
1132                                       getMCTPServiceName(objectPath), false);
1133         }
1134     }
1135     return ipmi::responseSuccess();
1136 }
1137 
1138 /** @brief implements MTM BMC Feature Control IPMI command which can be
1139  * used to enable or disable the supported BMC features.
1140  * @param yield - context object that represents the currently executing
1141  * coroutine
1142  * @param feature - feature enum to enable or disable
1143  * @param enable - enable or disable the feature
1144  * @param featureArg - custom arguments for that feature
1145  * @param reserved - reserved for future use
1146  *
1147  * @returns IPMI completion code
1148  */
1149 ipmi::RspType<> mtmBMCFeatureControl(ipmi::Context::ptr ctx,
1150                                      const uint8_t feature,
1151                                      const uint8_t enable,
1152                                      const uint8_t featureArg,
1153                                      const uint16_t reserved)
1154 {
1155     if (reserved != 0)
1156     {
1157         return ipmi::responseInvalidFieldRequest();
1158     }
1159 
1160     switch (feature)
1161     {
1162         case ipmi::SupportedFeatureControls::mctp:
1163             switch (featureArg)
1164             {
1165                 case ipmi::SupportedMCTPBindings::mctpPCIe:
1166                     return handleMCTPFeature(ctx, enable, "MCTP_PCIe");
1167                 case ipmi::SupportedMCTPBindings::mctpSMBusHSBP:
1168                     return handleMCTPFeature(ctx, enable, "MCTP_SMBus_HSBP");
1169                 case ipmi::SupportedMCTPBindings::mctpSMBusPCIeSlot:
1170                     return handleMCTPFeature(ctx, enable,
1171                                              "MCTP_SMBus_PCIe_slot");
1172                 default:
1173                     return ipmi::responseInvalidFieldRequest();
1174             }
1175             break;
1176         case ipmi::SupportedFeatureControls::pcieScan:
1177             if (featureArg != 0)
1178             {
1179                 return ipmi::responseInvalidFieldRequest();
1180             }
1181             startOrStopService(ctx, enable, "xyz.openbmc_project.PCIe.service");
1182             break;
1183         default:
1184             return ipmi::responseInvalidFieldRequest();
1185     }
1186     return ipmi::responseSuccess();
1187 }
1188 } // namespace ipmi
1189 
1190 void register_mtm_commands() __attribute__((constructor));
1191 void register_mtm_commands()
1192 {
1193     // <Get SM Signal>
1194     ipmi::registerHandler(ipmi::prioOemBase, ipmi::intel::netFnGeneral,
1195                           ipmi::intel::general::cmdGetSmSignal,
1196                           ipmi::Privilege::Admin, ipmi::appMTMGetSignal);
1197 
1198     ipmi::registerHandler(ipmi::prioOemBase, ipmi::intel::netFnGeneral,
1199                           ipmi::intel::general::cmdSetSmSignal,
1200                           ipmi::Privilege::Admin, ipmi::appMTMSetSignal);
1201 
1202     ipmi::registerHandler(ipmi::prioOemBase, ipmi::intel::netFnGeneral,
1203                           ipmi::intel::general::cmdMtmKeepAlive,
1204                           ipmi::Privilege::Admin, ipmi::mtmKeepAlive);
1205 
1206     ipmi::registerHandler(ipmi::prioOemBase, ipmi::intel::netFnGeneral,
1207                           ipmi::intel::general::cmdSetManufacturingData,
1208                           ipmi::Privilege::Admin, ipmi::setManufacturingData);
1209 
1210     ipmi::registerHandler(ipmi::prioOemBase, ipmi::intel::netFnGeneral,
1211                           ipmi::intel::general::cmdGetManufacturingData,
1212                           ipmi::Privilege::Admin, ipmi::getManufacturingData);
1213 
1214     ipmi::registerHandler(ipmi::prioOemBase, ipmi::intel::netFnGeneral,
1215                           ipmi::intel::general::cmdSetFITcLayout,
1216                           ipmi::Privilege::Admin, ipmi::setFITcLayout);
1217 
1218     ipmi::registerHandler(ipmi::prioOemBase, ipmi::intel::netFnGeneral,
1219                           ipmi::intel::general::cmdMTMBMCFeatureControl,
1220                           ipmi::Privilege::Admin, ipmi::mtmBMCFeatureControl);
1221 
1222     ipmi::registerHandler(ipmi::prioOemBase, ipmi::intel::netFnApp,
1223                           ipmi::intel::general::cmdSlotI2CMasterWriteRead,
1224                           ipmi::Privilege::Admin,
1225                           ipmi::appSlotI2CMasterWriteRead);
1226 
1227     ipmi::registerHandler(ipmi::prioOemBase, ipmi::intel::netFnPlatform,
1228                           ipmi::intel::platform::cmdClearCMOS,
1229                           ipmi::Privilege::Admin, ipmi::clearCMOS);
1230 
1231     ipmi::registerFilter(ipmi::prioOemBase,
1232                          [](ipmi::message::Request::ptr request) {
1233                              return ipmi::mfgFilterMessage(request);
1234                          });
1235 }
1236