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