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 <linux/input.h> 18 19 #include <boost/algorithm/string.hpp> 20 #include <boost/container/flat_map.hpp> 21 #include <ipmid/api.hpp> 22 #include <manufacturingcommands.hpp> 23 #include <oemcommands.hpp> 24 25 #include <filesystem> 26 #include <fstream> 27 28 namespace ipmi 29 { 30 31 Manufacturing mtm; 32 33 static auto revertTimeOut = 34 std::chrono::duration_cast<std::chrono::microseconds>( 35 std::chrono::seconds(60)); // 1 minute timeout 36 37 static constexpr uint8_t slotAddressTypeBus = 0; 38 static constexpr uint8_t slotAddressTypeUniqueid = 1; 39 static constexpr uint8_t slotI2CMaxReadSize = 35; 40 41 static constexpr const char* callbackMgrService = 42 "xyz.openbmc_project.CallbackManager"; 43 static constexpr const char* callbackMgrIntf = 44 "xyz.openbmc_project.CallbackManager"; 45 static constexpr const char* callbackMgrObjPath = 46 "/xyz/openbmc_project/CallbackManager"; 47 static constexpr const char* retriggerLedUpdate = "RetriggerLEDUpdate"; 48 49 const static constexpr char* systemDService = "org.freedesktop.systemd1"; 50 const static constexpr char* systemDObjPath = "/org/freedesktop/systemd1"; 51 const static constexpr char* systemDMgrIntf = 52 "org.freedesktop.systemd1.Manager"; 53 const static constexpr char* pidControlService = "phosphor-pid-control.service"; 54 55 static inline Cc resetMtmTimer(ipmi::Context::ptr ctx) 56 { 57 boost::system::error_code ec; 58 ctx->bus->yield_method_call<>(ctx->yield, ec, specialModeService, 59 specialModeObjPath, specialModeIntf, 60 "ResetTimer"); 61 if (ec) 62 { 63 phosphor::logging::log<phosphor::logging::level::ERR>( 64 "Failed to reset the manufacturing mode timer"); 65 return ccUnspecifiedError; 66 } 67 return ccSuccess; 68 } 69 70 int getGpioPathForSmSignal(const SmSignalGet signal, std::string& path) 71 { 72 switch (signal) 73 { 74 case SmSignalGet::smPowerButton: 75 path = "/xyz/openbmc_project/chassis/buttons/power"; 76 break; 77 case SmSignalGet::smResetButton: 78 path = "/xyz/openbmc_project/chassis/buttons/reset"; 79 break; 80 case SmSignalGet::smNMIButton: 81 path = "/xyz/openbmc_project/chassis/buttons/nmi"; 82 break; 83 case SmSignalGet::smIdentifyButton: 84 path = "/xyz/openbmc_project/chassis/buttons/id"; 85 break; 86 default: 87 return -1; 88 break; 89 } 90 return 0; 91 } 92 93 ipmi_ret_t ledStoreAndSet(SmSignalSet signal, const std::string& setState) 94 { 95 LedProperty* ledProp = mtm.findLedProperty(signal); 96 if (ledProp == nullptr) 97 { 98 return IPMI_CC_INVALID_FIELD_REQUEST; 99 } 100 101 std::string ledName = ledProp->getName(); 102 std::string ledService = ledServicePrefix + ledName; 103 std::string ledPath = ledPathPrefix + ledName; 104 ipmi::Value presentState; 105 106 if (false == ledProp->getLock()) 107 { 108 if (mtm.getProperty(ledService.c_str(), ledPath.c_str(), ledIntf, 109 "State", &presentState) != 0) 110 { 111 return IPMI_CC_UNSPECIFIED_ERROR; 112 } 113 ledProp->setPrevState(std::get<std::string>(presentState)); 114 ledProp->setLock(true); 115 if (signal == SmSignalSet::smPowerFaultLed || 116 signal == SmSignalSet::smSystemReadyLed) 117 { 118 mtm.revertLedCallback = true; 119 } 120 } 121 if (mtm.setProperty(ledService, ledPath, ledIntf, "State", 122 ledStateStr + setState) != 0) 123 { 124 return IPMI_CC_UNSPECIFIED_ERROR; 125 } 126 return IPMI_CC_OK; 127 } 128 129 ipmi_ret_t ledRevert(SmSignalSet signal) 130 { 131 LedProperty* ledProp = mtm.findLedProperty(signal); 132 if (ledProp == nullptr) 133 { 134 return IPMI_CC_INVALID_FIELD_REQUEST; 135 } 136 if (true == ledProp->getLock()) 137 { 138 ledProp->setLock(false); 139 if (signal == SmSignalSet::smPowerFaultLed || 140 signal == SmSignalSet::smSystemReadyLed) 141 { 142 try 143 { 144 ipmi::method_no_args::callDbusMethod( 145 *getSdBus(), callbackMgrService, callbackMgrObjPath, 146 callbackMgrIntf, retriggerLedUpdate); 147 } 148 catch (const sdbusplus::exception_t& e) 149 { 150 return IPMI_CC_UNSPECIFIED_ERROR; 151 } 152 mtm.revertLedCallback = false; 153 } 154 else 155 { 156 std::string ledName = ledProp->getName(); 157 std::string ledService = ledServicePrefix + ledName; 158 std::string ledPath = ledPathPrefix + ledName; 159 if (mtm.setProperty(ledService, ledPath, ledIntf, "State", 160 ledProp->getPrevState()) != 0) 161 { 162 return IPMI_CC_UNSPECIFIED_ERROR; 163 } 164 } 165 } 166 return IPMI_CC_OK; 167 } 168 169 void Manufacturing::initData() 170 { 171 ledPropertyList.push_back( 172 LedProperty(SmSignalSet::smPowerFaultLed, "status_amber")); 173 ledPropertyList.push_back( 174 LedProperty(SmSignalSet::smSystemReadyLed, "status_green")); 175 ledPropertyList.push_back( 176 LedProperty(SmSignalSet::smIdentifyLed, "identify")); 177 } 178 179 void Manufacturing::revertTimerHandler() 180 { 181 182 #ifdef BMC_VALIDATION_UNSECURE_FEATURE 183 if (mtm.getMfgMode() == SpecialMode::valUnsecure) 184 { 185 // Don't revert the behaviour for validation unsecure mode. 186 return; 187 } 188 #endif 189 if (revertFanPWM) 190 { 191 revertFanPWM = false; 192 disablePidControlService(false); 193 } 194 195 if (mtmTestBeepFd != -1) 196 { 197 ::close(mtmTestBeepFd); 198 mtmTestBeepFd = -1; 199 } 200 201 for (const auto& ledProperty : ledPropertyList) 202 { 203 const std::string& ledName = ledProperty.getName(); 204 if (ledName == "identify" && mtm.getMfgMode() == SpecialMode::mfg) 205 { 206 // Don't revert the behaviour for manufacturing mode 207 continue; 208 } 209 ledRevert(ledProperty.getSignal()); 210 } 211 } 212 213 Manufacturing::Manufacturing() : 214 revertTimer([&](void) { revertTimerHandler(); }) 215 { 216 initData(); 217 } 218 219 int8_t Manufacturing::getProperty(const std::string& service, 220 const std::string& path, 221 const std::string& interface, 222 const std::string& propertyName, 223 ipmi::Value* reply) 224 { 225 try 226 { 227 *reply = ipmi::getDbusProperty(*getSdBus(), service, path, interface, 228 propertyName); 229 } 230 catch (const sdbusplus::exception::exception& e) 231 { 232 phosphor::logging::log<phosphor::logging::level::INFO>( 233 "ERROR: getProperty"); 234 return -1; 235 } 236 237 return 0; 238 } 239 240 int8_t Manufacturing::setProperty(const std::string& service, 241 const std::string& path, 242 const std::string& interface, 243 const std::string& propertyName, 244 ipmi::Value value) 245 { 246 try 247 { 248 ipmi::setDbusProperty(*getSdBus(), service, path, interface, 249 propertyName, value); 250 } 251 catch (const sdbusplus::exception::exception& e) 252 { 253 phosphor::logging::log<phosphor::logging::level::INFO>( 254 "ERROR: setProperty"); 255 return -1; 256 } 257 258 return 0; 259 } 260 261 int8_t Manufacturing::disablePidControlService(const bool disable) 262 { 263 try 264 { 265 auto dbus = getSdBus(); 266 auto method = dbus->new_method_call(systemDService, systemDObjPath, 267 systemDMgrIntf, 268 disable ? "StopUnit" : "StartUnit"); 269 method.append(pidControlService, "replace"); 270 auto reply = dbus->call(method); 271 } 272 catch (const sdbusplus::exception::exception& e) 273 { 274 phosphor::logging::log<phosphor::logging::level::INFO>( 275 "ERROR: phosphor-pid-control service start or stop failed"); 276 return -1; 277 } 278 return 0; 279 } 280 281 ipmi::RspType<uint8_t, // Signal value 282 std::optional<uint16_t> // Fan tach value 283 > 284 appMTMGetSignal(ipmi::Context::ptr ctx, uint8_t signalTypeByte, 285 uint8_t instance, uint8_t actionByte) 286 { 287 // mfg filter logic is used to allow MTM get signal command only in 288 // manfacturing mode. 289 290 SmSignalGet signalType = static_cast<SmSignalGet>(signalTypeByte); 291 SmActionGet action = static_cast<SmActionGet>(actionByte); 292 293 switch (signalType) 294 { 295 case SmSignalGet::smChassisIntrusion: 296 { 297 ipmi::Value reply; 298 if (mtm.getProperty(intrusionService, intrusionPath, intrusionIntf, 299 "Status", &reply) < 0) 300 { 301 return ipmi::responseInvalidFieldRequest(); 302 } 303 std::string* intrusionStatus = std::get_if<std::string>(&reply); 304 if (!intrusionStatus) 305 { 306 return ipmi::responseUnspecifiedError(); 307 } 308 309 uint8_t status = 0; 310 if (!intrusionStatus->compare("Normal")) 311 { 312 status = static_cast<uint8_t>(IntrusionStatus::normal); 313 } 314 else if (!intrusionStatus->compare("HardwareIntrusion")) 315 { 316 status = 317 static_cast<uint8_t>(IntrusionStatus::hardwareIntrusion); 318 } 319 else if (!intrusionStatus->compare("TamperingDetected")) 320 { 321 status = 322 static_cast<uint8_t>(IntrusionStatus::tamperingDetected); 323 } 324 else 325 { 326 return ipmi::responseUnspecifiedError(); 327 } 328 return ipmi::responseSuccess(status, std::nullopt); 329 } 330 case SmSignalGet::smFanPwmGet: 331 { 332 ipmi::Value reply; 333 std::string fullPath = fanPwmPath + std::to_string(instance + 1); 334 if (mtm.getProperty(fanService, fullPath, fanIntf, "Value", 335 &reply) < 0) 336 { 337 return ipmi::responseInvalidFieldRequest(); 338 } 339 double* doubleVal = std::get_if<double>(&reply); 340 if (doubleVal == nullptr) 341 { 342 return ipmi::responseUnspecifiedError(); 343 } 344 uint8_t sensorVal = std::round(*doubleVal); 345 resetMtmTimer(ctx); 346 return ipmi::responseSuccess(sensorVal, std::nullopt); 347 } 348 break; 349 case SmSignalGet::smFanTachometerGet: 350 { 351 boost::system::error_code ec; 352 using objFlatMap = boost::container::flat_map< 353 std::string, boost::container::flat_map< 354 std::string, std::vector<std::string>>>; 355 356 auto flatMap = ctx->bus->yield_method_call<objFlatMap>( 357 ctx->yield, ec, "xyz.openbmc_project.ObjectMapper", 358 "/xyz/openbmc_project/object_mapper", 359 "xyz.openbmc_project.ObjectMapper", "GetSubTree", 360 fanTachBasePath, 0, std::array<const char*, 1>{fanIntf}); 361 if (ec) 362 { 363 phosphor::logging::log<phosphor::logging::level::ERR>( 364 "Failed to query fan tach sub tree objects"); 365 return ipmi::responseUnspecifiedError(); 366 } 367 if (instance >= flatMap.size()) 368 { 369 return ipmi::responseInvalidFieldRequest(); 370 } 371 auto itr = flatMap.nth(instance); 372 ipmi::Value reply; 373 if (mtm.getProperty(fanService, itr->first, fanIntf, "Value", 374 &reply) < 0) 375 { 376 return ipmi::responseInvalidFieldRequest(); 377 } 378 379 double* doubleVal = std::get_if<double>(&reply); 380 if (doubleVal == nullptr) 381 { 382 return ipmi::responseUnspecifiedError(); 383 } 384 uint8_t sensorVal = FAN_PRESENT | FAN_SENSOR_PRESENT; 385 std::optional<uint16_t> fanTach = std::round(*doubleVal); 386 387 resetMtmTimer(ctx); 388 return ipmi::responseSuccess(sensorVal, fanTach); 389 } 390 break; 391 case SmSignalGet::smIdentifyButton: 392 { 393 if (action == SmActionGet::revert || action == SmActionGet::ignore) 394 { 395 // ButtonMasked property is not supported for ID button as it is 396 // unnecessary. Hence if requested for revert / ignore, override 397 // it to sample action to make tools happy. 398 action = SmActionGet::sample; 399 } 400 // fall-through 401 } 402 case SmSignalGet::smResetButton: 403 case SmSignalGet::smPowerButton: 404 case SmSignalGet::smNMIButton: 405 { 406 std::string path; 407 if (getGpioPathForSmSignal(signalType, path) < 0) 408 { 409 return ipmi::responseInvalidFieldRequest(); 410 } 411 412 switch (action) 413 { 414 case SmActionGet::sample: 415 phosphor::logging::log<phosphor::logging::level::INFO>( 416 "case SmActionGet::sample"); 417 break; 418 case SmActionGet::ignore: 419 { 420 phosphor::logging::log<phosphor::logging::level::INFO>( 421 "case SmActionGet::ignore"); 422 if (mtm.setProperty(buttonService, path, buttonIntf, 423 "ButtonMasked", true) < 0) 424 { 425 return ipmi::responseUnspecifiedError(); 426 } 427 } 428 break; 429 case SmActionGet::revert: 430 { 431 phosphor::logging::log<phosphor::logging::level::INFO>( 432 "case SmActionGet::revert"); 433 if (mtm.setProperty(buttonService, path, buttonIntf, 434 "ButtonMasked", false) < 0) 435 { 436 return ipmi::responseUnspecifiedError(); 437 } 438 } 439 break; 440 441 default: 442 return ipmi::responseInvalidFieldRequest(); 443 break; 444 } 445 446 ipmi::Value reply; 447 if (mtm.getProperty(buttonService, path, buttonIntf, 448 "ButtonPressed", &reply) < 0) 449 { 450 return ipmi::responseUnspecifiedError(); 451 } 452 bool* valPtr = std::get_if<bool>(&reply); 453 if (valPtr == nullptr) 454 { 455 return ipmi::responseUnspecifiedError(); 456 } 457 resetMtmTimer(ctx); 458 uint8_t sensorVal = *valPtr; 459 return ipmi::responseSuccess(sensorVal, std::nullopt); 460 } 461 break; 462 case SmSignalGet::smNcsiDiag: 463 { 464 constexpr const char* netBasePath = "/sys/class/net/eth"; 465 constexpr const char* carrierSuffix = "/carrier"; 466 std::ifstream netIfs(netBasePath + std::to_string(instance) + 467 carrierSuffix); 468 if (!netIfs.good()) 469 { 470 return ipmi::responseInvalidFieldRequest(); 471 } 472 std::string carrier; 473 netIfs >> carrier; 474 resetMtmTimer(ctx); 475 return ipmi::responseSuccess( 476 static_cast<uint8_t>(std::stoi(carrier)), std::nullopt); 477 } 478 break; 479 default: 480 return ipmi::responseInvalidFieldRequest(); 481 break; 482 } 483 } 484 485 ipmi::RspType<> appMTMSetSignal(ipmi::Context::ptr ctx, uint8_t signalTypeByte, 486 uint8_t instance, uint8_t actionByte, 487 std::optional<uint8_t> pwmSpeed) 488 { 489 // mfg filter logic is used to allow MTM set signal command only in 490 // manfacturing mode. 491 492 SmSignalSet signalType = static_cast<SmSignalSet>(signalTypeByte); 493 SmActionSet action = static_cast<SmActionSet>(actionByte); 494 Cc retCode = ccSuccess; 495 int8_t ret = 0; 496 497 switch (signalType) 498 { 499 case SmSignalSet::smPowerFaultLed: 500 case SmSignalSet::smSystemReadyLed: 501 case SmSignalSet::smIdentifyLed: 502 switch (action) 503 { 504 case SmActionSet::forceDeasserted: 505 { 506 phosphor::logging::log<phosphor::logging::level::INFO>( 507 "case SmActionSet::forceDeasserted"); 508 509 retCode = ledStoreAndSet(signalType, std::string("Off")); 510 if (retCode != ccSuccess) 511 { 512 return ipmi::response(retCode); 513 } 514 mtm.revertTimer.start(revertTimeOut); 515 } 516 break; 517 case SmActionSet::forceAsserted: 518 { 519 phosphor::logging::log<phosphor::logging::level::INFO>( 520 "case SmActionSet::forceAsserted"); 521 522 retCode = ledStoreAndSet(signalType, std::string("On")); 523 if (retCode != ccSuccess) 524 { 525 return ipmi::response(retCode); 526 } 527 mtm.revertTimer.start(revertTimeOut); 528 if (SmSignalSet::smPowerFaultLed == signalType) 529 { 530 // Deassert "system ready" 531 retCode = ledStoreAndSet(SmSignalSet::smSystemReadyLed, 532 std::string("Off")); 533 } 534 else if (SmSignalSet::smSystemReadyLed == signalType) 535 { 536 // Deassert "fault led" 537 retCode = ledStoreAndSet(SmSignalSet::smPowerFaultLed, 538 std::string("Off")); 539 } 540 } 541 break; 542 case SmActionSet::revert: 543 { 544 phosphor::logging::log<phosphor::logging::level::INFO>( 545 "case SmActionSet::revert"); 546 retCode = ledRevert(signalType); 547 } 548 break; 549 default: 550 { 551 return ipmi::responseInvalidFieldRequest(); 552 } 553 } 554 break; 555 case SmSignalSet::smFanPowerSpeed: 556 { 557 if ((action == SmActionSet::forceAsserted) && (!pwmSpeed)) 558 { 559 return ipmi::responseReqDataLenInvalid(); 560 } 561 562 if ((action == SmActionSet::forceAsserted) && (*pwmSpeed > 100)) 563 { 564 return ipmi::responseInvalidFieldRequest(); 565 } 566 567 uint8_t pwmValue = 0; 568 switch (action) 569 { 570 case SmActionSet::revert: 571 { 572 if (mtm.revertFanPWM) 573 { 574 ret = mtm.disablePidControlService(false); 575 if (ret < 0) 576 { 577 return ipmi::responseUnspecifiedError(); 578 } 579 mtm.revertFanPWM = false; 580 } 581 } 582 break; 583 case SmActionSet::forceAsserted: 584 { 585 pwmValue = *pwmSpeed; 586 } // fall-through 587 case SmActionSet::forceDeasserted: 588 { 589 if (!mtm.revertFanPWM) 590 { 591 ret = mtm.disablePidControlService(true); 592 if (ret < 0) 593 { 594 return ipmi::responseUnspecifiedError(); 595 } 596 mtm.revertFanPWM = true; 597 } 598 mtm.revertTimer.start(revertTimeOut); 599 std::string fanPwmInstancePath = 600 fanPwmPath + std::to_string(instance + 1); 601 602 ret = 603 mtm.setProperty(fanService, fanPwmInstancePath, fanIntf, 604 "Value", static_cast<double>(pwmValue)); 605 if (ret < 0) 606 { 607 return ipmi::responseUnspecifiedError(); 608 } 609 } 610 break; 611 default: 612 { 613 return ipmi::responseInvalidFieldRequest(); 614 } 615 } 616 } 617 break; 618 case SmSignalSet::smSpeaker: 619 { 620 phosphor::logging::log<phosphor::logging::level::INFO>( 621 "Performing Speaker SmActionSet", 622 phosphor::logging::entry("ACTION=%d", 623 static_cast<uint8_t>(action))); 624 switch (action) 625 { 626 case SmActionSet::forceAsserted: 627 { 628 char beepDevName[] = "/dev/input/event0"; 629 if (mtm.mtmTestBeepFd != -1) 630 { 631 phosphor::logging::log<phosphor::logging::level::INFO>( 632 "mtm beep device is opened already!"); 633 // returning success as already beep is in progress 634 return ipmi::response(retCode); 635 } 636 637 if ((mtm.mtmTestBeepFd = 638 ::open(beepDevName, O_RDWR | O_CLOEXEC)) < 0) 639 { 640 phosphor::logging::log<phosphor::logging::level::ERR>( 641 "Failed to open input device"); 642 return ipmi::responseUnspecifiedError(); 643 } 644 645 struct input_event event; 646 event.type = EV_SND; 647 event.code = SND_TONE; 648 event.value = 2000; 649 650 if (::write(mtm.mtmTestBeepFd, &event, 651 sizeof(struct input_event)) != 652 sizeof(struct input_event)) 653 { 654 phosphor::logging::log<phosphor::logging::level::ERR>( 655 "Failed to write a tone sound event"); 656 ::close(mtm.mtmTestBeepFd); 657 mtm.mtmTestBeepFd = -1; 658 return ipmi::responseUnspecifiedError(); 659 } 660 mtm.revertTimer.start(revertTimeOut); 661 } 662 break; 663 case SmActionSet::revert: 664 case SmActionSet::forceDeasserted: 665 { 666 if (mtm.mtmTestBeepFd != -1) 667 { 668 ::close(mtm.mtmTestBeepFd); 669 mtm.mtmTestBeepFd = -1; 670 } 671 } 672 break; 673 default: 674 { 675 return ipmi::responseInvalidFieldRequest(); 676 } 677 } 678 } 679 break; 680 case SmSignalSet::smDiskFaultLed: 681 { 682 boost::system::error_code ec; 683 using objPaths = std::vector<std::string>; 684 std::string driveBasePath = 685 "/xyz/openbmc_project/inventory/item/drive/"; 686 static constexpr const char* driveLedIntf = 687 "xyz.openbmc_project.Led.Group"; 688 static constexpr const char* hsbpService = 689 "xyz.openbmc_project.HsbpManager"; 690 691 auto driveList = ctx->bus->yield_method_call<objPaths>( 692 ctx->yield, ec, "xyz.openbmc_project.ObjectMapper", 693 "/xyz/openbmc_project/object_mapper", 694 "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", 695 driveBasePath, 0, std::array<const char*, 1>{driveLedIntf}); 696 if (ec) 697 { 698 phosphor::logging::log<phosphor::logging::level::ERR>( 699 "Failed to query HSBP drive sub tree objects"); 700 return ipmi::responseUnspecifiedError(); 701 } 702 std::string driveObjPath = 703 driveBasePath + "Drive_" + std::to_string(instance + 1); 704 if (std::find(driveList.begin(), driveList.end(), driveObjPath) == 705 driveList.end()) 706 { 707 return ipmi::responseInvalidFieldRequest(); 708 } 709 bool driveLedState = false; 710 switch (action) 711 { 712 case SmActionSet::forceAsserted: 713 { 714 driveLedState = true; 715 } 716 break; 717 case SmActionSet::revert: 718 { 719 driveLedState = false; 720 } 721 break; 722 case SmActionSet::forceDeasserted: 723 { 724 driveLedState = false; 725 } 726 break; 727 default: 728 { 729 return ipmi::responseInvalidFieldRequest(); 730 } 731 } 732 ret = mtm.setProperty(hsbpService, driveObjPath, driveLedIntf, 733 "Asserted", driveLedState); 734 if (ret < 0) 735 { 736 return ipmi::responseUnspecifiedError(); 737 } 738 } 739 break; 740 default: 741 { 742 return ipmi::responseInvalidFieldRequest(); 743 } 744 } 745 if (retCode == ccSuccess) 746 { 747 resetMtmTimer(ctx); 748 } 749 return ipmi::response(retCode); 750 } 751 752 ipmi::RspType<> mtmKeepAlive(ipmi::Context::ptr ctx, uint8_t reserved, 753 const std::array<char, 5>& intentionalSignature) 754 { 755 // mfg filter logic is used to allow MTM keep alive command only in 756 // manfacturing mode 757 758 constexpr std::array<char, 5> signatureOk = {'I', 'N', 'T', 'E', 'L'}; 759 if (intentionalSignature != signatureOk || reserved != 0) 760 { 761 return ipmi::responseInvalidFieldRequest(); 762 } 763 return ipmi::response(resetMtmTimer(ctx)); 764 } 765 766 static constexpr unsigned int makeCmdKey(unsigned int netFn, unsigned int cmd) 767 { 768 return (netFn << 8) | cmd; 769 } 770 771 ipmi::Cc mfgFilterMessage(ipmi::message::Request::ptr request) 772 { 773 // Restricted commands, must be executed only in Manufacturing mode 774 switch (makeCmdKey(request->ctx->netFn, request->ctx->cmd)) 775 { 776 // i2c master write read command needs additional checking 777 case makeCmdKey(ipmi::netFnApp, ipmi::app::cmdMasterWriteRead): 778 if (request->payload.size() > 4) 779 { 780 // Allow write data count > 1 only in Special mode 781 if (mtm.getMfgMode() == SpecialMode::none) 782 { 783 return ipmi::ccInsufficientPrivilege; 784 } 785 } 786 return ipmi::ccSuccess; 787 case makeCmdKey(ipmi::netFnOemOne, 788 ipmi::intel::general::cmdGetSmSignal): 789 case makeCmdKey(ipmi::netFnOemOne, 790 ipmi::intel::general::cmdSetSmSignal): 791 case makeCmdKey(ipmi::netFnOemOne, 792 ipmi::intel::general::cmdMtmKeepAlive): 793 case makeCmdKey(ipmi::netFnOemOne, 794 ipmi::intel::general::cmdSetManufacturingData): 795 case makeCmdKey(ipmi::netFnOemOne, 796 ipmi::intel::general::cmdGetManufacturingData): 797 case makeCmdKey(ipmi::intel::netFnGeneral, 798 ipmi::intel::general::cmdSetFITcLayout): 799 case makeCmdKey(ipmi::netFnOemOne, 800 ipmi::intel::general::cmdMTMBMCFeatureControl): 801 case makeCmdKey(ipmi::netFnStorage, ipmi::storage::cmdWriteFruData): 802 case makeCmdKey(ipmi::netFnOemTwo, ipmi::intel::platform::cmdClearCMOS): 803 804 // Check for Special mode 805 if (mtm.getMfgMode() == SpecialMode::none) 806 { 807 return ipmi::ccInvalidCommand; 808 } 809 return ipmi::ccSuccess; 810 case makeCmdKey(ipmi::netFnStorage, ipmi::storage::cmdDeleteSelEntry): 811 { 812 return ipmi::ccInvalidCommand; 813 } 814 } 815 return ipmi::ccSuccess; 816 } 817 818 static constexpr uint8_t maxEthSize = 6; 819 static constexpr uint8_t maxSupportedEth = 3; 820 static constexpr const char* factoryEthAddrBaseFileName = 821 "/var/sofs/factory-settings/network/mac/eth"; 822 823 ipmi::RspType<> setManufacturingData(ipmi::Context::ptr ctx, uint8_t dataType, 824 std::array<uint8_t, maxEthSize> ethData) 825 { 826 // mfg filter logic will restrict this command executing only in mfg mode. 827 if (dataType >= maxSupportedEth) 828 { 829 return ipmi::responseParmOutOfRange(); 830 } 831 832 constexpr uint8_t invalidData = 0; 833 constexpr uint8_t validData = 1; 834 constexpr uint8_t ethAddrStrSize = 835 19; // XX:XX:XX:XX:XX:XX + \n + null termination; 836 std::vector<uint8_t> buff(ethAddrStrSize); 837 std::snprintf(reinterpret_cast<char*>(buff.data()), ethAddrStrSize, 838 "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx\n", ethData.at(0), 839 ethData.at(1), ethData.at(2), ethData.at(3), ethData.at(4), 840 ethData.at(5)); 841 std::ofstream oEthFile(factoryEthAddrBaseFileName + 842 std::to_string(dataType), 843 std::ofstream::out); 844 if (!oEthFile.good()) 845 { 846 return ipmi::responseUnspecifiedError(); 847 } 848 849 oEthFile << reinterpret_cast<char*>(buff.data()); 850 oEthFile.flush(); 851 oEthFile.close(); 852 853 resetMtmTimer(ctx); 854 return ipmi::responseSuccess(); 855 } 856 857 ipmi::RspType<uint8_t, std::array<uint8_t, maxEthSize>> 858 getManufacturingData(ipmi::Context::ptr ctx, uint8_t dataType) 859 { 860 // mfg filter logic will restrict this command executing only in mfg mode. 861 if (dataType >= maxSupportedEth) 862 { 863 return ipmi::responseParmOutOfRange(); 864 } 865 std::array<uint8_t, maxEthSize> ethData{0}; 866 constexpr uint8_t invalidData = 0; 867 constexpr uint8_t validData = 1; 868 869 std::ifstream iEthFile(factoryEthAddrBaseFileName + 870 std::to_string(dataType), 871 std::ifstream::in); 872 if (!iEthFile.good()) 873 { 874 return ipmi::responseSuccess(invalidData, ethData); 875 } 876 std::string ethStr; 877 iEthFile >> ethStr; 878 uint8_t* data = ethData.data(); 879 std::sscanf(ethStr.c_str(), "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx", 880 data, (data + 1), (data + 2), (data + 3), (data + 4), 881 (data + 5)); 882 883 resetMtmTimer(ctx); 884 return ipmi::responseSuccess(validData, ethData); 885 } 886 887 /** @brief implements slot master write read IPMI command which can be used for 888 * low-level I2C/SMBus write, read or write-read access for PCIE slots 889 * @param reserved - skip 6 bit 890 * @param addressType - address type 891 * @param bbSlotNum - baseboard slot number 892 * @param riserSlotNum - riser slot number 893 * @param reserved2 - skip 2 bit 894 * @param slaveAddr - slave address 895 * @param readCount - number of bytes to be read 896 * @param writeData - data to be written 897 * 898 * @returns IPMI completion code plus response data 899 */ 900 ipmi::RspType<std::vector<uint8_t>> 901 appSlotI2CMasterWriteRead(uint6_t reserved, uint2_t addressType, 902 uint3_t bbSlotNum, uint3_t riserSlotNum, 903 uint2_t resvered2, uint8_t slaveAddr, 904 uint8_t readCount, std::vector<uint8_t> writeData) 905 { 906 const size_t writeCount = writeData.size(); 907 std::string i2cBus; 908 if (addressType == slotAddressTypeBus) 909 { 910 std::string path = "/dev/i2c-mux/Riser_" + 911 std::to_string(static_cast<uint8_t>(bbSlotNum)) + 912 "_Mux/Pcie_Slot_" + 913 std::to_string(static_cast<uint8_t>(riserSlotNum)); 914 915 if (std::filesystem::exists(path) && std::filesystem::is_symlink(path)) 916 { 917 i2cBus = std::filesystem::read_symlink(path); 918 } 919 else 920 { 921 phosphor::logging::log<phosphor::logging::level::ERR>( 922 "Master write read command: Cannot get BusID"); 923 return ipmi::responseInvalidFieldRequest(); 924 } 925 } 926 else if (addressType == slotAddressTypeUniqueid) 927 { 928 i2cBus = "/dev/i2c-" + 929 std::to_string(static_cast<uint8_t>(bbSlotNum) | 930 (static_cast<uint8_t>(riserSlotNum) << 3)); 931 } 932 else 933 { 934 phosphor::logging::log<phosphor::logging::level::ERR>( 935 "Master write read command: invalid request"); 936 return ipmi::responseInvalidFieldRequest(); 937 } 938 939 // Allow single byte write as it is offset byte to read the data, rest allow 940 // only in Special mode. 941 if (writeCount > 1) 942 { 943 if (mtm.getMfgMode() == SpecialMode::none) 944 { 945 return ipmi::responseInsufficientPrivilege(); 946 } 947 } 948 949 if (readCount > slotI2CMaxReadSize) 950 { 951 phosphor::logging::log<phosphor::logging::level::ERR>( 952 "Master write read command: Read count exceeds limit"); 953 return ipmi::responseParmOutOfRange(); 954 } 955 956 if (!readCount && !writeCount) 957 { 958 phosphor::logging::log<phosphor::logging::level::ERR>( 959 "Master write read command: Read & write count are 0"); 960 return ipmi::responseInvalidFieldRequest(); 961 } 962 963 std::vector<uint8_t> readBuf(readCount); 964 965 ipmi::Cc retI2C = ipmi::i2cWriteRead(i2cBus, slaveAddr, writeData, readBuf); 966 if (retI2C != ipmi::ccSuccess) 967 { 968 return ipmi::response(retI2C); 969 } 970 971 return ipmi::responseSuccess(readBuf); 972 } 973 974 ipmi::RspType<> clearCMOS() 975 { 976 // There is an i2c device on bus 4, the slave address is 0x38. Based on the 977 // spec, writing 0x1 to address 0x61 on this device, will trigger the clear 978 // CMOS action. 979 constexpr uint8_t slaveAddr = 0x38; 980 std::string i2cBus = "/dev/i2c-4"; 981 std::vector<uint8_t> writeData = {0x61, 0x1}; 982 std::vector<uint8_t> readBuf(0); 983 984 ipmi::Cc retI2C = ipmi::i2cWriteRead(i2cBus, slaveAddr, writeData, readBuf); 985 return ipmi::response(retI2C); 986 } 987 988 ipmi::RspType<> setFITcLayout(uint32_t layout) 989 { 990 static constexpr const char* factoryFITcLayout = 991 "/var/sofs/factory-settings/layout/fitc"; 992 std::filesystem::path fitcDir = 993 std::filesystem::path(factoryFITcLayout).parent_path(); 994 std::error_code ec; 995 std::filesystem::create_directories(fitcDir, ec); 996 if (ec) 997 { 998 return ipmi::responseUnspecifiedError(); 999 } 1000 try 1001 { 1002 std::ofstream file(factoryFITcLayout); 1003 file << layout; 1004 file.flush(); 1005 file.close(); 1006 } 1007 catch (const std::exception& e) 1008 { 1009 return ipmi::responseUnspecifiedError(); 1010 } 1011 1012 return ipmi::responseSuccess(); 1013 } 1014 1015 static std::vector<std::string> 1016 getMCTPServiceConfigPaths(ipmi::Context::ptr& ctx) 1017 { 1018 boost::system::error_code ec; 1019 auto configPaths = ctx->bus->yield_method_call<std::vector<std::string>>( 1020 ctx->yield, ec, "xyz.openbmc_project.ObjectMapper", 1021 "/xyz/openbmc_project/object_mapper", 1022 "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", 1023 "/xyz/openbmc_project/inventory/system/board", 2, 1024 std::array<const char*, 1>{ 1025 "xyz.openbmc_project.Configuration.MctpConfiguration"}); 1026 if (ec) 1027 { 1028 throw std::runtime_error( 1029 "Failed to query configuration sub tree objects"); 1030 } 1031 return configPaths; 1032 } 1033 1034 static ipmi::RspType<> startOrStopService(ipmi::Context::ptr& ctx, 1035 const uint8_t enable, 1036 const std::string& serviceName, 1037 bool disableOrEnableUnitFiles = true) 1038 { 1039 constexpr bool runtimeOnly = false; 1040 constexpr bool force = false; 1041 1042 boost::system::error_code ec; 1043 switch (enable) 1044 { 1045 case ipmi::SupportedFeatureActions::stop: 1046 ctx->bus->yield_method_call(ctx->yield, ec, systemDService, 1047 systemDObjPath, systemDMgrIntf, 1048 "StopUnit", serviceName, "replace"); 1049 break; 1050 case ipmi::SupportedFeatureActions::start: 1051 ctx->bus->yield_method_call(ctx->yield, ec, systemDService, 1052 systemDObjPath, systemDMgrIntf, 1053 "StartUnit", serviceName, "replace"); 1054 break; 1055 case ipmi::SupportedFeatureActions::disable: 1056 if (disableOrEnableUnitFiles == true) 1057 { 1058 ctx->bus->yield_method_call( 1059 ctx->yield, ec, systemDService, systemDObjPath, 1060 systemDMgrIntf, "DisableUnitFiles", 1061 std::array<const char*, 1>{serviceName.c_str()}, 1062 runtimeOnly); 1063 } 1064 ctx->bus->yield_method_call( 1065 ctx->yield, ec, systemDService, systemDObjPath, systemDMgrIntf, 1066 "MaskUnitFiles", 1067 std::array<const char*, 1>{serviceName.c_str()}, runtimeOnly, 1068 force); 1069 break; 1070 case ipmi::SupportedFeatureActions::enable: 1071 ctx->bus->yield_method_call( 1072 ctx->yield, ec, systemDService, systemDObjPath, systemDMgrIntf, 1073 "UnmaskUnitFiles", 1074 std::array<const char*, 1>{serviceName.c_str()}, runtimeOnly); 1075 if (disableOrEnableUnitFiles == true) 1076 { 1077 ctx->bus->yield_method_call( 1078 ctx->yield, ec, systemDService, systemDObjPath, 1079 systemDMgrIntf, "EnableUnitFiles", 1080 std::array<const char*, 1>{serviceName.c_str()}, 1081 runtimeOnly, force); 1082 } 1083 break; 1084 default: 1085 phosphor::logging::log<phosphor::logging::level::WARNING>( 1086 "ERROR: Invalid feature action selected", 1087 phosphor::logging::entry("ACTION=%d", enable)); 1088 return ipmi::responseInvalidFieldRequest(); 1089 } 1090 if (ec) 1091 { 1092 phosphor::logging::log<phosphor::logging::level::WARNING>( 1093 "ERROR: Service start or stop failed", 1094 phosphor::logging::entry("SERVICE=%s", serviceName.c_str())); 1095 return ipmi::responseUnspecifiedError(); 1096 } 1097 return ipmi::responseSuccess(); 1098 } 1099 1100 static std::string getMCTPServiceName(const std::string& objectPath) 1101 { 1102 const auto serviceArgument = boost::algorithm::replace_all_copy( 1103 boost::algorithm::replace_first_copy( 1104 objectPath, "/xyz/openbmc_project/inventory/system/board/", ""), 1105 "/", "_2f"); 1106 std::string unitName = 1107 "xyz.openbmc_project.mctpd@" + serviceArgument + ".service"; 1108 return unitName; 1109 } 1110 1111 static ipmi::RspType<> handleMCTPFeature(ipmi::Context::ptr& ctx, 1112 const uint8_t enable, 1113 const std::string& binding) 1114 { 1115 std::vector<std::string> configPaths; 1116 try 1117 { 1118 configPaths = getMCTPServiceConfigPaths(ctx); 1119 } 1120 catch (const std::exception& e) 1121 { 1122 phosphor::logging::log<phosphor::logging::level::ERR>(e.what()); 1123 return ipmi::responseUnspecifiedError(); 1124 } 1125 1126 for (const auto& objectPath : configPaths) 1127 { 1128 auto const pos = objectPath.find_last_of('/'); 1129 if (binding == objectPath.substr(pos + 1)) 1130 { 1131 return startOrStopService(ctx, enable, 1132 getMCTPServiceName(objectPath), false); 1133 } 1134 } 1135 return ipmi::responseSuccess(); 1136 } 1137 1138 /** @brief implements MTM BMC Feature Control IPMI command which can be 1139 * used to enable or disable the supported BMC features. 1140 * @param yield - context object that represents the currently executing 1141 * coroutine 1142 * @param feature - feature enum to enable or disable 1143 * @param enable - enable or disable the feature 1144 * @param featureArg - custom arguments for that feature 1145 * @param reserved - reserved for future use 1146 * 1147 * @returns IPMI completion code 1148 */ 1149 ipmi::RspType<> mtmBMCFeatureControl(ipmi::Context::ptr ctx, 1150 const uint8_t feature, 1151 const uint8_t enable, 1152 const uint8_t featureArg, 1153 const uint16_t reserved) 1154 { 1155 if (reserved != 0) 1156 { 1157 return ipmi::responseInvalidFieldRequest(); 1158 } 1159 1160 switch (feature) 1161 { 1162 case ipmi::SupportedFeatureControls::mctp: 1163 switch (featureArg) 1164 { 1165 case ipmi::SupportedMCTPBindings::mctpPCIe: 1166 return handleMCTPFeature(ctx, enable, "MCTP_PCIe"); 1167 case ipmi::SupportedMCTPBindings::mctpSMBusHSBP: 1168 return handleMCTPFeature(ctx, enable, "MCTP_SMBus_HSBP"); 1169 case ipmi::SupportedMCTPBindings::mctpSMBusPCIeSlot: 1170 return handleMCTPFeature(ctx, enable, 1171 "MCTP_SMBus_PCIe_slot"); 1172 default: 1173 return ipmi::responseInvalidFieldRequest(); 1174 } 1175 break; 1176 case ipmi::SupportedFeatureControls::pcieScan: 1177 if (featureArg != 0) 1178 { 1179 return ipmi::responseInvalidFieldRequest(); 1180 } 1181 startOrStopService(ctx, enable, "xyz.openbmc_project.PCIe.service"); 1182 break; 1183 default: 1184 return ipmi::responseInvalidFieldRequest(); 1185 } 1186 return ipmi::responseSuccess(); 1187 } 1188 } // namespace ipmi 1189 1190 void register_mtm_commands() __attribute__((constructor)); 1191 void register_mtm_commands() 1192 { 1193 // <Get SM Signal> 1194 ipmi::registerHandler(ipmi::prioOemBase, ipmi::intel::netFnGeneral, 1195 ipmi::intel::general::cmdGetSmSignal, 1196 ipmi::Privilege::Admin, ipmi::appMTMGetSignal); 1197 1198 ipmi::registerHandler(ipmi::prioOemBase, ipmi::intel::netFnGeneral, 1199 ipmi::intel::general::cmdSetSmSignal, 1200 ipmi::Privilege::Admin, ipmi::appMTMSetSignal); 1201 1202 ipmi::registerHandler(ipmi::prioOemBase, ipmi::intel::netFnGeneral, 1203 ipmi::intel::general::cmdMtmKeepAlive, 1204 ipmi::Privilege::Admin, ipmi::mtmKeepAlive); 1205 1206 ipmi::registerHandler(ipmi::prioOemBase, ipmi::intel::netFnGeneral, 1207 ipmi::intel::general::cmdSetManufacturingData, 1208 ipmi::Privilege::Admin, ipmi::setManufacturingData); 1209 1210 ipmi::registerHandler(ipmi::prioOemBase, ipmi::intel::netFnGeneral, 1211 ipmi::intel::general::cmdGetManufacturingData, 1212 ipmi::Privilege::Admin, ipmi::getManufacturingData); 1213 1214 ipmi::registerHandler(ipmi::prioOemBase, ipmi::intel::netFnGeneral, 1215 ipmi::intel::general::cmdSetFITcLayout, 1216 ipmi::Privilege::Admin, ipmi::setFITcLayout); 1217 1218 ipmi::registerHandler(ipmi::prioOemBase, ipmi::intel::netFnGeneral, 1219 ipmi::intel::general::cmdMTMBMCFeatureControl, 1220 ipmi::Privilege::Admin, ipmi::mtmBMCFeatureControl); 1221 1222 ipmi::registerHandler(ipmi::prioOemBase, ipmi::intel::netFnApp, 1223 ipmi::intel::general::cmdSlotI2CMasterWriteRead, 1224 ipmi::Privilege::Admin, 1225 ipmi::appSlotI2CMasterWriteRead); 1226 1227 ipmi::registerHandler(ipmi::prioOemBase, ipmi::intel::netFnPlatform, 1228 ipmi::intel::platform::cmdClearCMOS, 1229 ipmi::Privilege::Admin, ipmi::clearCMOS); 1230 1231 ipmi::registerFilter(ipmi::prioOemBase, 1232 [](ipmi::message::Request::ptr request) { 1233 return ipmi::mfgFilterMessage(request); 1234 }); 1235 } 1236