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