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