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