xref: /openbmc/intel-ipmi-oem/src/manufacturingcommands.cpp (revision ae13ac627a52fbff08685fe9e157ff8c9a358aa2)
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/container/flat_map.hpp>
20 #include <filesystem>
21 #include <fstream>
22 #include <ipmid/api.hpp>
23 #include <manufacturingcommands.hpp>
24 #include <oemcommands.hpp>
25 
26 namespace ipmi
27 {
28 
29 Manufacturing mtm;
30 
31 static auto revertTimeOut =
32     std::chrono::duration_cast<std::chrono::microseconds>(
33         std::chrono::seconds(60)); // 1 minute timeout
34 
35 static constexpr uint8_t slotAddressTypeBus = 0;
36 static constexpr uint8_t slotAddressTypeUniqueid = 1;
37 static constexpr uint8_t slotI2CMaxReadSize = 35;
38 
39 static constexpr const char* callbackMgrService =
40     "xyz.openbmc_project.CallbackManager";
41 static constexpr const char* callbackMgrIntf =
42     "xyz.openbmc_project.CallbackManager";
43 static constexpr const char* callbackMgrObjPath =
44     "/xyz/openbmc_project/CallbackManager";
45 static constexpr const char* retriggerLedUpdate = "RetriggerLEDUpdate";
46 
47 const static constexpr char* systemDService = "org.freedesktop.systemd1";
48 const static constexpr char* systemDObjPath = "/org/freedesktop/systemd1";
49 const static constexpr char* systemDMgrIntf =
50     "org.freedesktop.systemd1.Manager";
51 const static constexpr char* pidControlService = "phosphor-pid-control.service";
52 
53 static inline Cc resetMtmTimer(ipmi::Context::ptr ctx)
54 {
55     boost::system::error_code ec;
56     ctx->bus->yield_method_call<>(ctx->yield, ec, specialModeService,
57                                   specialModeObjPath, specialModeIntf,
58                                   "ResetTimer");
59     if (ec)
60     {
61         phosphor::logging::log<phosphor::logging::level::ERR>(
62             "Failed to reset the manufacturing mode timer");
63         return ccUnspecifiedError;
64     }
65     return ccSuccess;
66 }
67 
68 int getGpioPathForSmSignal(const SmSignalGet signal, std::string& path)
69 {
70     switch (signal)
71     {
72         case SmSignalGet::smPowerButton:
73             path = "/xyz/openbmc_project/chassis/buttons/power";
74             break;
75         case SmSignalGet::smResetButton:
76             path = "/xyz/openbmc_project/chassis/buttons/reset";
77             break;
78         case SmSignalGet::smNMIButton:
79             path = "/xyz/openbmc_project/chassis/buttons/nmi";
80             break;
81         case SmSignalGet::smIdentifyButton:
82             path = "/xyz/openbmc_project/chassis/buttons/id";
83             break;
84         default:
85             return -1;
86             break;
87     }
88     return 0;
89 }
90 
91 ipmi_ret_t ledStoreAndSet(SmSignalSet signal, const std::string& setState)
92 {
93     LedProperty* ledProp = mtm.findLedProperty(signal);
94     if (ledProp == nullptr)
95     {
96         return IPMI_CC_INVALID_FIELD_REQUEST;
97     }
98 
99     std::string ledName = ledProp->getName();
100     std::string ledService = ledServicePrefix + ledName;
101     std::string ledPath = ledPathPrefix + ledName;
102     ipmi::Value presentState;
103 
104     if (false == ledProp->getLock())
105     {
106         if (mtm.getProperty(ledService.c_str(), ledPath.c_str(), ledIntf,
107                             "State", &presentState) != 0)
108         {
109             return IPMI_CC_UNSPECIFIED_ERROR;
110         }
111         ledProp->setPrevState(std::get<std::string>(presentState));
112         ledProp->setLock(true);
113         if (signal == SmSignalSet::smPowerFaultLed ||
114             signal == SmSignalSet::smSystemReadyLed)
115         {
116             mtm.revertLedCallback = true;
117         }
118     }
119     if (mtm.setProperty(ledService, ledPath, ledIntf, "State",
120                         ledStateStr + setState) != 0)
121     {
122         return IPMI_CC_UNSPECIFIED_ERROR;
123     }
124     return IPMI_CC_OK;
125 }
126 
127 ipmi_ret_t ledRevert(SmSignalSet signal)
128 {
129     LedProperty* ledProp = mtm.findLedProperty(signal);
130     if (ledProp == nullptr)
131     {
132         return IPMI_CC_INVALID_FIELD_REQUEST;
133     }
134     if (true == ledProp->getLock())
135     {
136         ledProp->setLock(false);
137         if (signal == SmSignalSet::smPowerFaultLed ||
138             signal == SmSignalSet::smSystemReadyLed)
139         {
140             try
141             {
142                 ipmi::method_no_args::callDbusMethod(
143                     *getSdBus(), callbackMgrService, callbackMgrObjPath,
144                     callbackMgrIntf, retriggerLedUpdate);
145             }
146             catch (sdbusplus::exception_t& e)
147             {
148                 return IPMI_CC_UNSPECIFIED_ERROR;
149             }
150             mtm.revertLedCallback = false;
151         }
152         else
153         {
154             std::string ledName = ledProp->getName();
155             std::string ledService = ledServicePrefix + ledName;
156             std::string ledPath = ledPathPrefix + ledName;
157             if (mtm.setProperty(ledService, ledPath, ledIntf, "State",
158                                 ledProp->getPrevState()) != 0)
159             {
160                 return IPMI_CC_UNSPECIFIED_ERROR;
161             }
162         }
163     }
164     return IPMI_CC_OK;
165 }
166 
167 void Manufacturing::initData()
168 {
169     ledPropertyList.push_back(
170         LedProperty(SmSignalSet::smPowerFaultLed, "status_amber"));
171     ledPropertyList.push_back(
172         LedProperty(SmSignalSet::smSystemReadyLed, "status_green"));
173     ledPropertyList.push_back(
174         LedProperty(SmSignalSet::smIdentifyLed, "identify"));
175 }
176 
177 void Manufacturing::revertTimerHandler()
178 {
179 
180 #ifdef BMC_VALIDATION_UNSECURE_FEATURE
181     if (mtm.getMfgMode() == SpecialMode::valUnsecure)
182     {
183         // Don't revert the behaviour for validation unsecure mode.
184         return;
185     }
186 #endif
187     if (revertFanPWM)
188     {
189         revertFanPWM = false;
190         disablePidControlService(false);
191     }
192 
193     if (mtmTestBeepFd != -1)
194     {
195         ::close(mtmTestBeepFd);
196         mtmTestBeepFd = -1;
197     }
198 
199     for (const auto& ledProperty : ledPropertyList)
200     {
201         const std::string& ledName = ledProperty.getName();
202         ledRevert(ledProperty.getSignal());
203     }
204 }
205 
206 Manufacturing::Manufacturing() :
207     revertTimer([&](void) { revertTimerHandler(); })
208 {
209     initData();
210 }
211 
212 int8_t Manufacturing::getProperty(const std::string& service,
213                                   const std::string& path,
214                                   const std::string& interface,
215                                   const std::string& propertyName,
216                                   ipmi::Value* reply)
217 {
218     try
219     {
220         *reply = ipmi::getDbusProperty(*getSdBus(), service, path, interface,
221                                        propertyName);
222     }
223     catch (const sdbusplus::exception::SdBusError& e)
224     {
225         phosphor::logging::log<phosphor::logging::level::INFO>(
226             "ERROR: getProperty");
227         return -1;
228     }
229 
230     return 0;
231 }
232 
233 int8_t Manufacturing::setProperty(const std::string& service,
234                                   const std::string& path,
235                                   const std::string& interface,
236                                   const std::string& propertyName,
237                                   ipmi::Value value)
238 {
239     try
240     {
241         ipmi::setDbusProperty(*getSdBus(), service, path, interface,
242                               propertyName, value);
243     }
244     catch (const sdbusplus::exception::SdBusError& e)
245     {
246         phosphor::logging::log<phosphor::logging::level::INFO>(
247             "ERROR: setProperty");
248         return -1;
249     }
250 
251     return 0;
252 }
253 
254 int8_t Manufacturing::disablePidControlService(const bool disable)
255 {
256     try
257     {
258         auto dbus = getSdBus();
259         auto method = dbus->new_method_call(systemDService, systemDObjPath,
260                                             systemDMgrIntf,
261                                             disable ? "StopUnit" : "StartUnit");
262         method.append(pidControlService, "replace");
263         auto reply = dbus->call(method);
264     }
265     catch (const sdbusplus::exception::SdBusError& e)
266     {
267         phosphor::logging::log<phosphor::logging::level::INFO>(
268             "ERROR: phosphor-pid-control service start or stop failed");
269         return -1;
270     }
271     return 0;
272 }
273 
274 ipmi::RspType<uint8_t,                // Signal value
275               std::optional<uint16_t> // Fan tach value
276               >
277     appMTMGetSignal(ipmi::Context::ptr ctx, uint8_t signalTypeByte,
278                     uint8_t instance, uint8_t actionByte)
279 {
280     // mfg filter logic is used to allow MTM get signal command only in
281     // manfacturing mode.
282 
283     SmSignalGet signalType = static_cast<SmSignalGet>(signalTypeByte);
284     SmActionGet action = static_cast<SmActionGet>(actionByte);
285 
286     switch (signalType)
287     {
288         case SmSignalGet::smChassisIntrusion:
289         {
290             ipmi::Value reply;
291             if (mtm.getProperty(intrusionService, intrusionPath, intrusionIntf,
292                                 "Status", &reply) < 0)
293             {
294                 return ipmi::responseInvalidFieldRequest();
295             }
296             std::string* intrusionStatus = std::get_if<std::string>(&reply);
297             if (!intrusionStatus)
298             {
299                 return ipmi::responseUnspecifiedError();
300             }
301 
302             uint8_t status = 0;
303             if (!intrusionStatus->compare("Normal"))
304             {
305                 status = static_cast<uint8_t>(IntrusionStatus::normal);
306             }
307             else if (!intrusionStatus->compare("HardwareIntrusion"))
308             {
309                 status =
310                     static_cast<uint8_t>(IntrusionStatus::hardwareIntrusion);
311             }
312             else if (!intrusionStatus->compare("TamperingDetected"))
313             {
314                 status =
315                     static_cast<uint8_t>(IntrusionStatus::tamperingDetected);
316             }
317             else
318             {
319                 return ipmi::responseUnspecifiedError();
320             }
321             return ipmi::responseSuccess(status, std::nullopt);
322         }
323         case SmSignalGet::smFanPwmGet:
324         {
325             ipmi::Value reply;
326             std::string fullPath = fanPwmPath + std::to_string(instance + 1);
327             if (mtm.getProperty(fanService, fullPath, fanIntf, "Value",
328                                 &reply) < 0)
329             {
330                 return ipmi::responseInvalidFieldRequest();
331             }
332             double* doubleVal = std::get_if<double>(&reply);
333             if (doubleVal == nullptr)
334             {
335                 return ipmi::responseUnspecifiedError();
336             }
337             uint8_t sensorVal = std::round(*doubleVal);
338             resetMtmTimer(ctx);
339             return ipmi::responseSuccess(sensorVal, std::nullopt);
340         }
341         break;
342         case SmSignalGet::smFanTachometerGet:
343         {
344             boost::system::error_code ec;
345             using objFlatMap = boost::container::flat_map<
346                 std::string, boost::container::flat_map<
347                                  std::string, std::vector<std::string>>>;
348 
349             auto flatMap = ctx->bus->yield_method_call<objFlatMap>(
350                 ctx->yield, ec, "xyz.openbmc_project.ObjectMapper",
351                 "/xyz/openbmc_project/object_mapper",
352                 "xyz.openbmc_project.ObjectMapper", "GetSubTree",
353                 fanTachBasePath, 0, std::array<const char*, 1>{fanIntf});
354             if (ec)
355             {
356                 phosphor::logging::log<phosphor::logging::level::ERR>(
357                     "Failed to query fan tach sub tree objects");
358                 return ipmi::responseUnspecifiedError();
359             }
360             if (instance >= flatMap.size())
361             {
362                 return ipmi::responseInvalidFieldRequest();
363             }
364             auto itr = flatMap.nth(instance);
365             ipmi::Value reply;
366             if (mtm.getProperty(fanService, itr->first, fanIntf, "Value",
367                                 &reply) < 0)
368             {
369                 return ipmi::responseInvalidFieldRequest();
370             }
371 
372             double* doubleVal = std::get_if<double>(&reply);
373             if (doubleVal == nullptr)
374             {
375                 return ipmi::responseUnspecifiedError();
376             }
377             uint8_t sensorVal = FAN_PRESENT | FAN_SENSOR_PRESENT;
378             std::optional<uint16_t> fanTach = std::round(*doubleVal);
379 
380             resetMtmTimer(ctx);
381             return ipmi::responseSuccess(sensorVal, fanTach);
382         }
383         break;
384         case SmSignalGet::smIdentifyButton:
385         {
386             if (action == SmActionGet::revert || action == SmActionGet::ignore)
387             {
388                 // ButtonMasked property is not supported for ID button as it is
389                 // unnecessary. Hence if requested for revert / ignore, override
390                 // it to sample action to make tools happy.
391                 action = SmActionGet::sample;
392             }
393             // fall-through
394         }
395         case SmSignalGet::smResetButton:
396         case SmSignalGet::smPowerButton:
397         case SmSignalGet::smNMIButton:
398         {
399             std::string path;
400             if (getGpioPathForSmSignal(signalType, path) < 0)
401             {
402                 return ipmi::responseInvalidFieldRequest();
403             }
404 
405             switch (action)
406             {
407                 case SmActionGet::sample:
408                     phosphor::logging::log<phosphor::logging::level::INFO>(
409                         "case SmActionGet::sample");
410                     break;
411                 case SmActionGet::ignore:
412                 {
413                     phosphor::logging::log<phosphor::logging::level::INFO>(
414                         "case SmActionGet::ignore");
415                     if (mtm.setProperty(buttonService, path, buttonIntf,
416                                         "ButtonMasked", true) < 0)
417                     {
418                         return ipmi::responseUnspecifiedError();
419                     }
420                 }
421                 break;
422                 case SmActionGet::revert:
423                 {
424                     phosphor::logging::log<phosphor::logging::level::INFO>(
425                         "case SmActionGet::revert");
426                     if (mtm.setProperty(buttonService, path, buttonIntf,
427                                         "ButtonMasked", false) < 0)
428                     {
429                         return ipmi::responseUnspecifiedError();
430                     }
431                 }
432                 break;
433 
434                 default:
435                     return ipmi::responseInvalidFieldRequest();
436                     break;
437             }
438 
439             ipmi::Value reply;
440             if (mtm.getProperty(buttonService, path, buttonIntf,
441                                 "ButtonPressed", &reply) < 0)
442             {
443                 return ipmi::responseUnspecifiedError();
444             }
445             bool* valPtr = std::get_if<bool>(&reply);
446             if (valPtr == nullptr)
447             {
448                 return ipmi::responseUnspecifiedError();
449             }
450             resetMtmTimer(ctx);
451             uint8_t sensorVal = *valPtr;
452             return ipmi::responseSuccess(sensorVal, std::nullopt);
453         }
454         break;
455         case SmSignalGet::smNcsiDiag:
456         {
457             constexpr const char* netBasePath = "/sys/class/net/eth";
458             constexpr const char* carrierSuffix = "/carrier";
459             std::ifstream netIfs(netBasePath + std::to_string(instance) +
460                                  carrierSuffix);
461             if (!netIfs.good())
462             {
463                 return ipmi::responseInvalidFieldRequest();
464             }
465             std::string carrier;
466             netIfs >> carrier;
467             resetMtmTimer(ctx);
468             return ipmi::responseSuccess(
469                 static_cast<uint8_t>(std::stoi(carrier)), std::nullopt);
470         }
471         break;
472         default:
473             return ipmi::responseInvalidFieldRequest();
474             break;
475     }
476 }
477 
478 ipmi::RspType<> appMTMSetSignal(ipmi::Context::ptr ctx, uint8_t signalTypeByte,
479                                 uint8_t instance, uint8_t actionByte,
480                                 std::optional<uint8_t> pwmSpeed)
481 {
482     // mfg filter logic is used to allow MTM set signal command only in
483     // manfacturing mode.
484 
485     SmSignalSet signalType = static_cast<SmSignalSet>(signalTypeByte);
486     SmActionSet action = static_cast<SmActionSet>(actionByte);
487     Cc retCode = ccSuccess;
488     int8_t ret = 0;
489 
490     switch (signalType)
491     {
492         case SmSignalSet::smPowerFaultLed:
493         case SmSignalSet::smSystemReadyLed:
494         case SmSignalSet::smIdentifyLed:
495             switch (action)
496             {
497                 case SmActionSet::forceDeasserted:
498                 {
499                     phosphor::logging::log<phosphor::logging::level::INFO>(
500                         "case SmActionSet::forceDeasserted");
501 
502                     retCode = ledStoreAndSet(signalType, std::string("Off"));
503                     if (retCode != ccSuccess)
504                     {
505                         return ipmi::response(retCode);
506                     }
507                     mtm.revertTimer.start(revertTimeOut);
508                 }
509                 break;
510                 case SmActionSet::forceAsserted:
511                 {
512                     phosphor::logging::log<phosphor::logging::level::INFO>(
513                         "case SmActionSet::forceAsserted");
514 
515                     retCode = ledStoreAndSet(signalType, std::string("On"));
516                     if (retCode != ccSuccess)
517                     {
518                         return ipmi::response(retCode);
519                     }
520                     mtm.revertTimer.start(revertTimeOut);
521                     if (SmSignalSet::smPowerFaultLed == signalType)
522                     {
523                         // Deassert "system ready"
524                         retCode = ledStoreAndSet(SmSignalSet::smSystemReadyLed,
525                                                  std::string("Off"));
526                     }
527                     else if (SmSignalSet::smSystemReadyLed == signalType)
528                     {
529                         // Deassert "fault led"
530                         retCode = ledStoreAndSet(SmSignalSet::smPowerFaultLed,
531                                                  std::string("Off"));
532                     }
533                 }
534                 break;
535                 case SmActionSet::revert:
536                 {
537                     phosphor::logging::log<phosphor::logging::level::INFO>(
538                         "case SmActionSet::revert");
539                     retCode = ledRevert(signalType);
540                 }
541                 break;
542                 default:
543                 {
544                     return ipmi::responseInvalidFieldRequest();
545                 }
546             }
547             break;
548         case SmSignalSet::smFanPowerSpeed:
549         {
550             if ((action == SmActionSet::forceAsserted) && (!pwmSpeed))
551             {
552                 return ipmi::responseReqDataLenInvalid();
553             }
554 
555             if ((action == SmActionSet::forceAsserted) && (*pwmSpeed > 100))
556             {
557                 return ipmi::responseInvalidFieldRequest();
558             }
559 
560             uint8_t pwmValue = 0;
561             switch (action)
562             {
563                 case SmActionSet::revert:
564                 {
565                     if (mtm.revertFanPWM)
566                     {
567                         ret = mtm.disablePidControlService(false);
568                         if (ret < 0)
569                         {
570                             return ipmi::responseUnspecifiedError();
571                         }
572                         mtm.revertFanPWM = false;
573                     }
574                 }
575                 break;
576                 case SmActionSet::forceAsserted:
577                 {
578                     pwmValue = *pwmSpeed;
579                 } // fall-through
580                 case SmActionSet::forceDeasserted:
581                 {
582                     if (!mtm.revertFanPWM)
583                     {
584                         ret = mtm.disablePidControlService(true);
585                         if (ret < 0)
586                         {
587                             return ipmi::responseUnspecifiedError();
588                         }
589                         mtm.revertFanPWM = true;
590                     }
591                     mtm.revertTimer.start(revertTimeOut);
592                     std::string fanPwmInstancePath =
593                         fanPwmPath + std::to_string(instance + 1);
594 
595                     ret =
596                         mtm.setProperty(fanService, fanPwmInstancePath, fanIntf,
597                                         "Value", static_cast<double>(pwmValue));
598                     if (ret < 0)
599                     {
600                         return ipmi::responseUnspecifiedError();
601                     }
602                 }
603                 break;
604                 default:
605                 {
606                     return ipmi::responseInvalidFieldRequest();
607                 }
608             }
609         }
610         break;
611         case SmSignalSet::smSpeaker:
612         {
613             phosphor::logging::log<phosphor::logging::level::INFO>(
614                 "Performing Speaker SmActionSet",
615                 phosphor::logging::entry("ACTION=%d",
616                                          static_cast<uint8_t>(action)));
617             switch (action)
618             {
619                 case SmActionSet::forceAsserted:
620                 {
621                     char beepDevName[] = "/dev/input/event0";
622                     if (mtm.mtmTestBeepFd != -1)
623                     {
624                         phosphor::logging::log<phosphor::logging::level::INFO>(
625                             "mtm beep device is opened already!");
626                         // returning success as already beep is in progress
627                         return ipmi::response(retCode);
628                     }
629 
630                     if ((mtm.mtmTestBeepFd =
631                              ::open(beepDevName, O_RDWR | O_CLOEXEC)) < 0)
632                     {
633                         phosphor::logging::log<phosphor::logging::level::ERR>(
634                             "Failed to open input device");
635                         return ipmi::responseUnspecifiedError();
636                     }
637 
638                     struct input_event event;
639                     event.type = EV_SND;
640                     event.code = SND_TONE;
641                     event.value = 2000;
642 
643                     if (::write(mtm.mtmTestBeepFd, &event,
644                                 sizeof(struct input_event)) !=
645                         sizeof(struct input_event))
646                     {
647                         phosphor::logging::log<phosphor::logging::level::ERR>(
648                             "Failed to write a tone sound event");
649                         ::close(mtm.mtmTestBeepFd);
650                         mtm.mtmTestBeepFd = -1;
651                         return ipmi::responseUnspecifiedError();
652                     }
653                     mtm.revertTimer.start(revertTimeOut);
654                 }
655                 break;
656                 case SmActionSet::revert:
657                 case SmActionSet::forceDeasserted:
658                 {
659                     if (mtm.mtmTestBeepFd != -1)
660                     {
661                         ::close(mtm.mtmTestBeepFd);
662                         mtm.mtmTestBeepFd = -1;
663                     }
664                 }
665                 break;
666                 default:
667                 {
668                     return ipmi::responseInvalidFieldRequest();
669                 }
670             }
671         }
672         break;
673         case SmSignalSet::smDiskFaultLed:
674         {
675             boost::system::error_code ec;
676             using objPaths = std::vector<std::string>;
677             std::string driveBasePath =
678                 "/xyz/openbmc_project/inventory/item/drive/";
679             static constexpr const char* driveLedIntf =
680                 "xyz.openbmc_project.Led.Group";
681             static constexpr const char* hsbpService =
682                 "xyz.openbmc_project.HsbpManager";
683 
684             auto driveList = ctx->bus->yield_method_call<objPaths>(
685                 ctx->yield, ec, "xyz.openbmc_project.ObjectMapper",
686                 "/xyz/openbmc_project/object_mapper",
687                 "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths",
688                 driveBasePath, 0, std::array<const char*, 1>{driveLedIntf});
689             if (ec)
690             {
691                 phosphor::logging::log<phosphor::logging::level::ERR>(
692                     "Failed to query HSBP drive sub tree objects");
693                 return ipmi::responseUnspecifiedError();
694             }
695             std::string driveObjPath =
696                 driveBasePath + "Drive_" + std::to_string(instance + 1);
697             if (std::find(driveList.begin(), driveList.end(), driveObjPath) ==
698                 driveList.end())
699             {
700                 return ipmi::responseInvalidFieldRequest();
701             }
702             bool driveLedState = false;
703             switch (action)
704             {
705                 case SmActionSet::forceAsserted:
706                 {
707                     driveLedState = true;
708                 }
709                 break;
710                 case SmActionSet::revert:
711                 {
712                     driveLedState = false;
713                 }
714                 break;
715                 case SmActionSet::forceDeasserted:
716                 {
717                     driveLedState = false;
718                 }
719                 break;
720                 default:
721                 {
722                     return ipmi::responseInvalidFieldRequest();
723                 }
724             }
725             ret = mtm.setProperty(hsbpService, driveObjPath, driveLedIntf,
726                                   "Asserted", driveLedState);
727             if (ret < 0)
728             {
729                 return ipmi::responseUnspecifiedError();
730             }
731         }
732         break;
733         default:
734         {
735             return ipmi::responseInvalidFieldRequest();
736         }
737     }
738     if (retCode == ccSuccess)
739     {
740         resetMtmTimer(ctx);
741     }
742     return ipmi::response(retCode);
743 }
744 
745 ipmi::RspType<> mtmKeepAlive(ipmi::Context::ptr ctx, uint8_t reserved,
746                              const std::array<char, 5>& intentionalSignature)
747 {
748     // mfg filter logic is used to allow MTM keep alive command only in
749     // manfacturing mode
750 
751     constexpr std::array<char, 5> signatureOk = {'I', 'N', 'T', 'E', 'L'};
752     if (intentionalSignature != signatureOk || reserved != 0)
753     {
754         return ipmi::responseInvalidFieldRequest();
755     }
756     return ipmi::response(resetMtmTimer(ctx));
757 }
758 
759 static constexpr unsigned int makeCmdKey(unsigned int netFn, unsigned int cmd)
760 {
761     return (netFn << 8) | cmd;
762 }
763 
764 ipmi::Cc mfgFilterMessage(ipmi::message::Request::ptr request)
765 {
766     // Restricted commands, must be executed only in Manufacturing mode
767     switch (makeCmdKey(request->ctx->netFn, request->ctx->cmd))
768     {
769         // i2c master write read command needs additional checking
770         case makeCmdKey(ipmi::netFnApp, ipmi::app::cmdMasterWriteRead):
771             if (request->payload.size() > 4)
772             {
773                 // Allow write data count > 1 only in Special mode
774                 if (mtm.getMfgMode() == SpecialMode::none)
775                 {
776                     return ipmi::ccInsufficientPrivilege;
777                 }
778             }
779             return ipmi::ccSuccess;
780         case makeCmdKey(ipmi::netFnOemOne,
781                         ipmi::intel::general::cmdGetSmSignal):
782         case makeCmdKey(ipmi::netFnOemOne,
783                         ipmi::intel::general::cmdSetSmSignal):
784         case makeCmdKey(ipmi::netFnOemOne,
785                         ipmi::intel::general::cmdMtmKeepAlive):
786         case makeCmdKey(ipmi::netFnOemOne,
787                         ipmi::intel::general::cmdSetManufacturingData):
788         case makeCmdKey(ipmi::netFnOemOne,
789                         ipmi::intel::general::cmdGetManufacturingData):
790         case makeCmdKey(ipmi::netFnStorage, ipmi::storage::cmdWriteFruData):
791 
792             // Check for Special mode
793             if (mtm.getMfgMode() == SpecialMode::none)
794             {
795                 return ipmi::ccInvalidCommand;
796             }
797             return ipmi::ccSuccess;
798         case makeCmdKey(ipmi::netFnStorage, ipmi::storage::cmdDeleteSelEntry):
799         {
800             return ipmi::ccInvalidCommand;
801         }
802     }
803     return ipmi::ccSuccess;
804 }
805 
806 static constexpr uint8_t maxEthSize = 6;
807 static constexpr uint8_t maxSupportedEth = 3;
808 static constexpr const char* factoryEthAddrBaseFileName =
809     "/var/sofs/factory-settings/network/mac/eth";
810 
811 ipmi::RspType<> setManufacturingData(ipmi::Context::ptr ctx, uint8_t dataType,
812                                      std::array<uint8_t, maxEthSize> ethData)
813 {
814     // mfg filter logic will restrict this command executing only in mfg mode.
815     if (dataType >= maxSupportedEth)
816     {
817         return ipmi::responseParmOutOfRange();
818     }
819 
820     constexpr uint8_t invalidData = 0;
821     constexpr uint8_t validData = 1;
822     constexpr uint8_t ethAddrStrSize =
823         19; // XX:XX:XX:XX:XX:XX + \n + null termination;
824     std::vector<uint8_t> buff(ethAddrStrSize);
825     std::snprintf(reinterpret_cast<char*>(buff.data()), ethAddrStrSize,
826                   "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx\n", ethData.at(0),
827                   ethData.at(1), ethData.at(2), ethData.at(3), ethData.at(4),
828                   ethData.at(5));
829     std::ofstream oEthFile(factoryEthAddrBaseFileName +
830                                std::to_string(dataType),
831                            std::ofstream::out);
832     if (!oEthFile.good())
833     {
834         return ipmi::responseUnspecifiedError();
835     }
836 
837     oEthFile << reinterpret_cast<char*>(buff.data());
838     oEthFile << fflush;
839     oEthFile.close();
840 
841     resetMtmTimer(ctx);
842     return ipmi::responseSuccess();
843 }
844 
845 ipmi::RspType<uint8_t, std::array<uint8_t, maxEthSize>>
846     getManufacturingData(ipmi::Context::ptr ctx, uint8_t dataType)
847 {
848     // mfg filter logic will restrict this command executing only in mfg mode.
849     if (dataType >= maxSupportedEth)
850     {
851         return ipmi::responseParmOutOfRange();
852     }
853     std::array<uint8_t, maxEthSize> ethData{0};
854     constexpr uint8_t invalidData = 0;
855     constexpr uint8_t validData = 1;
856 
857     std::ifstream iEthFile(factoryEthAddrBaseFileName +
858                                std::to_string(dataType),
859                            std::ifstream::in);
860     if (!iEthFile.good())
861     {
862         return ipmi::responseSuccess(invalidData, ethData);
863     }
864     std::string ethStr;
865     iEthFile >> ethStr;
866     uint8_t* data = ethData.data();
867     std::sscanf(ethStr.c_str(), "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx",
868                 data, (data + 1), (data + 2), (data + 3), (data + 4),
869                 (data + 5));
870 
871     resetMtmTimer(ctx);
872     return ipmi::responseSuccess(validData, ethData);
873 }
874 
875 /** @brief implements slot master write read IPMI command which can be used for
876  * low-level I2C/SMBus write, read or write-read access for PCIE slots
877  * @param reserved - skip 6 bit
878  * @param addressType - address type
879  * @param bbSlotNum - baseboard slot number
880  * @param riserSlotNum - riser slot number
881  * @param reserved2 - skip 2 bit
882  * @param slaveAddr - slave address
883  * @param readCount - number of bytes to be read
884  * @param writeData - data to be written
885  *
886  * @returns IPMI completion code plus response data
887  */
888 ipmi::RspType<std::vector<uint8_t>>
889     appSlotI2CMasterWriteRead(uint6_t reserved, uint2_t addressType,
890                               uint3_t bbSlotNum, uint3_t riserSlotNum,
891                               uint2_t resvered2, uint8_t slaveAddr,
892                               uint8_t readCount, std::vector<uint8_t> writeData)
893 {
894     const size_t writeCount = writeData.size();
895     std::string i2cBus;
896     if (addressType == slotAddressTypeBus)
897     {
898         std::string path = "/dev/i2c-mux/Riser_" +
899                            std::to_string(static_cast<uint8_t>(bbSlotNum)) +
900                            "_Mux/Pcie_Slot_" +
901                            std::to_string(static_cast<uint8_t>(riserSlotNum));
902 
903         if (std::filesystem::exists(path) && std::filesystem::is_symlink(path))
904         {
905             i2cBus = std::filesystem::read_symlink(path);
906         }
907         else
908         {
909             phosphor::logging::log<phosphor::logging::level::ERR>(
910                 "Master write read command: Cannot get BusID");
911             return ipmi::responseInvalidFieldRequest();
912         }
913     }
914     else if (addressType == slotAddressTypeUniqueid)
915     {
916         i2cBus = "/dev/i2c-" +
917                  std::to_string(static_cast<uint8_t>(bbSlotNum) |
918                                 (static_cast<uint8_t>(riserSlotNum) << 3));
919     }
920     else
921     {
922         phosphor::logging::log<phosphor::logging::level::ERR>(
923             "Master write read command: invalid request");
924         return ipmi::responseInvalidFieldRequest();
925     }
926 
927     // Allow single byte write as it is offset byte to read the data, rest allow
928     // only in Special mode.
929     if (writeCount > 1)
930     {
931         if (mtm.getMfgMode() == SpecialMode::none)
932         {
933             return ipmi::responseInsufficientPrivilege();
934         }
935     }
936 
937     if (readCount > slotI2CMaxReadSize)
938     {
939         phosphor::logging::log<phosphor::logging::level::ERR>(
940             "Master write read command: Read count exceeds limit");
941         return ipmi::responseParmOutOfRange();
942     }
943 
944     if (!readCount && !writeCount)
945     {
946         phosphor::logging::log<phosphor::logging::level::ERR>(
947             "Master write read command: Read & write count are 0");
948         return ipmi::responseInvalidFieldRequest();
949     }
950 
951     std::vector<uint8_t> readBuf(readCount);
952 
953     ipmi::Cc retI2C = ipmi::i2cWriteRead(i2cBus, slaveAddr, writeData, readBuf);
954     if (retI2C != ipmi::ccSuccess)
955     {
956         return ipmi::response(retI2C);
957     }
958 
959     return ipmi::responseSuccess(readBuf);
960 }
961 
962 ipmi::RspType<> clearCMOS()
963 {
964     // There is an i2c device on bus 4, the slave address is 0x70. Based on the
965     // spec, writing 0x1 to address 0x60 on this device, will trigger the clear
966     // CMOS action.
967     constexpr uint8_t slaveAddr = 0x70;
968     std::string i2cBus = "/dev/i2c-4";
969     std::vector<uint8_t> writeData = {0x60, 0x1};
970     std::vector<uint8_t> readBuf(0);
971 
972     if (mtm.getMfgMode() == SpecialMode::none)
973     {
974         return ipmi::responseInsufficientPrivilege();
975     }
976 
977     ipmi::Cc retI2C = ipmi::i2cWriteRead(i2cBus, slaveAddr, writeData, readBuf);
978     return ipmi::response(retI2C);
979 }
980 } // namespace ipmi
981 
982 void register_mtm_commands() __attribute__((constructor));
983 void register_mtm_commands()
984 {
985     // <Get SM Signal>
986     ipmi::registerHandler(ipmi::prioOemBase, ipmi::intel::netFnGeneral,
987                           ipmi::intel::general::cmdGetSmSignal,
988                           ipmi::Privilege::Admin, ipmi::appMTMGetSignal);
989 
990     ipmi::registerHandler(ipmi::prioOemBase, ipmi::intel::netFnGeneral,
991                           ipmi::intel::general::cmdSetSmSignal,
992                           ipmi::Privilege::Admin, ipmi::appMTMSetSignal);
993 
994     ipmi::registerHandler(ipmi::prioOemBase, ipmi::intel::netFnGeneral,
995                           ipmi::intel::general::cmdMtmKeepAlive,
996                           ipmi::Privilege::Admin, ipmi::mtmKeepAlive);
997 
998     ipmi::registerHandler(ipmi::prioOemBase, ipmi::intel::netFnGeneral,
999                           ipmi::intel::general::cmdSetManufacturingData,
1000                           ipmi::Privilege::Admin, ipmi::setManufacturingData);
1001 
1002     ipmi::registerHandler(ipmi::prioOemBase, ipmi::intel::netFnGeneral,
1003                           ipmi::intel::general::cmdGetManufacturingData,
1004                           ipmi::Privilege::Admin, ipmi::getManufacturingData);
1005 
1006     ipmi::registerHandler(ipmi::prioOemBase, ipmi::intel::netFnApp,
1007                           ipmi::intel::general::cmdSlotI2CMasterWriteRead,
1008                           ipmi::Privilege::Admin,
1009                           ipmi::appSlotI2CMasterWriteRead);
1010 
1011     ipmi::registerHandler(ipmi::prioOemBase, ipmi::intel::netFnPlatform,
1012                           ipmi::intel::platform::cmdClearCMOS,
1013                           ipmi::Privilege::Admin, ipmi::clearCMOS);
1014 
1015     ipmi::registerFilter(ipmi::prioOemBase,
1016                          [](ipmi::message::Request::ptr request) {
1017                              return ipmi::mfgFilterMessage(request);
1018                          });
1019 }
1020