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 #include <phosphor-logging/lg2.hpp> 25 #include <types.hpp> 26 27 #include <charconv> 28 #include <filesystem> 29 #include <fstream> 30 31 namespace ipmi 32 { 33 34 Manufacturing mtm; 35 36 static auto revertTimeOut = 37 std::chrono::duration_cast<std::chrono::microseconds>( 38 std::chrono::seconds(60)); // 1 minute timeout 39 40 static constexpr uint8_t bbRiserMux = 0; 41 static constexpr uint8_t leftRiserMux = 1; 42 static constexpr uint8_t rightRiserMux = 2; 43 static constexpr uint8_t pcieMux = 3; 44 static constexpr uint8_t hsbpMux = 4; 45 46 static constexpr uint8_t slotAddressTypeBus = 0; 47 static constexpr uint8_t slotAddressTypeUniqueid = 1; 48 static constexpr uint8_t slotI2CMaxReadSize = 35; 49 50 static constexpr const char* callbackMgrService = 51 "xyz.openbmc_project.CallbackManager"; 52 static constexpr const char* callbackMgrIntf = 53 "xyz.openbmc_project.CallbackManager"; 54 static constexpr const char* callbackMgrObjPath = 55 "/xyz/openbmc_project/CallbackManager"; 56 static constexpr const char* retriggerLedUpdate = "RetriggerLEDUpdate"; 57 58 const static constexpr char* systemDService = "org.freedesktop.systemd1"; 59 const static constexpr char* systemDObjPath = "/org/freedesktop/systemd1"; 60 const static constexpr char* systemDMgrIntf = 61 "org.freedesktop.systemd1.Manager"; 62 const static constexpr char* pidControlService = "phosphor-pid-control.service"; 63 64 static inline Cc resetMtmTimer(ipmi::Context::ptr ctx) 65 { 66 boost::system::error_code ec; 67 ctx->bus->yield_method_call<>(ctx->yield, ec, specialModeService, 68 specialModeObjPath, specialModeIntf, 69 "ResetTimer"); 70 if (ec) 71 { 72 lg2::error("Failed to reset the manufacturing mode timer"); 73 return ccUnspecifiedError; 74 } 75 return ccSuccess; 76 } 77 78 int getGpioPathForSmSignal(const SmSignalGet signal, std::string& path) 79 { 80 switch (signal) 81 { 82 case SmSignalGet::smPowerButton: 83 path = "/xyz/openbmc_project/chassis/buttons/power"; 84 break; 85 case SmSignalGet::smResetButton: 86 path = "/xyz/openbmc_project/chassis/buttons/reset"; 87 break; 88 case SmSignalGet::smNMIButton: 89 path = "/xyz/openbmc_project/chassis/buttons/nmi"; 90 break; 91 case SmSignalGet::smIdentifyButton: 92 path = "/xyz/openbmc_project/chassis/buttons/id"; 93 break; 94 default: 95 return -1; 96 break; 97 } 98 return 0; 99 } 100 101 ipmi_ret_t ledStoreAndSet(SmSignalSet signal, const std::string& setState) 102 { 103 LedProperty* ledProp = mtm.findLedProperty(signal); 104 if (ledProp == nullptr) 105 { 106 return IPMI_CC_INVALID_FIELD_REQUEST; 107 } 108 109 std::string ledName = ledProp->getName(); 110 std::string ledPath = ledPathPrefix + ledName; 111 ipmi::Value presentState; 112 113 if (false == ledProp->getLock()) 114 { 115 if (mtm.getProperty(ledService, ledPath.c_str(), ledIntf, "State", 116 &presentState) != 0) 117 { 118 return IPMI_CC_UNSPECIFIED_ERROR; 119 } 120 ledProp->setPrevState(std::get<std::string>(presentState)); 121 ledProp->setLock(true); 122 if (signal == SmSignalSet::smPowerFaultLed || 123 signal == SmSignalSet::smSystemReadyLed) 124 { 125 mtm.revertLedCallback = true; 126 } 127 } 128 if (mtm.setProperty(ledService, ledPath, ledIntf, "State", 129 ledStateStr + setState) != 0) 130 { 131 return IPMI_CC_UNSPECIFIED_ERROR; 132 } 133 return IPMI_CC_OK; 134 } 135 136 ipmi_ret_t ledRevert(SmSignalSet signal) 137 { 138 LedProperty* ledProp = mtm.findLedProperty(signal); 139 if (ledProp == nullptr) 140 { 141 return IPMI_CC_INVALID_FIELD_REQUEST; 142 } 143 if (true == ledProp->getLock()) 144 { 145 ledProp->setLock(false); 146 if (signal == SmSignalSet::smPowerFaultLed || 147 signal == SmSignalSet::smSystemReadyLed) 148 { 149 try 150 { 151 ipmi::method_no_args::callDbusMethod( 152 *getSdBus(), callbackMgrService, callbackMgrObjPath, 153 callbackMgrIntf, retriggerLedUpdate); 154 } 155 catch (const sdbusplus::exception_t& e) 156 { 157 return IPMI_CC_UNSPECIFIED_ERROR; 158 } 159 mtm.revertLedCallback = false; 160 } 161 else 162 { 163 std::string ledName = ledProp->getName(); 164 std::string ledPath = ledPathPrefix + ledName; 165 if (mtm.setProperty(ledService, ledPath, ledIntf, "State", 166 ledProp->getPrevState()) != 0) 167 { 168 return IPMI_CC_UNSPECIFIED_ERROR; 169 } 170 } 171 } 172 return IPMI_CC_OK; 173 } 174 175 void Manufacturing::initData() 176 { 177 ledPropertyList.push_back( 178 LedProperty(SmSignalSet::smPowerFaultLed, "status_amber")); 179 ledPropertyList.push_back( 180 LedProperty(SmSignalSet::smSystemReadyLed, "status_green")); 181 ledPropertyList.push_back( 182 LedProperty(SmSignalSet::smIdentifyLed, "identify")); 183 } 184 185 void Manufacturing::revertTimerHandler() 186 { 187 #ifdef BMC_VALIDATION_UNSECURE_FEATURE 188 if (mtm.getMfgMode() == SpecialMode::valUnsecure) 189 { 190 // Don't revert the behaviour for validation unsecure mode. 191 return; 192 } 193 #endif 194 if (revertFanPWM) 195 { 196 revertFanPWM = false; 197 disablePidControlService(false); 198 } 199 200 if (mtmTestBeepFd != -1) 201 { 202 ::close(mtmTestBeepFd); 203 mtmTestBeepFd = -1; 204 } 205 206 for (const auto& ledProperty : ledPropertyList) 207 { 208 const std::string& ledName = ledProperty.getName(); 209 if (ledName == "identify" && mtm.getMfgMode() == SpecialMode::mfg) 210 { 211 // Don't revert the behaviour for manufacturing mode 212 continue; 213 } 214 ledRevert(ledProperty.getSignal()); 215 } 216 } 217 218 Manufacturing::Manufacturing() : 219 revertTimer([&](void) { revertTimerHandler(); }) 220 { 221 initData(); 222 } 223 224 int8_t Manufacturing::getProperty( 225 const std::string& service, const std::string& path, 226 const std::string& interface, const std::string& propertyName, 227 ipmi::Value* reply) 228 { 229 try 230 { 231 *reply = ipmi::getDbusProperty(*getSdBus(), service, path, interface, 232 propertyName); 233 } 234 catch (const sdbusplus::exception_t& e) 235 { 236 lg2::info("ERROR: getProperty"); 237 return -1; 238 } 239 240 return 0; 241 } 242 243 int8_t Manufacturing::setProperty( 244 const std::string& service, const std::string& path, 245 const std::string& interface, const std::string& propertyName, 246 ipmi::Value value) 247 { 248 try 249 { 250 ipmi::setDbusProperty(*getSdBus(), service, path, interface, 251 propertyName, value); 252 } 253 catch (const sdbusplus::exception_t& e) 254 { 255 lg2::info("ERROR: setProperty"); 256 return -1; 257 } 258 259 return 0; 260 } 261 262 int8_t Manufacturing::disablePidControlService(const bool disable) 263 { 264 try 265 { 266 auto dbus = getSdBus(); 267 auto method = dbus->new_method_call(systemDService, systemDObjPath, 268 systemDMgrIntf, 269 disable ? "StopUnit" : "StartUnit"); 270 method.append(pidControlService, "replace"); 271 auto reply = dbus->call(method); 272 } 273 catch (const sdbusplus::exception_t& e) 274 { 275 lg2::info("ERROR: phosphor-pid-control service start or stop failed"); 276 return -1; 277 } 278 return 0; 279 } 280 281 static bool findPwmName(ipmi::Context::ptr& ctx, uint8_t instance, 282 std::string& pwmName) 283 { 284 boost::system::error_code ec{}; 285 ObjectValueTree obj; 286 287 // GetAll the objects under service FruDevice 288 ec = getManagedObjects(ctx, "xyz.openbmc_project.EntityManager", 289 "/xyz/openbmc_project/inventory", obj); 290 if (ec) 291 { 292 lg2::error("GetMangagedObjects failed", "ERROR", ec.message().c_str()); 293 return false; 294 } 295 for (const auto& [path, objData] : obj) 296 { 297 for (const auto& [intf, propMap] : objData) 298 { 299 // Currently, these are the three different fan types supported. 300 if (intf == "xyz.openbmc_project.Configuration.AspeedFan" || 301 intf == "xyz.openbmc_project.Configuration.I2CFan" || 302 intf == "xyz.openbmc_project.Configuration.NuvotonFan") 303 { 304 std::string fanPath = "/Fan_"; 305 306 fanPath += std::to_string(instance); 307 std::string objPath = path.str; 308 objPath = objPath.substr(objPath.find_last_of("/")); 309 if (objPath != fanPath) 310 { 311 continue; 312 } 313 auto connector = objData.find(intf + std::string(".Connector")); 314 if (connector == objData.end()) 315 { 316 return false; 317 } 318 auto findPwmName = connector->second.find("PwmName"); 319 if (findPwmName != connector->second.end()) 320 { 321 auto fanPwmName = 322 std::get_if<std::string>(&findPwmName->second); 323 if (!fanPwmName) 324 { 325 lg2::error("PwmName parse ERROR."); 326 return false; 327 } 328 pwmName = *fanPwmName; 329 return true; 330 } 331 auto findPwm = connector->second.find("Pwm"); 332 if (findPwm == connector->second.end()) 333 { 334 return false; 335 } 336 auto fanPwm = std::get_if<uint64_t>(&findPwm->second); 337 if (!fanPwm) 338 { 339 return false; 340 } 341 pwmName = "Pwm_" + std::to_string(*fanPwm + 1); 342 return true; 343 } 344 } 345 } 346 return false; 347 } 348 ipmi::RspType<uint8_t, // Signal value 349 std::optional<uint16_t> // Fan tach value 350 > 351 appMTMGetSignal(ipmi::Context::ptr ctx, uint8_t signalTypeByte, 352 uint8_t instance, uint8_t actionByte) 353 { 354 // mfg filter logic is used to allow MTM get signal command only in 355 // manfacturing mode. 356 357 SmSignalGet signalType = static_cast<SmSignalGet>(signalTypeByte); 358 SmActionGet action = static_cast<SmActionGet>(actionByte); 359 360 switch (signalType) 361 { 362 case SmSignalGet::smChassisIntrusion: 363 { 364 ipmi::Value reply; 365 if (mtm.getProperty(intrusionService, intrusionPath, intrusionIntf, 366 "Status", &reply) < 0) 367 { 368 return ipmi::responseInvalidFieldRequest(); 369 } 370 std::string* intrusionStatus = std::get_if<std::string>(&reply); 371 if (!intrusionStatus) 372 { 373 return ipmi::responseUnspecifiedError(); 374 } 375 376 uint8_t status = 0; 377 if (!intrusionStatus->compare("Normal")) 378 { 379 status = static_cast<uint8_t>(IntrusionStatus::normal); 380 } 381 else if (!intrusionStatus->compare("HardwareIntrusion")) 382 { 383 status = 384 static_cast<uint8_t>(IntrusionStatus::hardwareIntrusion); 385 } 386 else if (!intrusionStatus->compare("TamperingDetected")) 387 { 388 status = 389 static_cast<uint8_t>(IntrusionStatus::tamperingDetected); 390 } 391 else 392 { 393 return ipmi::responseUnspecifiedError(); 394 } 395 return ipmi::responseSuccess(status, std::nullopt); 396 } 397 case SmSignalGet::smFanPwmGet: 398 { 399 ipmi::Value reply; 400 std::string pwmName, fullPath; 401 if (!findPwmName(ctx, instance + 1, pwmName)) 402 { 403 // The default PWM name is Pwm_# 404 pwmName = "Pwm_" + std::to_string(instance + 1); 405 } 406 fullPath = fanPwmPath + pwmName; 407 if (mtm.getProperty(fanService, fullPath, fanIntf, "Value", 408 &reply) < 0) 409 { 410 return ipmi::responseInvalidFieldRequest(); 411 } 412 double* doubleVal = std::get_if<double>(&reply); 413 if (doubleVal == nullptr) 414 { 415 return ipmi::responseUnspecifiedError(); 416 } 417 uint8_t sensorVal = std::round(*doubleVal); 418 resetMtmTimer(ctx); 419 return ipmi::responseSuccess(sensorVal, std::nullopt); 420 } 421 break; 422 case SmSignalGet::smFanTachometerGet: 423 { 424 boost::system::error_code ec; 425 using objFlatMap = boost::container::flat_map< 426 std::string, boost::container::flat_map< 427 std::string, std::vector<std::string>>>; 428 429 auto flatMap = ctx->bus->yield_method_call<objFlatMap>( 430 ctx->yield, ec, "xyz.openbmc_project.ObjectMapper", 431 "/xyz/openbmc_project/object_mapper", 432 "xyz.openbmc_project.ObjectMapper", "GetSubTree", 433 fanTachBasePath, 0, std::array<const char*, 1>{fanIntf}); 434 if (ec) 435 { 436 lg2::error("Failed to query fan tach sub tree objects"); 437 return ipmi::responseUnspecifiedError(); 438 } 439 if (instance >= flatMap.size()) 440 { 441 return ipmi::responseInvalidFieldRequest(); 442 } 443 auto itr = flatMap.nth(instance); 444 ipmi::Value reply; 445 if (mtm.getProperty(fanService, itr->first, fanIntf, "Value", 446 &reply) < 0) 447 { 448 return ipmi::responseInvalidFieldRequest(); 449 } 450 451 double* doubleVal = std::get_if<double>(&reply); 452 if (doubleVal == nullptr) 453 { 454 return ipmi::responseUnspecifiedError(); 455 } 456 uint8_t sensorVal = FAN_PRESENT | FAN_SENSOR_PRESENT; 457 std::optional<uint16_t> fanTach = std::round(*doubleVal); 458 459 resetMtmTimer(ctx); 460 return ipmi::responseSuccess(sensorVal, fanTach); 461 } 462 break; 463 case SmSignalGet::smIdentifyButton: 464 { 465 if (action == SmActionGet::revert || action == SmActionGet::ignore) 466 { 467 // ButtonMasked property is not supported for ID button as it is 468 // unnecessary. Hence if requested for revert / ignore, override 469 // it to sample action to make tools happy. 470 action = SmActionGet::sample; 471 } 472 [[fallthrough]]; 473 } 474 case SmSignalGet::smResetButton: 475 case SmSignalGet::smPowerButton: 476 case SmSignalGet::smNMIButton: 477 { 478 std::string path; 479 if (getGpioPathForSmSignal(signalType, path) < 0) 480 { 481 return ipmi::responseInvalidFieldRequest(); 482 } 483 484 switch (action) 485 { 486 case SmActionGet::sample: 487 lg2::info("case SmActionGet::sample"); 488 break; 489 case SmActionGet::ignore: 490 { 491 lg2::info("case SmActionGet::ignore"); 492 if (mtm.setProperty(buttonService, path, buttonIntf, 493 "ButtonMasked", true) < 0) 494 { 495 return ipmi::responseUnspecifiedError(); 496 } 497 } 498 break; 499 case SmActionGet::revert: 500 { 501 lg2::info("case SmActionGet::revert"); 502 if (mtm.setProperty(buttonService, path, buttonIntf, 503 "ButtonMasked", false) < 0) 504 { 505 return ipmi::responseUnspecifiedError(); 506 } 507 } 508 break; 509 510 default: 511 return ipmi::responseInvalidFieldRequest(); 512 break; 513 } 514 515 ipmi::Value reply; 516 if (mtm.getProperty(buttonService, path, buttonIntf, 517 "ButtonPressed", &reply) < 0) 518 { 519 return ipmi::responseUnspecifiedError(); 520 } 521 bool* valPtr = std::get_if<bool>(&reply); 522 if (valPtr == nullptr) 523 { 524 return ipmi::responseUnspecifiedError(); 525 } 526 resetMtmTimer(ctx); 527 uint8_t sensorVal = *valPtr; 528 return ipmi::responseSuccess(sensorVal, std::nullopt); 529 } 530 break; 531 case SmSignalGet::smNcsiDiag: 532 { 533 constexpr const char* netBasePath = "/sys/class/net/eth"; 534 constexpr const char* carrierSuffix = "/carrier"; 535 std::ifstream netIfs( 536 netBasePath + std::to_string(instance) + carrierSuffix); 537 if (!netIfs.good()) 538 { 539 return ipmi::responseInvalidFieldRequest(); 540 } 541 std::string carrier; 542 netIfs >> carrier; 543 resetMtmTimer(ctx); 544 return ipmi::responseSuccess( 545 static_cast<uint8_t>(std::stoi(carrier)), std::nullopt); 546 } 547 break; 548 default: 549 return ipmi::responseInvalidFieldRequest(); 550 break; 551 } 552 } 553 554 ipmi::RspType<> appMTMSetSignal(ipmi::Context::ptr ctx, uint8_t signalTypeByte, 555 uint8_t instance, uint8_t actionByte, 556 std::optional<uint8_t> pwmSpeed) 557 { 558 // mfg filter logic is used to allow MTM set signal command only in 559 // manfacturing mode. 560 561 SmSignalSet signalType = static_cast<SmSignalSet>(signalTypeByte); 562 SmActionSet action = static_cast<SmActionSet>(actionByte); 563 Cc retCode = ccSuccess; 564 int8_t ret = 0; 565 566 switch (signalType) 567 { 568 case SmSignalSet::smPowerFaultLed: 569 case SmSignalSet::smSystemReadyLed: 570 case SmSignalSet::smIdentifyLed: 571 switch (action) 572 { 573 case SmActionSet::forceDeasserted: 574 { 575 lg2::info("case SmActionSet::forceDeasserted"); 576 577 retCode = ledStoreAndSet(signalType, std::string("Off")); 578 if (retCode != ccSuccess) 579 { 580 return ipmi::response(retCode); 581 } 582 mtm.revertTimer.start(revertTimeOut); 583 } 584 break; 585 case SmActionSet::forceAsserted: 586 { 587 lg2::info("case SmActionSet::forceAsserted"); 588 589 retCode = ledStoreAndSet(signalType, std::string("On")); 590 if (retCode != ccSuccess) 591 { 592 return ipmi::response(retCode); 593 } 594 mtm.revertTimer.start(revertTimeOut); 595 if (SmSignalSet::smPowerFaultLed == signalType) 596 { 597 // Deassert "system ready" 598 retCode = ledStoreAndSet(SmSignalSet::smSystemReadyLed, 599 std::string("Off")); 600 } 601 else if (SmSignalSet::smSystemReadyLed == signalType) 602 { 603 // Deassert "fault led" 604 retCode = ledStoreAndSet(SmSignalSet::smPowerFaultLed, 605 std::string("Off")); 606 } 607 } 608 break; 609 case SmActionSet::revert: 610 { 611 lg2::info("case SmActionSet::revert"); 612 retCode = ledRevert(signalType); 613 } 614 break; 615 default: 616 { 617 return ipmi::responseInvalidFieldRequest(); 618 } 619 } 620 break; 621 case SmSignalSet::smFanPowerSpeed: 622 { 623 if ((action == SmActionSet::forceAsserted) && (!pwmSpeed)) 624 { 625 return ipmi::responseReqDataLenInvalid(); 626 } 627 628 if ((action == SmActionSet::forceAsserted) && (*pwmSpeed > 100)) 629 { 630 return ipmi::responseInvalidFieldRequest(); 631 } 632 633 uint8_t pwmValue = 0; 634 switch (action) 635 { 636 case SmActionSet::revert: 637 { 638 if (mtm.revertFanPWM) 639 { 640 ret = mtm.disablePidControlService(false); 641 if (ret < 0) 642 { 643 return ipmi::responseUnspecifiedError(); 644 } 645 mtm.revertFanPWM = false; 646 } 647 } 648 break; 649 case SmActionSet::forceAsserted: 650 { 651 pwmValue = *pwmSpeed; 652 } // fall-through 653 case SmActionSet::forceDeasserted: 654 { 655 if (!mtm.revertFanPWM) 656 { 657 ret = mtm.disablePidControlService(true); 658 if (ret < 0) 659 { 660 return ipmi::responseUnspecifiedError(); 661 } 662 mtm.revertFanPWM = true; 663 } 664 mtm.revertTimer.start(revertTimeOut); 665 std::string pwmName, fanPwmInstancePath; 666 if (!findPwmName(ctx, instance + 1, pwmName)) 667 { 668 pwmName = "Pwm_" + std::to_string(instance + 1); 669 } 670 fanPwmInstancePath = fanPwmPath + pwmName; 671 ret = 672 mtm.setProperty(fanService, fanPwmInstancePath, fanIntf, 673 "Value", static_cast<double>(pwmValue)); 674 if (ret < 0) 675 { 676 return ipmi::responseUnspecifiedError(); 677 } 678 } 679 break; 680 default: 681 { 682 return ipmi::responseInvalidFieldRequest(); 683 } 684 } 685 } 686 break; 687 case SmSignalSet::smSpeaker: 688 { 689 lg2::info("Performing Speaker SmActionSet", "ACTION", lg2::dec, 690 static_cast<uint8_t>(action)); 691 switch (action) 692 { 693 case SmActionSet::forceAsserted: 694 { 695 char beepDevName[] = "/dev/input/event0"; 696 if (mtm.mtmTestBeepFd != -1) 697 { 698 lg2::info("mtm beep device is opened already!"); 699 // returning success as already beep is in progress 700 return ipmi::response(retCode); 701 } 702 703 if ((mtm.mtmTestBeepFd = 704 ::open(beepDevName, O_RDWR | O_CLOEXEC)) < 0) 705 { 706 lg2::error("Failed to open input device"); 707 return ipmi::responseUnspecifiedError(); 708 } 709 710 struct input_event event; 711 event.type = EV_SND; 712 event.code = SND_TONE; 713 event.value = 2000; 714 715 if (::write(mtm.mtmTestBeepFd, &event, 716 sizeof(struct input_event)) != 717 sizeof(struct input_event)) 718 { 719 lg2::error("Failed to write a tone sound event"); 720 ::close(mtm.mtmTestBeepFd); 721 mtm.mtmTestBeepFd = -1; 722 return ipmi::responseUnspecifiedError(); 723 } 724 mtm.revertTimer.start(revertTimeOut); 725 } 726 break; 727 case SmActionSet::revert: 728 case SmActionSet::forceDeasserted: 729 { 730 if (mtm.mtmTestBeepFd != -1) 731 { 732 ::close(mtm.mtmTestBeepFd); 733 mtm.mtmTestBeepFd = -1; 734 } 735 } 736 break; 737 default: 738 { 739 return ipmi::responseInvalidFieldRequest(); 740 } 741 } 742 } 743 break; 744 case SmSignalSet::smDiskFaultLed: 745 { 746 boost::system::error_code ec; 747 using objPaths = std::vector<std::string>; 748 std::string driveBasePath = 749 "/xyz/openbmc_project/inventory/item/drive/"; 750 static constexpr const char* driveLedIntf = 751 "xyz.openbmc_project.Led.Group"; 752 static constexpr const char* hsbpService = 753 "xyz.openbmc_project.HsbpManager"; 754 755 auto driveList = ctx->bus->yield_method_call<objPaths>( 756 ctx->yield, ec, "xyz.openbmc_project.ObjectMapper", 757 "/xyz/openbmc_project/object_mapper", 758 "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", 759 driveBasePath, 0, std::array<const char*, 1>{driveLedIntf}); 760 if (ec) 761 { 762 lg2::error("Failed to query HSBP drive sub tree objects"); 763 return ipmi::responseUnspecifiedError(); 764 } 765 std::string driveObjPath = 766 driveBasePath + "Drive_" + std::to_string(instance + 1); 767 if (std::find(driveList.begin(), driveList.end(), driveObjPath) == 768 driveList.end()) 769 { 770 return ipmi::responseInvalidFieldRequest(); 771 } 772 bool driveLedState = false; 773 switch (action) 774 { 775 case SmActionSet::forceAsserted: 776 { 777 driveLedState = true; 778 } 779 break; 780 case SmActionSet::revert: 781 { 782 driveLedState = false; 783 } 784 break; 785 case SmActionSet::forceDeasserted: 786 { 787 driveLedState = false; 788 } 789 break; 790 default: 791 { 792 return ipmi::responseInvalidFieldRequest(); 793 } 794 } 795 ret = mtm.setProperty(hsbpService, driveObjPath, driveLedIntf, 796 "Asserted", driveLedState); 797 if (ret < 0) 798 { 799 return ipmi::responseUnspecifiedError(); 800 } 801 } 802 break; 803 default: 804 { 805 return ipmi::responseInvalidFieldRequest(); 806 } 807 } 808 if (retCode == ccSuccess) 809 { 810 resetMtmTimer(ctx); 811 } 812 return ipmi::response(retCode); 813 } 814 815 ipmi::RspType<> mtmKeepAlive(ipmi::Context::ptr ctx, uint8_t reserved, 816 const std::array<char, 5>& intentionalSignature) 817 { 818 // mfg filter logic is used to allow MTM keep alive command only in 819 // manfacturing mode 820 821 constexpr std::array<char, 5> signatureOk = {'I', 'N', 'T', 'E', 'L'}; 822 if (intentionalSignature != signatureOk || reserved != 0) 823 { 824 return ipmi::responseInvalidFieldRequest(); 825 } 826 return ipmi::response(resetMtmTimer(ctx)); 827 } 828 829 static constexpr unsigned int makeCmdKey(unsigned int netFn, unsigned int cmd) 830 { 831 return (netFn << 8) | cmd; 832 } 833 834 ipmi::Cc mfgFilterMessage(ipmi::message::Request::ptr request) 835 { 836 // Restricted commands, must be executed only in Manufacturing mode 837 switch (makeCmdKey(request->ctx->netFn, request->ctx->cmd)) 838 { 839 // i2c controller write read command needs additional checking 840 case makeCmdKey(ipmi::netFnApp, ipmi::app::cmdMasterWriteRead): 841 if (request->payload.size() > 4) 842 { 843 // Allow write data count > 1 only in Special mode 844 if (mtm.getMfgMode() == SpecialMode::none) 845 { 846 return ipmi::ccInsufficientPrivilege; 847 } 848 } 849 return ipmi::ccSuccess; 850 case makeCmdKey(ipmi::netFnOemOne, 851 ipmi::intel::general::cmdGetSmSignal): 852 case makeCmdKey(ipmi::netFnOemOne, 853 ipmi::intel::general::cmdSetSmSignal): 854 case makeCmdKey(ipmi::netFnOemOne, 855 ipmi::intel::general::cmdMtmKeepAlive): 856 case makeCmdKey(ipmi::netFnOemOne, 857 ipmi::intel::general::cmdSetManufacturingData): 858 case makeCmdKey(ipmi::netFnOemOne, 859 ipmi::intel::general::cmdGetManufacturingData): 860 case makeCmdKey(ipmi::intel::netFnGeneral, 861 ipmi::intel::general::cmdSetFITcLayout): 862 case makeCmdKey(ipmi::netFnOemOne, 863 ipmi::intel::general::cmdMTMBMCFeatureControl): 864 case makeCmdKey(ipmi::netFnStorage, ipmi::storage::cmdWriteFruData): 865 case makeCmdKey(ipmi::netFnOemTwo, ipmi::intel::platform::cmdClearCMOS): 866 867 // Check for Special mode 868 if (mtm.getMfgMode() == SpecialMode::none) 869 { 870 return ipmi::ccInvalidCommand; 871 } 872 return ipmi::ccSuccess; 873 case makeCmdKey(ipmi::netFnStorage, ipmi::storage::cmdDeleteSelEntry): 874 { 875 return ipmi::ccInvalidCommand; 876 } 877 } 878 return ipmi::ccSuccess; 879 } 880 881 static constexpr uint8_t maxEthSize = 6; 882 static constexpr uint8_t maxSupportedEth = 3; 883 static constexpr const char* factoryEthAddrBaseFileName = 884 "/var/sofs/factory-settings/network/mac/eth"; 885 886 bool findFruDevice(ipmi::Context::ptr& ctx, uint64_t& macOffset, 887 uint64_t& busNum, uint64_t& address) 888 { 889 boost::system::error_code ec{}; 890 ObjectValueTree obj; 891 892 // GetAll the objects under service FruDevice 893 ec = getManagedObjects(ctx, "xyz.openbmc_project.EntityManager", 894 "/xyz/openbmc_project/inventory", obj); 895 if (ec) 896 { 897 lg2::error("GetManagedObjects failed", "ERROR", ec.message().c_str()); 898 return false; 899 } 900 901 for (const auto& [path, fru] : obj) 902 { 903 for (const auto& [intf, propMap] : fru) 904 { 905 if (intf == "xyz.openbmc_project.Inventory.Item.Board.Motherboard") 906 { 907 auto findBus = propMap.find("FruBus"); 908 auto findAddress = propMap.find("FruAddress"); 909 auto findMacOffset = propMap.find("MacOffset"); 910 if (findBus == propMap.end() || findAddress == propMap.end() || 911 findMacOffset == propMap.end()) 912 { 913 continue; 914 } 915 916 auto fruBus = std::get_if<uint64_t>(&findBus->second); 917 auto fruAddress = std::get_if<uint64_t>(&findAddress->second); 918 auto macFruOffset = 919 std::get_if<uint64_t>(&findMacOffset->second); 920 if (!fruBus || !fruAddress || !macFruOffset) 921 { 922 lg2::info("ERROR: MotherBoard FRU config data type " 923 "invalid, not used"); 924 return false; 925 } 926 busNum = *fruBus; 927 address = *fruAddress; 928 macOffset = *macFruOffset; 929 return true; 930 } 931 } 932 } 933 return false; 934 } 935 936 static constexpr uint64_t fruEnd = 0xff; 937 // write rolls over within current page, need to keep mac within a page 938 static constexpr uint64_t fruPageSize = 0x8; 939 // MAC record struct: HEADER, MAC DATA, CheckSum 940 static constexpr uint64_t macRecordSize = maxEthSize + 2; 941 static_assert(fruPageSize >= macRecordSize, 942 "macRecordSize greater than eeprom page size"); 943 static constexpr uint8_t macHeader = 0x40; 944 // Calculate new checksum for fru info area 945 static uint8_t calculateChecksum(std::vector<uint8_t>::const_iterator iter, 946 std::vector<uint8_t>::const_iterator end) 947 { 948 constexpr int checksumMod = 256; 949 uint8_t sum = std::accumulate(iter, end, static_cast<uint8_t>(0)); 950 return (checksumMod - sum) % checksumMod; 951 } 952 953 bool readMacFromFru(ipmi::Context::ptr ctx, uint8_t macIndex, 954 std::array<uint8_t, maxEthSize>& ethData) 955 { 956 uint64_t macOffset = fruEnd; 957 uint64_t fruBus = 0; 958 uint64_t fruAddress = 0; 959 960 if (findFruDevice(ctx, macOffset, fruBus, fruAddress)) 961 { 962 lg2::info("Found mac fru", "BUS", lg2::dec, 963 static_cast<uint8_t>(fruBus), "ADDRESS", lg2::dec, 964 static_cast<uint8_t>(fruAddress)); 965 966 if (macOffset % fruPageSize) 967 { 968 macOffset = (macOffset / fruPageSize + 1) * fruPageSize; 969 } 970 macOffset += macIndex * fruPageSize; 971 if ((macOffset + macRecordSize) > fruEnd) 972 { 973 lg2::error("ERROR: read fru mac failed, offset invalid"); 974 return false; 975 } 976 std::vector<uint8_t> writeData; 977 writeData.push_back(static_cast<uint8_t>(macOffset)); 978 std::vector<uint8_t> readBuf(macRecordSize); 979 std::string i2cBus = "/dev/i2c-" + std::to_string(fruBus); 980 ipmi::Cc retI2C = 981 ipmi::i2cWriteRead(i2cBus, fruAddress, writeData, readBuf); 982 if (retI2C == ipmi::ccSuccess) 983 { 984 uint8_t cs = calculateChecksum(readBuf.cbegin(), readBuf.cend()); 985 if (cs == 0) 986 { 987 std::copy(++readBuf.begin(), --readBuf.end(), ethData.data()); 988 return true; 989 } 990 } 991 } 992 return false; 993 } 994 995 ipmi::Cc writeMacToFru(ipmi::Context::ptr ctx, uint8_t macIndex, 996 std::array<uint8_t, maxEthSize>& ethData) 997 { 998 uint64_t macOffset = fruEnd; 999 uint64_t fruBus = 0; 1000 uint64_t fruAddress = 0; 1001 1002 if (findFruDevice(ctx, macOffset, fruBus, fruAddress)) 1003 { 1004 lg2::info("Found mac fru", "BUS", lg2::dec, 1005 static_cast<uint8_t>(fruBus), "ADDRESS", lg2::dec, 1006 static_cast<uint8_t>(fruAddress)); 1007 1008 if (macOffset % fruPageSize) 1009 { 1010 macOffset = (macOffset / fruPageSize + 1) * fruPageSize; 1011 } 1012 macOffset += macIndex * fruPageSize; 1013 if ((macOffset + macRecordSize) > fruEnd) 1014 { 1015 lg2::error("ERROR: write mac fru failed, offset invalid."); 1016 return ipmi::ccParmOutOfRange; 1017 } 1018 std::vector<uint8_t> writeData; 1019 writeData.reserve(macRecordSize + 1); // include start location 1020 writeData.push_back(static_cast<uint8_t>(macOffset)); 1021 writeData.push_back(macHeader); 1022 std::for_each(ethData.cbegin(), ethData.cend(), 1023 [&](uint8_t i) { writeData.push_back(i); }); 1024 uint8_t macCheckSum = 1025 calculateChecksum(++writeData.cbegin(), writeData.cend()); 1026 writeData.push_back(macCheckSum); 1027 1028 std::string i2cBus = "/dev/i2c-" + std::to_string(fruBus); 1029 std::vector<uint8_t> readBuf; 1030 ipmi::Cc ret = 1031 ipmi::i2cWriteRead(i2cBus, fruAddress, writeData, readBuf); 1032 1033 // prepare for read to detect chip is write protected 1034 writeData.resize(1); 1035 readBuf.resize(maxEthSize + 1); // include macHeader 1036 1037 switch (ret) 1038 { 1039 case ipmi::ccSuccess: 1040 // Wait for internal write cycle to complete 1041 // example: ATMEL 24c0x chip has Twr spec as 5ms 1042 1043 // Ideally we want yield wait, but currently following code 1044 // crash with "thread not supported" 1045 // boost::asio::deadline_timer timer( 1046 // boost::asio::get_associated_executor(ctx->yield), 1047 // boost::posix_time::seconds(1)); 1048 // timer.async_wait(ctx->yield); 1049 // use usleep as temp WA 1050 usleep(5000); 1051 if (ipmi::i2cWriteRead(i2cBus, fruAddress, writeData, 1052 readBuf) == ipmi::ccSuccess) 1053 { 1054 if (std::equal(ethData.begin(), ethData.end(), 1055 ++readBuf.begin())) // skip macHeader 1056 { 1057 return ipmi::ccSuccess; 1058 } 1059 lg2::info("INFO: write mac fru verify failed, fru may be " 1060 "write protected."); 1061 } 1062 return ipmi::ccCommandNotAvailable; 1063 default: 1064 if (ipmi::i2cWriteRead(i2cBus, fruAddress, writeData, 1065 readBuf) == ipmi::ccSuccess) 1066 { 1067 lg2::info("INFO: write mac fru failed, but successfully " 1068 "read from fru, fru may be write protected."); 1069 return ipmi::ccCommandNotAvailable; 1070 } 1071 else // assume failure is due to no eeprom on board 1072 { 1073 lg2::error("ERROR: write mac fru failed, assume no eeprom " 1074 "is available."); 1075 } 1076 break; 1077 } 1078 } 1079 // no FRU eeprom found 1080 return ipmi::ccDestinationUnavailable; 1081 } 1082 1083 ipmi::RspType<> setManufacturingData(ipmi::Context::ptr ctx, uint8_t dataType, 1084 std::array<uint8_t, maxEthSize> ethData) 1085 { 1086 // mfg filter logic will restrict this command executing only in mfg 1087 // mode. 1088 if (dataType >= maxSupportedEth) 1089 { 1090 return ipmi::responseParmOutOfRange(); 1091 } 1092 1093 ipmi::Cc ret = writeMacToFru(ctx, dataType, ethData); 1094 if (ret != ipmi::ccDestinationUnavailable) 1095 { 1096 resetMtmTimer(ctx); 1097 return response(ret); 1098 } 1099 1100 constexpr uint8_t ethAddrStrSize = 1101 19; // XX:XX:XX:XX:XX:XX + \n + null termination; 1102 std::vector<uint8_t> buff(ethAddrStrSize); 1103 std::snprintf(reinterpret_cast<char*>(buff.data()), ethAddrStrSize, 1104 "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx\n", ethData.at(0), 1105 ethData.at(1), ethData.at(2), ethData.at(3), ethData.at(4), 1106 ethData.at(5)); 1107 std::ofstream oEthFile( 1108 factoryEthAddrBaseFileName + std::to_string(dataType), 1109 std::ofstream::out); 1110 if (!oEthFile.good()) 1111 { 1112 return ipmi::responseUnspecifiedError(); 1113 } 1114 1115 oEthFile << reinterpret_cast<char*>(buff.data()); 1116 oEthFile.flush(); 1117 oEthFile.close(); 1118 1119 resetMtmTimer(ctx); 1120 return ipmi::responseSuccess(); 1121 } 1122 1123 ipmi::RspType<uint8_t, std::array<uint8_t, maxEthSize>> 1124 getManufacturingData(ipmi::Context::ptr ctx, uint8_t dataType) 1125 { 1126 // mfg filter logic will restrict this command executing only in mfg 1127 // mode. 1128 if (dataType >= maxSupportedEth) 1129 { 1130 return ipmi::responseParmOutOfRange(); 1131 } 1132 std::array<uint8_t, maxEthSize> ethData{0}; 1133 constexpr uint8_t invalidData = 0; 1134 constexpr uint8_t validData = 1; 1135 1136 std::ifstream iEthFile( 1137 factoryEthAddrBaseFileName + std::to_string(dataType), 1138 std::ifstream::in); 1139 if (!iEthFile.good()) 1140 { 1141 if (readMacFromFru(ctx, dataType, ethData)) 1142 { 1143 resetMtmTimer(ctx); 1144 return ipmi::responseSuccess(validData, ethData); 1145 } 1146 return ipmi::responseSuccess(invalidData, ethData); 1147 } 1148 std::string ethStr; 1149 iEthFile >> ethStr; 1150 uint8_t* data = ethData.data(); 1151 std::sscanf(ethStr.c_str(), "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx", 1152 data, (data + 1), (data + 2), (data + 3), (data + 4), 1153 (data + 5)); 1154 1155 resetMtmTimer(ctx); 1156 return ipmi::responseSuccess(validData, ethData); 1157 } 1158 1159 /** @brief implements slot controller write read IPMI command which can be used 1160 * for low-level I2C/SMBus write, read or write-read access for PCIE slots 1161 * @param reserved - skip 3 bit 1162 * @param muxType - mux type 1163 * @param addressType - address type 1164 * @param bbSlotNum - baseboard slot number 1165 * @param riserSlotNum - riser slot number 1166 * @param reserved2 - skip 2 bit 1167 * @param targetAddr - target address 1168 * @param readCount - number of bytes to be read 1169 * @param writeData - data to be written 1170 * 1171 * @returns IPMI completion code plus response data 1172 */ 1173 1174 ipmi::RspType<std::vector<uint8_t>> appSlotI2CControllerWriteRead( 1175 uint3_t reserved, uint3_t muxType, uint2_t addressType, uint3_t bbSlotNum, 1176 uint3_t riserSlotNum, uint2_t reserved2, uint8_t targetAddr, 1177 uint8_t readCount, std::vector<uint8_t> writeData) 1178 { 1179 if (reserved || reserved2) 1180 { 1181 return ipmi::responseInvalidFieldRequest(); 1182 } 1183 const size_t writeCount = writeData.size(); 1184 std::string i2cBus; 1185 if (addressType == slotAddressTypeBus) 1186 { 1187 std::string path = "/dev/i2c-mux/"; 1188 if (muxType == bbRiserMux) 1189 { 1190 path += "Riser_" + std::to_string(static_cast<uint8_t>(bbSlotNum)) + 1191 "_Mux/Pcie_Slot_" + 1192 std::to_string(static_cast<uint8_t>(riserSlotNum)); 1193 } 1194 else if (muxType == leftRiserMux) 1195 { 1196 path += "Left_Riser_Mux/Slot_" + 1197 std::to_string(static_cast<uint8_t>(riserSlotNum)); 1198 } 1199 else if (muxType == rightRiserMux) 1200 { 1201 path += "Right_Riser_Mux/Slot_" + 1202 std::to_string(static_cast<uint8_t>(riserSlotNum)); 1203 } 1204 else if (muxType == pcieMux) 1205 { 1206 path += "PCIe_Mux/Slot_" + 1207 std::to_string(static_cast<uint8_t>(riserSlotNum)); 1208 } 1209 else if (muxType == hsbpMux) 1210 { 1211 path += "HSBP_Mux/Slot" + 1212 std::to_string(static_cast<uint8_t>(riserSlotNum)); 1213 } 1214 phosphor::logging::log<phosphor::logging::level::DEBUG>( 1215 ("Path is: " + path).c_str()); 1216 if (std::filesystem::exists(path) && std::filesystem::is_symlink(path)) 1217 { 1218 i2cBus = std::filesystem::read_symlink(path); 1219 } 1220 else 1221 { 1222 lg2::error("Controller write read command: Cannot get BusID"); 1223 return ipmi::responseInvalidFieldRequest(); 1224 } 1225 } 1226 else if (addressType == slotAddressTypeUniqueid) 1227 { 1228 i2cBus = "/dev/i2c-" + 1229 std::to_string(static_cast<uint8_t>(bbSlotNum) | 1230 (static_cast<uint8_t>(riserSlotNum) << 3)); 1231 } 1232 else 1233 { 1234 lg2::error("Controller write read command: invalid request"); 1235 return ipmi::responseInvalidFieldRequest(); 1236 } 1237 1238 // Allow single byte write as it is offset byte to read the data, rest 1239 // allow only in Special mode. 1240 if (writeCount > 1) 1241 { 1242 if (mtm.getMfgMode() == SpecialMode::none) 1243 { 1244 return ipmi::responseInsufficientPrivilege(); 1245 } 1246 } 1247 1248 if (readCount > slotI2CMaxReadSize) 1249 { 1250 lg2::error("Controller write read command: Read count exceeds limit"); 1251 return ipmi::responseParmOutOfRange(); 1252 } 1253 1254 if (!readCount && !writeCount) 1255 { 1256 lg2::error("Controller write read command: Read & write count are 0"); 1257 return ipmi::responseInvalidFieldRequest(); 1258 } 1259 1260 std::vector<uint8_t> readBuf(readCount); 1261 1262 ipmi::Cc retI2C = 1263 ipmi::i2cWriteRead(i2cBus, targetAddr, writeData, readBuf); 1264 if (retI2C != ipmi::ccSuccess) 1265 { 1266 return ipmi::response(retI2C); 1267 } 1268 1269 return ipmi::responseSuccess(readBuf); 1270 } 1271 1272 ipmi::RspType<> clearCMOS() 1273 { 1274 // There is an i2c device on bus 4, the target address is 0x38. Based on 1275 // the spec, writing 0x1 to address 0x61 on this device, will trigger 1276 // the clear CMOS action. 1277 constexpr uint8_t targetAddr = 0x38; 1278 std::string i2cBus = "/dev/i2c-4"; 1279 std::vector<uint8_t> writeData = {0x61, 0x1}; 1280 std::vector<uint8_t> readBuf(0); 1281 1282 ipmi::Cc retI2C = 1283 ipmi::i2cWriteRead(i2cBus, targetAddr, writeData, readBuf); 1284 return ipmi::response(retI2C); 1285 } 1286 1287 ipmi::RspType<> setFITcLayout(uint32_t layout) 1288 { 1289 static constexpr const char* factoryFITcLayout = 1290 "/var/sofs/factory-settings/layout/fitc"; 1291 std::filesystem::path fitcDir = 1292 std::filesystem::path(factoryFITcLayout).parent_path(); 1293 std::error_code ec; 1294 std::filesystem::create_directories(fitcDir, ec); 1295 if (ec) 1296 { 1297 return ipmi::responseUnspecifiedError(); 1298 } 1299 try 1300 { 1301 std::ofstream file(factoryFITcLayout); 1302 file << layout; 1303 file.flush(); 1304 file.close(); 1305 } 1306 catch (const std::exception& e) 1307 { 1308 return ipmi::responseUnspecifiedError(); 1309 } 1310 1311 return ipmi::responseSuccess(); 1312 } 1313 1314 static std::vector<std::string> 1315 getMCTPServiceConfigPaths(ipmi::Context::ptr& ctx) 1316 { 1317 boost::system::error_code ec; 1318 auto configPaths = ctx->bus->yield_method_call<std::vector<std::string>>( 1319 ctx->yield, ec, "xyz.openbmc_project.ObjectMapper", 1320 "/xyz/openbmc_project/object_mapper", 1321 "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", 1322 "/xyz/openbmc_project/inventory/system/board", 2, 1323 std::array<const char*, 1>{ 1324 "xyz.openbmc_project.Configuration.MctpConfiguration"}); 1325 if (ec) 1326 { 1327 throw std::runtime_error( 1328 "Failed to query configuration sub tree objects"); 1329 } 1330 return configPaths; 1331 } 1332 1333 static ipmi::RspType<> startOrStopService( 1334 ipmi::Context::ptr& ctx, const uint8_t enable, 1335 const std::string& serviceName, bool disableOrEnableUnitFiles = true) 1336 { 1337 constexpr bool runtimeOnly = false; 1338 constexpr bool force = false; 1339 1340 boost::system::error_code ec; 1341 switch (enable) 1342 { 1343 case ipmi::SupportedFeatureActions::stop: 1344 ctx->bus->yield_method_call(ctx->yield, ec, systemDService, 1345 systemDObjPath, systemDMgrIntf, 1346 "StopUnit", serviceName, "replace"); 1347 break; 1348 case ipmi::SupportedFeatureActions::start: 1349 ctx->bus->yield_method_call(ctx->yield, ec, systemDService, 1350 systemDObjPath, systemDMgrIntf, 1351 "StartUnit", serviceName, "replace"); 1352 break; 1353 case ipmi::SupportedFeatureActions::disable: 1354 if (disableOrEnableUnitFiles == true) 1355 { 1356 ctx->bus->yield_method_call( 1357 ctx->yield, ec, systemDService, systemDObjPath, 1358 systemDMgrIntf, "DisableUnitFiles", 1359 std::array<const char*, 1>{serviceName.c_str()}, 1360 runtimeOnly); 1361 } 1362 ctx->bus->yield_method_call( 1363 ctx->yield, ec, systemDService, systemDObjPath, systemDMgrIntf, 1364 "MaskUnitFiles", 1365 std::array<const char*, 1>{serviceName.c_str()}, runtimeOnly, 1366 force); 1367 break; 1368 case ipmi::SupportedFeatureActions::enable: 1369 ctx->bus->yield_method_call( 1370 ctx->yield, ec, systemDService, systemDObjPath, systemDMgrIntf, 1371 "UnmaskUnitFiles", 1372 std::array<const char*, 1>{serviceName.c_str()}, runtimeOnly); 1373 if (disableOrEnableUnitFiles == true) 1374 { 1375 ctx->bus->yield_method_call( 1376 ctx->yield, ec, systemDService, systemDObjPath, 1377 systemDMgrIntf, "EnableUnitFiles", 1378 std::array<const char*, 1>{serviceName.c_str()}, 1379 runtimeOnly, force); 1380 } 1381 break; 1382 default: 1383 lg2::warning("ERROR: Invalid feature action selected", "ACTION", 1384 lg2::dec, enable); 1385 return ipmi::responseInvalidFieldRequest(); 1386 } 1387 if (ec) 1388 { 1389 lg2::warning("ERROR: Service start or stop failed", "SERVICE", 1390 serviceName.c_str()); 1391 return ipmi::responseUnspecifiedError(); 1392 } 1393 return ipmi::responseSuccess(); 1394 } 1395 1396 static std::string getMCTPServiceName(const std::string& objectPath) 1397 { 1398 const auto serviceArgument = boost::algorithm::replace_all_copy( 1399 boost::algorithm::replace_first_copy( 1400 objectPath, "/xyz/openbmc_project/inventory/system/board/", ""), 1401 "/", "_2f"); 1402 std::string unitName = 1403 "xyz.openbmc_project.mctpd@" + serviceArgument + ".service"; 1404 return unitName; 1405 } 1406 1407 static ipmi::RspType<> handleMCTPFeature( 1408 ipmi::Context::ptr& ctx, const uint8_t enable, const std::string& binding) 1409 { 1410 std::vector<std::string> configPaths; 1411 try 1412 { 1413 configPaths = getMCTPServiceConfigPaths(ctx); 1414 } 1415 catch (const std::exception& e) 1416 { 1417 lg2::error(e.what()); 1418 return ipmi::responseUnspecifiedError(); 1419 } 1420 1421 for (const auto& objectPath : configPaths) 1422 { 1423 const auto pos = objectPath.find_last_of('/'); 1424 if (binding == objectPath.substr(pos + 1)) 1425 { 1426 return startOrStopService(ctx, enable, 1427 getMCTPServiceName(objectPath), false); 1428 } 1429 } 1430 return ipmi::responseSuccess(); 1431 } 1432 1433 static bool isNum(const std::string& s) 1434 { 1435 if (s.empty()) 1436 { 1437 return false; 1438 } 1439 uint8_t busNumber; 1440 const auto sEnd = s.data() + s.size(); 1441 const auto& [ptr, ec] = std::from_chars(s.data(), sEnd, busNumber); 1442 if (ec == std::errc() || ptr == sEnd) 1443 { 1444 return true; 1445 } 1446 return false; 1447 } 1448 1449 bool getBusNumFromPath(const std::string& path, std::string& busStr) 1450 { 1451 std::vector<std::string> parts; 1452 boost::split(parts, path, boost::is_any_of("-")); 1453 if (parts.size() == 2) 1454 { 1455 busStr = parts[1]; 1456 if (isNum(busStr)) 1457 { 1458 return true; 1459 } 1460 } 1461 return false; 1462 } 1463 1464 static ipmi::RspType<> muxSlotDisable(ipmi::Context::ptr& ctx, 1465 std::string service, std::string muxName, 1466 uint8_t action, uint8_t slotNum) 1467 { 1468 boost::system::error_code ec; 1469 const std::filesystem::path muxSymlinkDirPath = 1470 "/dev/i2c-mux/" + muxName + "/Slot_" + std::to_string(slotNum + 1); 1471 if (!std::filesystem::is_symlink(muxSymlinkDirPath)) 1472 { 1473 return ipmi::responseInvalidFieldRequest(); 1474 } 1475 std::string linkPath = std::filesystem::read_symlink(muxSymlinkDirPath); 1476 1477 std::string portNum; 1478 if (!getBusNumFromPath(linkPath, portNum)) 1479 { 1480 return ipmi::responseInvalidFieldRequest(); 1481 } 1482 auto res = ctx->bus->yield_method_call<int>( 1483 ctx->yield, ec, service, mctpObjPath, mctpBaseIntf, "SkipList", 1484 std::vector<uint8_t>{action, static_cast<uint8_t>(std::stoi(portNum))}); 1485 if (ec) 1486 { 1487 lg2::error("Failed to set mctp skiplist"); 1488 return ipmi::responseUnspecifiedError(); 1489 } 1490 1491 if (!res) 1492 { 1493 return ipmi::responseResponseError(); 1494 } 1495 return ipmi::responseSuccess(); 1496 } 1497 1498 static ipmi::RspType<> handleMCTPSlotFeature( 1499 ipmi::Context::ptr& ctx, const uint8_t enable, const uint8_t featureArg) 1500 { 1501 uint8_t slotNum = (featureArg & slotNumMask); 1502 switch ((featureArg & muxTypeMask) >> muxTypeShift) 1503 { 1504 case ipmi::SupportedFeatureMuxs::pcieMuxSlot: 1505 return muxSlotDisable(ctx, mctpPcieSlotService, "PCIe_Mux", enable, 1506 slotNum); 1507 break; 1508 case ipmi::SupportedFeatureMuxs::pcieMcioMuxSlot: 1509 return muxSlotDisable(ctx, mctpPcieSlotService, "PCIe_MCIO_Mux", 1510 enable, slotNum); 1511 break; 1512 case ipmi::SupportedFeatureMuxs::pcieM2EdSffMuxSlot: 1513 return muxSlotDisable(ctx, mctpPcieSlotService, "M2_EDSFF_Mux", 1514 enable, slotNum); 1515 break; 1516 case ipmi::SupportedFeatureMuxs::leftRaiserMuxSlot: 1517 return muxSlotDisable(ctx, mctpPcieSlotService, "Left_Riser_Mux", 1518 enable, slotNum); 1519 break; 1520 case ipmi::SupportedFeatureMuxs::rightRaiserMuxSlot: 1521 return muxSlotDisable(ctx, mctpPcieSlotService, "Right_Riser_Mux", 1522 enable, slotNum); 1523 break; 1524 case ipmi::SupportedFeatureMuxs::HsbpMuxSlot: 1525 return muxSlotDisable(ctx, mctpHsbpService, "HSBP_Mux", enable, 1526 slotNum); 1527 break; 1528 default: 1529 lg2::warning("ERROR: Invalid Mux slot selected"); 1530 return ipmi::responseInvalidFieldRequest(); 1531 } 1532 } 1533 1534 /** @brief implements MTM BMC Feature Control IPMI command which can be 1535 * used to enable or disable the supported BMC features. 1536 * @param yield - context object that represents the currently executing 1537 * coroutine 1538 * @param feature - feature enum to enable or disable 1539 * @param enable - enable or disable the feature 1540 * @param featureArg - custom arguments for that feature 1541 * @param reserved - reserved for future use 1542 * 1543 * @returns IPMI completion code 1544 */ 1545 ipmi::RspType<> mtmBMCFeatureControl( 1546 ipmi::Context::ptr ctx, const uint8_t feature, const uint8_t enable, 1547 const uint8_t featureArg, const uint16_t reserved) 1548 { 1549 if (reserved != 0) 1550 { 1551 return ipmi::responseInvalidFieldRequest(); 1552 } 1553 1554 switch (feature) 1555 { 1556 case ipmi::SupportedFeatureControls::mctp: 1557 switch (featureArg) 1558 { 1559 case ipmi::SupportedMCTPBindings::mctpPCIe: 1560 return handleMCTPFeature(ctx, enable, "MCTP_PCIe"); 1561 case ipmi::SupportedMCTPBindings::mctpSMBusHSBP: 1562 return handleMCTPFeature(ctx, enable, "MCTP_SMBus_HSBP"); 1563 case ipmi::SupportedMCTPBindings::mctpSMBusPCIeSlot: 1564 return handleMCTPFeature(ctx, enable, 1565 "MCTP_SMBus_PCIe_slot"); 1566 default: 1567 return ipmi::responseInvalidFieldRequest(); 1568 } 1569 break; 1570 case ipmi::SupportedFeatureControls::pcieScan: 1571 if (featureArg != 0) 1572 { 1573 return ipmi::responseInvalidFieldRequest(); 1574 } 1575 startOrStopService(ctx, enable, "xyz.openbmc_project.PCIe.service"); 1576 break; 1577 case ipmi::SupportedFeatureControls::mctpSlotSupport: 1578 return handleMCTPSlotFeature(ctx, enable, featureArg); 1579 break; 1580 default: 1581 return ipmi::responseInvalidFieldRequest(); 1582 } 1583 return ipmi::responseSuccess(); 1584 } 1585 } // namespace ipmi 1586 1587 void register_mtm_commands() __attribute__((constructor)); 1588 void register_mtm_commands() 1589 { 1590 // <Get SM Signal> 1591 ipmi::registerHandler(ipmi::prioOemBase, ipmi::intel::netFnGeneral, 1592 ipmi::intel::general::cmdGetSmSignal, 1593 ipmi::Privilege::Admin, ipmi::appMTMGetSignal); 1594 1595 ipmi::registerHandler(ipmi::prioOemBase, ipmi::intel::netFnGeneral, 1596 ipmi::intel::general::cmdSetSmSignal, 1597 ipmi::Privilege::Admin, ipmi::appMTMSetSignal); 1598 1599 ipmi::registerHandler(ipmi::prioOemBase, ipmi::intel::netFnGeneral, 1600 ipmi::intel::general::cmdMtmKeepAlive, 1601 ipmi::Privilege::Admin, ipmi::mtmKeepAlive); 1602 1603 ipmi::registerHandler(ipmi::prioOemBase, ipmi::intel::netFnGeneral, 1604 ipmi::intel::general::cmdSetManufacturingData, 1605 ipmi::Privilege::Admin, ipmi::setManufacturingData); 1606 1607 ipmi::registerHandler(ipmi::prioOemBase, ipmi::intel::netFnGeneral, 1608 ipmi::intel::general::cmdGetManufacturingData, 1609 ipmi::Privilege::Admin, ipmi::getManufacturingData); 1610 1611 ipmi::registerHandler(ipmi::prioOemBase, ipmi::intel::netFnGeneral, 1612 ipmi::intel::general::cmdSetFITcLayout, 1613 ipmi::Privilege::Admin, ipmi::setFITcLayout); 1614 1615 ipmi::registerHandler(ipmi::prioOemBase, ipmi::intel::netFnGeneral, 1616 ipmi::intel::general::cmdMTMBMCFeatureControl, 1617 ipmi::Privilege::Admin, ipmi::mtmBMCFeatureControl); 1618 1619 ipmi::registerHandler(ipmi::prioOemBase, ipmi::intel::netFnApp, 1620 ipmi::intel::general::cmdSlotI2CControllerWriteRead, 1621 ipmi::Privilege::Admin, 1622 ipmi::appSlotI2CControllerWriteRead); 1623 1624 ipmi::registerHandler(ipmi::prioOemBase, ipmi::intel::netFnPlatform, 1625 ipmi::intel::platform::cmdClearCMOS, 1626 ipmi::Privilege::Admin, ipmi::clearCMOS); 1627 1628 ipmi::registerFilter(ipmi::prioOemBase, 1629 [](ipmi::message::Request::ptr request) { 1630 return ipmi::mfgFilterMessage(request); 1631 }); 1632 } 1633