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