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 <boost/container/flat_map.hpp>
18 #include <fstream>
19 #include <ipmid/api.hpp>
20 #include <manufacturingcommands.hpp>
21 #include <oemcommands.hpp>
22 
23 namespace ipmi
24 {
25 
26 Manufacturing mtm;
27 
28 static auto revertTimeOut =
29     std::chrono::duration_cast<std::chrono::microseconds>(
30         std::chrono::seconds(60)); // 1 minute timeout
31 
32 static constexpr const char* callbackMgrService =
33     "xyz.openbmc_project.CallbackManager";
34 static constexpr const char* callbackMgrIntf =
35     "xyz.openbmc_project.CallbackManager";
36 static constexpr const char* callbackMgrObjPath =
37     "/xyz/openbmc_project/CallbackManager";
38 static constexpr const char* retriggerLedUpdate = "RetriggerLEDUpdate";
39 
40 const static constexpr char* systemDService = "org.freedesktop.systemd1";
41 const static constexpr char* systemDObjPath = "/org/freedesktop/systemd1";
42 const static constexpr char* systemDMgrIntf =
43     "org.freedesktop.systemd1.Manager";
44 const static constexpr char* pidControlService = "phosphor-pid-control.service";
45 
46 static inline Cc resetMtmTimer(boost::asio::yield_context yield)
47 {
48     auto sdbusp = getSdBus();
49     boost::system::error_code ec;
50     sdbusp->yield_method_call<>(yield, ec, specialModeService,
51                                 specialModeObjPath, specialModeIntf,
52                                 "ResetTimer");
53     if (ec)
54     {
55         phosphor::logging::log<phosphor::logging::level::ERR>(
56             "Failed to reset the manufacturing mode timer");
57         return ccUnspecifiedError;
58     }
59     return ccSuccess;
60 }
61 
62 int getGpioPathForSmSignal(const SmSignalGet signal, std::string& path)
63 {
64     switch (signal)
65     {
66         case SmSignalGet::smPowerButton:
67             path = "/xyz/openbmc_project/chassis/buttons/power";
68             break;
69         case SmSignalGet::smResetButton:
70             path = "/xyz/openbmc_project/chassis/buttons/reset";
71             break;
72         case SmSignalGet::smNMIButton:
73             path = "/xyz/openbmc_project/chassis/buttons/nmi";
74             break;
75         case SmSignalGet::smIdentifyButton:
76             path = "/xyz/openbmc_project/chassis/buttons/id";
77             break;
78         default:
79             return -1;
80             break;
81     }
82     return 0;
83 }
84 
85 ipmi_ret_t ledStoreAndSet(SmSignalSet signal, std::string setState)
86 {
87     LedProperty* ledProp = mtm.findLedProperty(signal);
88     if (ledProp == nullptr)
89     {
90         return IPMI_CC_INVALID_FIELD_REQUEST;
91     }
92 
93     std::string ledName = ledProp->getName();
94     std::string ledService = ledServicePrefix + ledName;
95     std::string ledPath = ledPathPrefix + ledName;
96     ipmi::Value presentState;
97 
98     if (false == ledProp->getLock())
99     {
100         if (mtm.getProperty(ledService.c_str(), ledPath.c_str(), ledIntf,
101                             "State", &presentState) != 0)
102         {
103             return IPMI_CC_UNSPECIFIED_ERROR;
104         }
105         ledProp->setPrevState(std::get<std::string>(presentState));
106         ledProp->setLock(true);
107         if (signal == SmSignalSet::smPowerFaultLed ||
108             signal == SmSignalSet::smSystemReadyLed)
109         {
110             mtm.revertLedCallback = true;
111         }
112     }
113     if (mtm.setProperty(ledService, ledPath, ledIntf, "State",
114                         ledStateStr + setState) != 0)
115     {
116         return IPMI_CC_UNSPECIFIED_ERROR;
117     }
118     return IPMI_CC_OK;
119 }
120 
121 ipmi_ret_t ledRevert(SmSignalSet signal)
122 {
123     LedProperty* ledProp = mtm.findLedProperty(signal);
124     if (ledProp == nullptr)
125     {
126         return IPMI_CC_INVALID_FIELD_REQUEST;
127     }
128     if (true == ledProp->getLock())
129     {
130         ledProp->setLock(false);
131         if (signal == SmSignalSet::smPowerFaultLed ||
132             signal == SmSignalSet::smSystemReadyLed)
133         {
134             try
135             {
136                 ipmi::method_no_args::callDbusMethod(
137                     *getSdBus(), callbackMgrService, callbackMgrObjPath,
138                     callbackMgrIntf, retriggerLedUpdate);
139             }
140             catch (sdbusplus::exception_t& e)
141             {
142                 return IPMI_CC_UNSPECIFIED_ERROR;
143             }
144             mtm.revertLedCallback = false;
145         }
146         else
147         {
148             std::string ledName = ledProp->getName();
149             std::string ledService = ledServicePrefix + ledName;
150             std::string ledPath = ledPathPrefix + ledName;
151             if (mtm.setProperty(ledService, ledPath, ledIntf, "State",
152                                 ledProp->getPrevState()) != 0)
153             {
154                 return IPMI_CC_UNSPECIFIED_ERROR;
155             }
156         }
157     }
158     return IPMI_CC_OK;
159 }
160 
161 void Manufacturing::initData()
162 {
163     ledPropertyList.push_back(
164         LedProperty(SmSignalSet::smPowerFaultLed, "status_amber"));
165     ledPropertyList.push_back(
166         LedProperty(SmSignalSet::smSystemReadyLed, "status_green"));
167     ledPropertyList.push_back(
168         LedProperty(SmSignalSet::smIdentifyLed, "identify"));
169 }
170 
171 void Manufacturing::revertTimerHandler()
172 {
173     if (revertFanPWM)
174     {
175         revertFanPWM = false;
176         disablePidControlService(false);
177     }
178 
179     for (const auto& ledProperty : ledPropertyList)
180     {
181         const std::string& ledName = ledProperty.getName();
182         ledRevert(ledProperty.getSignal());
183     }
184 }
185 
186 Manufacturing::Manufacturing() :
187     revertTimer([&](void) { revertTimerHandler(); })
188 {
189     initData();
190 }
191 
192 int8_t Manufacturing::getProperty(const std::string& service,
193                                   const std::string& path,
194                                   const std::string& interface,
195                                   const std::string& propertyName,
196                                   ipmi::Value* reply)
197 {
198     try
199     {
200         *reply = ipmi::getDbusProperty(*getSdBus(), service, path, interface,
201                                        propertyName);
202     }
203     catch (const sdbusplus::exception::SdBusError& e)
204     {
205         phosphor::logging::log<phosphor::logging::level::INFO>(
206             "ERROR: getProperty");
207         return -1;
208     }
209 
210     return 0;
211 }
212 
213 int8_t Manufacturing::setProperty(const std::string& service,
214                                   const std::string& path,
215                                   const std::string& interface,
216                                   const std::string& propertyName,
217                                   ipmi::Value value)
218 {
219     try
220     {
221         ipmi::setDbusProperty(*getSdBus(), service, path, interface,
222                               propertyName, value);
223     }
224     catch (const sdbusplus::exception::SdBusError& e)
225     {
226         phosphor::logging::log<phosphor::logging::level::INFO>(
227             "ERROR: setProperty");
228         return -1;
229     }
230 
231     return 0;
232 }
233 
234 int8_t Manufacturing::disablePidControlService(const bool disable)
235 {
236     try
237     {
238         auto dbus = getSdBus();
239         auto method = dbus->new_method_call(systemDService, systemDObjPath,
240                                             systemDMgrIntf,
241                                             disable ? "StopUnit" : "StartUnit");
242         method.append(pidControlService, "replace");
243         auto reply = dbus->call(method);
244     }
245     catch (const sdbusplus::exception::SdBusError& e)
246     {
247         phosphor::logging::log<phosphor::logging::level::INFO>(
248             "ERROR: phosphor-pid-control service start or stop failed");
249         return -1;
250     }
251     return 0;
252 }
253 
254 ipmi::RspType<uint8_t,                // Signal value
255               std::optional<uint16_t> // Fan tach value
256               >
257     appMTMGetSignal(boost::asio::yield_context yield, uint8_t signalTypeByte,
258                     uint8_t instance, uint8_t actionByte)
259 {
260     if (mtm.getAccessLvl() < MtmLvl::mtmAvailable)
261     {
262         return ipmi::responseInvalidCommand();
263     }
264 
265     SmSignalGet signalType = static_cast<SmSignalGet>(signalTypeByte);
266     SmActionGet action = static_cast<SmActionGet>(actionByte);
267 
268     switch (signalType)
269     {
270         case SmSignalGet::smFanPwmGet:
271         {
272             ipmi::Value reply;
273             std::string fullPath = fanPwmPath + std::to_string(instance + 1);
274             if (mtm.getProperty(fanService, fullPath, fanIntf, "Value",
275                                 &reply) < 0)
276             {
277                 return ipmi::responseInvalidFieldRequest();
278             }
279             double* doubleVal = std::get_if<double>(&reply);
280             if (doubleVal == nullptr)
281             {
282                 return ipmi::responseUnspecifiedError();
283             }
284             uint8_t sensorVal = std::round(*doubleVal);
285             resetMtmTimer(yield);
286             return ipmi::responseSuccess(sensorVal, std::nullopt);
287         }
288         break;
289         case SmSignalGet::smFanTachometerGet:
290         {
291             auto sdbusp = getSdBus();
292             boost::system::error_code ec;
293             using objFlatMap = boost::container::flat_map<
294                 std::string, boost::container::flat_map<
295                                  std::string, std::vector<std::string>>>;
296 
297             auto flatMap = sdbusp->yield_method_call<objFlatMap>(
298                 yield, ec, "xyz.openbmc_project.ObjectMapper",
299                 "/xyz/openbmc_project/object_mapper",
300                 "xyz.openbmc_project.ObjectMapper", "GetSubTree",
301                 fanTachBasePath, 0, std::array<const char*, 1>{fanIntf});
302             if (ec)
303             {
304                 phosphor::logging::log<phosphor::logging::level::ERR>(
305                     "Failed to query fan tach sub tree objects");
306                 return ipmi::responseUnspecifiedError();
307             }
308             if (instance >= flatMap.size())
309             {
310                 return ipmi::responseInvalidFieldRequest();
311             }
312             auto itr = flatMap.nth(instance);
313             ipmi::Value reply;
314             if (mtm.getProperty(fanService, itr->first, fanIntf, "Value",
315                                 &reply) < 0)
316             {
317                 return ipmi::responseInvalidFieldRequest();
318             }
319 
320             double* doubleVal = std::get_if<double>(&reply);
321             if (doubleVal == nullptr)
322             {
323                 return ipmi::responseUnspecifiedError();
324             }
325             uint8_t sensorVal = FAN_PRESENT | FAN_SENSOR_PRESENT;
326             std::optional<uint16_t> fanTach = std::round(*doubleVal);
327 
328             resetMtmTimer(yield);
329             return ipmi::responseSuccess(sensorVal, fanTach);
330         }
331         break;
332         case SmSignalGet::smIdentifyButton:
333         {
334             if (action == SmActionGet::revert || action == SmActionGet::ignore)
335             {
336                 // ButtonMasked property is not supported for ID button as it is
337                 // unnecessary. Hence if requested for revert / ignore, override
338                 // it to sample action to make tools happy.
339                 action = SmActionGet::sample;
340             }
341             // fall-through
342         }
343         case SmSignalGet::smResetButton:
344         case SmSignalGet::smPowerButton:
345         case SmSignalGet::smNMIButton:
346         {
347             std::string path;
348             if (getGpioPathForSmSignal(signalType, path) < 0)
349             {
350                 return ipmi::responseInvalidFieldRequest();
351             }
352 
353             switch (action)
354             {
355                 case SmActionGet::sample:
356                     phosphor::logging::log<phosphor::logging::level::INFO>(
357                         "case SmActionGet::sample");
358                     break;
359                 case SmActionGet::ignore:
360                 {
361                     phosphor::logging::log<phosphor::logging::level::INFO>(
362                         "case SmActionGet::ignore");
363                     if (mtm.setProperty(buttonService, path, buttonIntf,
364                                         "ButtonMasked", true) < 0)
365                     {
366                         return ipmi::responseUnspecifiedError();
367                     }
368                 }
369                 break;
370                 case SmActionGet::revert:
371                 {
372                     phosphor::logging::log<phosphor::logging::level::INFO>(
373                         "case SmActionGet::revert");
374                     if (mtm.setProperty(buttonService, path, buttonIntf,
375                                         "ButtonMasked", false) < 0)
376                     {
377                         return ipmi::responseUnspecifiedError();
378                     }
379                 }
380                 break;
381 
382                 default:
383                     return ipmi::responseInvalidFieldRequest();
384                     break;
385             }
386 
387             ipmi::Value reply;
388             if (mtm.getProperty(buttonService, path, buttonIntf,
389                                 "ButtonPressed", &reply) < 0)
390             {
391                 return ipmi::responseUnspecifiedError();
392             }
393             bool* valPtr = std::get_if<bool>(&reply);
394             if (valPtr == nullptr)
395             {
396                 return ipmi::responseUnspecifiedError();
397             }
398             resetMtmTimer(yield);
399             uint8_t sensorVal = *valPtr;
400             return ipmi::responseSuccess(sensorVal, std::nullopt);
401         }
402         break;
403         case SmSignalGet::smNcsiDiag:
404         {
405             constexpr const char* netBasePath = "/sys/class/net/eth";
406             constexpr const char* carrierSuffix = "/carrier";
407             std::ifstream netIfs(netBasePath + std::to_string(instance) +
408                                  carrierSuffix);
409             if (!netIfs.good())
410             {
411                 return ipmi::responseInvalidFieldRequest();
412             }
413             std::string carrier;
414             netIfs >> carrier;
415             resetMtmTimer(yield);
416             return ipmi::responseSuccess(
417                 static_cast<uint8_t>(std::stoi(carrier)), std::nullopt);
418         }
419         break;
420         default:
421             return ipmi::responseInvalidFieldRequest();
422             break;
423     }
424 }
425 
426 ipmi::RspType<> appMTMSetSignal(boost::asio::yield_context yield,
427                                 uint8_t signalTypeByte, uint8_t instance,
428                                 uint8_t actionByte,
429                                 std::optional<uint8_t> pwmSpeed)
430 {
431     if (mtm.getAccessLvl() < MtmLvl::mtmAvailable)
432     {
433         return ipmi::responseInvalidCommand();
434     }
435 
436     SmSignalSet signalType = static_cast<SmSignalSet>(signalTypeByte);
437     SmActionSet action = static_cast<SmActionSet>(actionByte);
438     Cc retCode = ccSuccess;
439     int8_t ret = 0;
440 
441     switch (signalType)
442     {
443         case SmSignalSet::smPowerFaultLed:
444         case SmSignalSet::smSystemReadyLed:
445         case SmSignalSet::smIdentifyLed:
446             switch (action)
447             {
448                 case SmActionSet::forceDeasserted:
449                 {
450                     phosphor::logging::log<phosphor::logging::level::INFO>(
451                         "case SmActionSet::forceDeasserted");
452 
453                     retCode = ledStoreAndSet(signalType, std::string("Off"));
454                     if (retCode != ccSuccess)
455                     {
456                         return ipmi::response(retCode);
457                     }
458                     mtm.revertTimer.start(revertTimeOut);
459                 }
460                 break;
461                 case SmActionSet::forceAsserted:
462                 {
463                     phosphor::logging::log<phosphor::logging::level::INFO>(
464                         "case SmActionSet::forceAsserted");
465 
466                     retCode = ledStoreAndSet(signalType, std::string("On"));
467                     if (retCode != ccSuccess)
468                     {
469                         return ipmi::response(retCode);
470                     }
471                     mtm.revertTimer.start(revertTimeOut);
472                     if (SmSignalSet::smPowerFaultLed == signalType)
473                     {
474                         // Deassert "system ready"
475                         retCode = ledStoreAndSet(SmSignalSet::smSystemReadyLed,
476                                                  std::string("Off"));
477                     }
478                     else if (SmSignalSet::smSystemReadyLed == signalType)
479                     {
480                         // Deassert "fault led"
481                         retCode = ledStoreAndSet(SmSignalSet::smPowerFaultLed,
482                                                  std::string("Off"));
483                     }
484                 }
485                 break;
486                 case SmActionSet::revert:
487                 {
488                     phosphor::logging::log<phosphor::logging::level::INFO>(
489                         "case SmActionSet::revert");
490                     retCode = ledRevert(signalType);
491                 }
492                 break;
493                 default:
494                 {
495                     return ipmi::responseInvalidFieldRequest();
496                 }
497             }
498             break;
499         case SmSignalSet::smFanPowerSpeed:
500         {
501             if ((action == SmActionSet::forceAsserted) && (!pwmSpeed))
502             {
503                 return ipmi::responseReqDataLenInvalid();
504             }
505 
506             if ((action == SmActionSet::forceAsserted) && (*pwmSpeed > 100))
507             {
508                 return ipmi::responseInvalidFieldRequest();
509             }
510 
511             uint8_t pwmValue = 0;
512             switch (action)
513             {
514                 case SmActionSet::revert:
515                 {
516                     if (mtm.revertFanPWM)
517                     {
518                         ret = mtm.disablePidControlService(false);
519                         if (ret < 0)
520                         {
521                             return ipmi::responseUnspecifiedError();
522                         }
523                         mtm.revertFanPWM = false;
524                     }
525                 }
526                 break;
527                 case SmActionSet::forceAsserted:
528                 {
529                     pwmValue = *pwmSpeed;
530                 } // fall-through
531                 case SmActionSet::forceDeasserted:
532                 {
533                     if (!mtm.revertFanPWM)
534                     {
535                         ret = mtm.disablePidControlService(true);
536                         if (ret < 0)
537                         {
538                             return ipmi::responseUnspecifiedError();
539                         }
540                         mtm.revertFanPWM = true;
541                     }
542                     mtm.revertTimer.start(revertTimeOut);
543                     std::string fanPwmInstancePath =
544                         fanPwmPath + std::to_string(instance + 1);
545 
546                     ret =
547                         mtm.setProperty(fanService, fanPwmInstancePath, fanIntf,
548                                         "Value", static_cast<double>(pwmValue));
549                     if (ret < 0)
550                     {
551                         return ipmi::responseUnspecifiedError();
552                     }
553                 }
554                 break;
555                 default:
556                 {
557                     return ipmi::responseInvalidFieldRequest();
558                 }
559             }
560         }
561         break;
562         default:
563         {
564             return ipmi::responseInvalidFieldRequest();
565         }
566     }
567     if (retCode == ccSuccess)
568     {
569         resetMtmTimer(yield);
570     }
571     return ipmi::response(retCode);
572 }
573 
574 ipmi::RspType<> mtmKeepAlive(boost::asio::yield_context yield, uint8_t reserved,
575                              const std::array<char, 5>& intentionalSignature)
576 {
577     // Allow MTM keep alive command only in manfacturing mode.
578     if (mtm.getAccessLvl() != MtmLvl::mtmAvailable)
579     {
580         return ipmi::responseInvalidCommand();
581     }
582     constexpr std::array<char, 5> signatureOk = {'I', 'N', 'T', 'E', 'L'};
583     if (intentionalSignature != signatureOk || reserved != 0)
584     {
585         return ipmi::responseInvalidFieldRequest();
586     }
587     return ipmi::response(resetMtmTimer(yield));
588 }
589 
590 } // namespace ipmi
591 
592 void register_mtm_commands() __attribute__((constructor));
593 void register_mtm_commands()
594 {
595     // <Get SM Signal>
596     ipmi::registerHandler(
597         ipmi::prioOemBase, ipmi::netFnOemOne,
598         static_cast<ipmi::Cmd>(IPMINetfnIntelOEMGeneralCmd::cmdGetSmSignal),
599         ipmi::Privilege::Admin, ipmi::appMTMGetSignal);
600 
601     ipmi::registerHandler(
602         ipmi::prioOemBase, ipmi::netFnOemOne,
603         static_cast<ipmi::Cmd>(IPMINetfnIntelOEMGeneralCmd::cmdSetSmSignal),
604         ipmi::Privilege::Admin, ipmi::appMTMSetSignal);
605 
606     ipmi::registerHandler(
607         ipmi::prioOemBase, ipmi::netFnOemOne,
608         static_cast<ipmi::Cmd>(IPMINetfnIntelOEMGeneralCmd::cmdMtmKeepAlive),
609         ipmi::Privilege::Admin, ipmi::mtmKeepAlive);
610 
611     return;
612 }
613