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 <fstream> 19 #include <ipmid/api.hpp> 20 #include <manufacturingcommands.hpp> 21 #include <oemcommands.hpp> 22 23 namespace ipmi 24 { 25 26 Manufacturing mtm; 27 28 static auto revertTimeOut = 29 std::chrono::duration_cast<std::chrono::microseconds>( 30 std::chrono::seconds(60)); // 1 minute timeout 31 32 static constexpr const char* callbackMgrService = 33 "xyz.openbmc_project.CallbackManager"; 34 static constexpr const char* callbackMgrIntf = 35 "xyz.openbmc_project.CallbackManager"; 36 static constexpr const char* callbackMgrObjPath = 37 "/xyz/openbmc_project/CallbackManager"; 38 static constexpr const char* retriggerLedUpdate = "RetriggerLEDUpdate"; 39 40 const static constexpr char* systemDService = "org.freedesktop.systemd1"; 41 const static constexpr char* systemDObjPath = "/org/freedesktop/systemd1"; 42 const static constexpr char* systemDMgrIntf = 43 "org.freedesktop.systemd1.Manager"; 44 const static constexpr char* pidControlService = "phosphor-pid-control.service"; 45 46 static inline Cc resetMtmTimer(boost::asio::yield_context yield) 47 { 48 auto sdbusp = getSdBus(); 49 boost::system::error_code ec; 50 sdbusp->yield_method_call<>(yield, ec, specialModeService, 51 specialModeObjPath, specialModeIntf, 52 "ResetTimer"); 53 if (ec) 54 { 55 phosphor::logging::log<phosphor::logging::level::ERR>( 56 "Failed to reset the manufacturing mode timer"); 57 return ccUnspecifiedError; 58 } 59 return ccSuccess; 60 } 61 62 int getGpioPathForSmSignal(const SmSignalGet signal, std::string& path) 63 { 64 switch (signal) 65 { 66 case SmSignalGet::smPowerButton: 67 path = "/xyz/openbmc_project/chassis/buttons/power"; 68 break; 69 case SmSignalGet::smResetButton: 70 path = "/xyz/openbmc_project/chassis/buttons/reset"; 71 break; 72 case SmSignalGet::smNMIButton: 73 path = "/xyz/openbmc_project/chassis/buttons/nmi"; 74 break; 75 case SmSignalGet::smIdentifyButton: 76 path = "/xyz/openbmc_project/chassis/buttons/id"; 77 break; 78 default: 79 return -1; 80 break; 81 } 82 return 0; 83 } 84 85 ipmi_ret_t ledStoreAndSet(SmSignalSet signal, std::string setState) 86 { 87 LedProperty* ledProp = mtm.findLedProperty(signal); 88 if (ledProp == nullptr) 89 { 90 return IPMI_CC_INVALID_FIELD_REQUEST; 91 } 92 93 std::string ledName = ledProp->getName(); 94 std::string ledService = ledServicePrefix + ledName; 95 std::string ledPath = ledPathPrefix + ledName; 96 ipmi::Value presentState; 97 98 if (false == ledProp->getLock()) 99 { 100 if (mtm.getProperty(ledService.c_str(), ledPath.c_str(), ledIntf, 101 "State", &presentState) != 0) 102 { 103 return IPMI_CC_UNSPECIFIED_ERROR; 104 } 105 ledProp->setPrevState(std::get<std::string>(presentState)); 106 ledProp->setLock(true); 107 if (signal == SmSignalSet::smPowerFaultLed || 108 signal == SmSignalSet::smSystemReadyLed) 109 { 110 mtm.revertLedCallback = true; 111 } 112 } 113 if (mtm.setProperty(ledService, ledPath, ledIntf, "State", 114 ledStateStr + setState) != 0) 115 { 116 return IPMI_CC_UNSPECIFIED_ERROR; 117 } 118 return IPMI_CC_OK; 119 } 120 121 ipmi_ret_t ledRevert(SmSignalSet signal) 122 { 123 LedProperty* ledProp = mtm.findLedProperty(signal); 124 if (ledProp == nullptr) 125 { 126 return IPMI_CC_INVALID_FIELD_REQUEST; 127 } 128 if (true == ledProp->getLock()) 129 { 130 ledProp->setLock(false); 131 if (signal == SmSignalSet::smPowerFaultLed || 132 signal == SmSignalSet::smSystemReadyLed) 133 { 134 try 135 { 136 ipmi::method_no_args::callDbusMethod( 137 *getSdBus(), callbackMgrService, callbackMgrObjPath, 138 callbackMgrIntf, retriggerLedUpdate); 139 } 140 catch (sdbusplus::exception_t& e) 141 { 142 return IPMI_CC_UNSPECIFIED_ERROR; 143 } 144 mtm.revertLedCallback = false; 145 } 146 else 147 { 148 std::string ledName = ledProp->getName(); 149 std::string ledService = ledServicePrefix + ledName; 150 std::string ledPath = ledPathPrefix + ledName; 151 if (mtm.setProperty(ledService, ledPath, ledIntf, "State", 152 ledProp->getPrevState()) != 0) 153 { 154 return IPMI_CC_UNSPECIFIED_ERROR; 155 } 156 } 157 } 158 return IPMI_CC_OK; 159 } 160 161 void Manufacturing::initData() 162 { 163 ledPropertyList.push_back( 164 LedProperty(SmSignalSet::smPowerFaultLed, "status_amber")); 165 ledPropertyList.push_back( 166 LedProperty(SmSignalSet::smSystemReadyLed, "status_green")); 167 ledPropertyList.push_back( 168 LedProperty(SmSignalSet::smIdentifyLed, "identify")); 169 } 170 171 void Manufacturing::revertTimerHandler() 172 { 173 if (revertFanPWM) 174 { 175 revertFanPWM = false; 176 disablePidControlService(false); 177 } 178 179 for (const auto& ledProperty : ledPropertyList) 180 { 181 const std::string& ledName = ledProperty.getName(); 182 ledRevert(ledProperty.getSignal()); 183 } 184 } 185 186 Manufacturing::Manufacturing() : 187 revertTimer([&](void) { revertTimerHandler(); }) 188 { 189 initData(); 190 } 191 192 int8_t Manufacturing::getProperty(const std::string& service, 193 const std::string& path, 194 const std::string& interface, 195 const std::string& propertyName, 196 ipmi::Value* reply) 197 { 198 try 199 { 200 *reply = ipmi::getDbusProperty(*getSdBus(), service, path, interface, 201 propertyName); 202 } 203 catch (const sdbusplus::exception::SdBusError& e) 204 { 205 phosphor::logging::log<phosphor::logging::level::INFO>( 206 "ERROR: getProperty"); 207 return -1; 208 } 209 210 return 0; 211 } 212 213 int8_t Manufacturing::setProperty(const std::string& service, 214 const std::string& path, 215 const std::string& interface, 216 const std::string& propertyName, 217 ipmi::Value value) 218 { 219 try 220 { 221 ipmi::setDbusProperty(*getSdBus(), service, path, interface, 222 propertyName, value); 223 } 224 catch (const sdbusplus::exception::SdBusError& e) 225 { 226 phosphor::logging::log<phosphor::logging::level::INFO>( 227 "ERROR: setProperty"); 228 return -1; 229 } 230 231 return 0; 232 } 233 234 int8_t Manufacturing::disablePidControlService(const bool disable) 235 { 236 try 237 { 238 auto dbus = getSdBus(); 239 auto method = dbus->new_method_call(systemDService, systemDObjPath, 240 systemDMgrIntf, 241 disable ? "StopUnit" : "StartUnit"); 242 method.append(pidControlService, "replace"); 243 auto reply = dbus->call(method); 244 } 245 catch (const sdbusplus::exception::SdBusError& e) 246 { 247 phosphor::logging::log<phosphor::logging::level::INFO>( 248 "ERROR: phosphor-pid-control service start or stop failed"); 249 return -1; 250 } 251 return 0; 252 } 253 254 ipmi::RspType<uint8_t, // Signal value 255 std::optional<uint16_t> // Fan tach value 256 > 257 appMTMGetSignal(boost::asio::yield_context yield, uint8_t signalTypeByte, 258 uint8_t instance, uint8_t actionByte) 259 { 260 if (mtm.getAccessLvl() < MtmLvl::mtmAvailable) 261 { 262 return ipmi::responseInvalidCommand(); 263 } 264 265 SmSignalGet signalType = static_cast<SmSignalGet>(signalTypeByte); 266 SmActionGet action = static_cast<SmActionGet>(actionByte); 267 268 switch (signalType) 269 { 270 case SmSignalGet::smFanPwmGet: 271 { 272 ipmi::Value reply; 273 std::string fullPath = fanPwmPath + std::to_string(instance + 1); 274 if (mtm.getProperty(fanService, fullPath, fanIntf, "Value", 275 &reply) < 0) 276 { 277 return ipmi::responseInvalidFieldRequest(); 278 } 279 double* doubleVal = std::get_if<double>(&reply); 280 if (doubleVal == nullptr) 281 { 282 return ipmi::responseUnspecifiedError(); 283 } 284 uint8_t sensorVal = std::round(*doubleVal); 285 resetMtmTimer(yield); 286 return ipmi::responseSuccess(sensorVal, std::nullopt); 287 } 288 break; 289 case SmSignalGet::smFanTachometerGet: 290 { 291 auto sdbusp = getSdBus(); 292 boost::system::error_code ec; 293 using objFlatMap = boost::container::flat_map< 294 std::string, boost::container::flat_map< 295 std::string, std::vector<std::string>>>; 296 297 auto flatMap = sdbusp->yield_method_call<objFlatMap>( 298 yield, ec, "xyz.openbmc_project.ObjectMapper", 299 "/xyz/openbmc_project/object_mapper", 300 "xyz.openbmc_project.ObjectMapper", "GetSubTree", 301 fanTachBasePath, 0, std::array<const char*, 1>{fanIntf}); 302 if (ec) 303 { 304 phosphor::logging::log<phosphor::logging::level::ERR>( 305 "Failed to query fan tach sub tree objects"); 306 return ipmi::responseUnspecifiedError(); 307 } 308 if (instance >= flatMap.size()) 309 { 310 return ipmi::responseInvalidFieldRequest(); 311 } 312 auto itr = flatMap.nth(instance); 313 ipmi::Value reply; 314 if (mtm.getProperty(fanService, itr->first, fanIntf, "Value", 315 &reply) < 0) 316 { 317 return ipmi::responseInvalidFieldRequest(); 318 } 319 320 double* doubleVal = std::get_if<double>(&reply); 321 if (doubleVal == nullptr) 322 { 323 return ipmi::responseUnspecifiedError(); 324 } 325 uint8_t sensorVal = FAN_PRESENT | FAN_SENSOR_PRESENT; 326 std::optional<uint16_t> fanTach = std::round(*doubleVal); 327 328 resetMtmTimer(yield); 329 return ipmi::responseSuccess(sensorVal, fanTach); 330 } 331 break; 332 case SmSignalGet::smIdentifyButton: 333 { 334 if (action == SmActionGet::revert || action == SmActionGet::ignore) 335 { 336 // ButtonMasked property is not supported for ID button as it is 337 // unnecessary. Hence if requested for revert / ignore, override 338 // it to sample action to make tools happy. 339 action = SmActionGet::sample; 340 } 341 // fall-through 342 } 343 case SmSignalGet::smResetButton: 344 case SmSignalGet::smPowerButton: 345 case SmSignalGet::smNMIButton: 346 { 347 std::string path; 348 if (getGpioPathForSmSignal(signalType, path) < 0) 349 { 350 return ipmi::responseInvalidFieldRequest(); 351 } 352 353 switch (action) 354 { 355 case SmActionGet::sample: 356 phosphor::logging::log<phosphor::logging::level::INFO>( 357 "case SmActionGet::sample"); 358 break; 359 case SmActionGet::ignore: 360 { 361 phosphor::logging::log<phosphor::logging::level::INFO>( 362 "case SmActionGet::ignore"); 363 if (mtm.setProperty(buttonService, path, buttonIntf, 364 "ButtonMasked", true) < 0) 365 { 366 return ipmi::responseUnspecifiedError(); 367 } 368 } 369 break; 370 case SmActionGet::revert: 371 { 372 phosphor::logging::log<phosphor::logging::level::INFO>( 373 "case SmActionGet::revert"); 374 if (mtm.setProperty(buttonService, path, buttonIntf, 375 "ButtonMasked", false) < 0) 376 { 377 return ipmi::responseUnspecifiedError(); 378 } 379 } 380 break; 381 382 default: 383 return ipmi::responseInvalidFieldRequest(); 384 break; 385 } 386 387 ipmi::Value reply; 388 if (mtm.getProperty(buttonService, path, buttonIntf, 389 "ButtonPressed", &reply) < 0) 390 { 391 return ipmi::responseUnspecifiedError(); 392 } 393 bool* valPtr = std::get_if<bool>(&reply); 394 if (valPtr == nullptr) 395 { 396 return ipmi::responseUnspecifiedError(); 397 } 398 resetMtmTimer(yield); 399 uint8_t sensorVal = *valPtr; 400 return ipmi::responseSuccess(sensorVal, std::nullopt); 401 } 402 break; 403 case SmSignalGet::smNcsiDiag: 404 { 405 constexpr const char* netBasePath = "/sys/class/net/eth"; 406 constexpr const char* carrierSuffix = "/carrier"; 407 std::ifstream netIfs(netBasePath + std::to_string(instance) + 408 carrierSuffix); 409 if (!netIfs.good()) 410 { 411 return ipmi::responseInvalidFieldRequest(); 412 } 413 std::string carrier; 414 netIfs >> carrier; 415 resetMtmTimer(yield); 416 return ipmi::responseSuccess( 417 static_cast<uint8_t>(std::stoi(carrier)), std::nullopt); 418 } 419 break; 420 default: 421 return ipmi::responseInvalidFieldRequest(); 422 break; 423 } 424 } 425 426 ipmi::RspType<> appMTMSetSignal(boost::asio::yield_context yield, 427 uint8_t signalTypeByte, uint8_t instance, 428 uint8_t actionByte, 429 std::optional<uint8_t> pwmSpeed) 430 { 431 if (mtm.getAccessLvl() < MtmLvl::mtmAvailable) 432 { 433 return ipmi::responseInvalidCommand(); 434 } 435 436 SmSignalSet signalType = static_cast<SmSignalSet>(signalTypeByte); 437 SmActionSet action = static_cast<SmActionSet>(actionByte); 438 Cc retCode = ccSuccess; 439 int8_t ret = 0; 440 441 switch (signalType) 442 { 443 case SmSignalSet::smPowerFaultLed: 444 case SmSignalSet::smSystemReadyLed: 445 case SmSignalSet::smIdentifyLed: 446 switch (action) 447 { 448 case SmActionSet::forceDeasserted: 449 { 450 phosphor::logging::log<phosphor::logging::level::INFO>( 451 "case SmActionSet::forceDeasserted"); 452 453 retCode = ledStoreAndSet(signalType, std::string("Off")); 454 if (retCode != ccSuccess) 455 { 456 return ipmi::response(retCode); 457 } 458 mtm.revertTimer.start(revertTimeOut); 459 } 460 break; 461 case SmActionSet::forceAsserted: 462 { 463 phosphor::logging::log<phosphor::logging::level::INFO>( 464 "case SmActionSet::forceAsserted"); 465 466 retCode = ledStoreAndSet(signalType, std::string("On")); 467 if (retCode != ccSuccess) 468 { 469 return ipmi::response(retCode); 470 } 471 mtm.revertTimer.start(revertTimeOut); 472 if (SmSignalSet::smPowerFaultLed == signalType) 473 { 474 // Deassert "system ready" 475 retCode = ledStoreAndSet(SmSignalSet::smSystemReadyLed, 476 std::string("Off")); 477 } 478 else if (SmSignalSet::smSystemReadyLed == signalType) 479 { 480 // Deassert "fault led" 481 retCode = ledStoreAndSet(SmSignalSet::smPowerFaultLed, 482 std::string("Off")); 483 } 484 } 485 break; 486 case SmActionSet::revert: 487 { 488 phosphor::logging::log<phosphor::logging::level::INFO>( 489 "case SmActionSet::revert"); 490 retCode = ledRevert(signalType); 491 } 492 break; 493 default: 494 { 495 return ipmi::responseInvalidFieldRequest(); 496 } 497 } 498 break; 499 case SmSignalSet::smFanPowerSpeed: 500 { 501 if ((action == SmActionSet::forceAsserted) && (!pwmSpeed)) 502 { 503 return ipmi::responseReqDataLenInvalid(); 504 } 505 506 if ((action == SmActionSet::forceAsserted) && (*pwmSpeed > 100)) 507 { 508 return ipmi::responseInvalidFieldRequest(); 509 } 510 511 uint8_t pwmValue = 0; 512 switch (action) 513 { 514 case SmActionSet::revert: 515 { 516 if (mtm.revertFanPWM) 517 { 518 ret = mtm.disablePidControlService(false); 519 if (ret < 0) 520 { 521 return ipmi::responseUnspecifiedError(); 522 } 523 mtm.revertFanPWM = false; 524 } 525 } 526 break; 527 case SmActionSet::forceAsserted: 528 { 529 pwmValue = *pwmSpeed; 530 } // fall-through 531 case SmActionSet::forceDeasserted: 532 { 533 if (!mtm.revertFanPWM) 534 { 535 ret = mtm.disablePidControlService(true); 536 if (ret < 0) 537 { 538 return ipmi::responseUnspecifiedError(); 539 } 540 mtm.revertFanPWM = true; 541 } 542 mtm.revertTimer.start(revertTimeOut); 543 std::string fanPwmInstancePath = 544 fanPwmPath + std::to_string(instance + 1); 545 546 ret = 547 mtm.setProperty(fanService, fanPwmInstancePath, fanIntf, 548 "Value", static_cast<double>(pwmValue)); 549 if (ret < 0) 550 { 551 return ipmi::responseUnspecifiedError(); 552 } 553 } 554 break; 555 default: 556 { 557 return ipmi::responseInvalidFieldRequest(); 558 } 559 } 560 } 561 break; 562 default: 563 { 564 return ipmi::responseInvalidFieldRequest(); 565 } 566 } 567 if (retCode == ccSuccess) 568 { 569 resetMtmTimer(yield); 570 } 571 return ipmi::response(retCode); 572 } 573 574 ipmi::RspType<> mtmKeepAlive(boost::asio::yield_context yield, uint8_t reserved, 575 const std::array<char, 5>& intentionalSignature) 576 { 577 // Allow MTM keep alive command only in manfacturing mode. 578 if (mtm.getAccessLvl() != MtmLvl::mtmAvailable) 579 { 580 return ipmi::responseInvalidCommand(); 581 } 582 constexpr std::array<char, 5> signatureOk = {'I', 'N', 'T', 'E', 'L'}; 583 if (intentionalSignature != signatureOk || reserved != 0) 584 { 585 return ipmi::responseInvalidFieldRequest(); 586 } 587 return ipmi::response(resetMtmTimer(yield)); 588 } 589 590 ipmi::Cc mfgFilterMessage(ipmi::message::Request::ptr request) 591 { 592 // i2c master write read command needs additional checking 593 if ((request->ctx->netFn == ipmi::netFnApp) && 594 (request->ctx->cmd == ipmi::app::cmdMasterWriteRead)) 595 { 596 if (request->payload.size() > 4) 597 { 598 // Allow write data count > 1, only if it is in MFG mode 599 if (mtm.getAccessLvl() != MtmLvl::mtmAvailable) 600 { 601 return ipmi::ccInsufficientPrivilege; 602 } 603 } 604 } 605 606 return ipmi::ccSuccess; 607 } 608 609 static constexpr uint8_t maxEthSize = 6; 610 static constexpr uint8_t maxSupportedEth = 3; 611 static constexpr const char* factoryEthAddrBaseFileName = 612 "/var/sofs/factory-settings/network/mac/eth"; 613 614 ipmi::RspType<> setManufacturingData(boost::asio::yield_context yield, 615 uint8_t dataType, 616 std::array<uint8_t, maxEthSize> ethData) 617 { 618 // mfg filter logic will restrict this command executing only in mfg mode. 619 if (dataType >= maxSupportedEth) 620 { 621 return ipmi::responseParmOutOfRange(); 622 } 623 624 constexpr uint8_t invalidData = 0; 625 constexpr uint8_t validData = 1; 626 constexpr uint8_t ethAddrStrSize = 627 19; // XX:XX:XX:XX:XX:XX + \n + null termination; 628 std::vector<uint8_t> buff(ethAddrStrSize); 629 std::snprintf(reinterpret_cast<char*>(buff.data()), ethAddrStrSize, 630 "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx\n", ethData.at(0), 631 ethData.at(1), ethData.at(2), ethData.at(3), ethData.at(4), 632 ethData.at(5)); 633 std::ofstream oEthFile(factoryEthAddrBaseFileName + 634 std::to_string(dataType), 635 std::ofstream::out); 636 if (!oEthFile.good()) 637 { 638 return ipmi::responseUnspecifiedError(); 639 } 640 641 oEthFile << reinterpret_cast<char*>(buff.data()); 642 oEthFile << fflush; 643 oEthFile.close(); 644 645 resetMtmTimer(yield); 646 return ipmi::responseSuccess(); 647 } 648 649 ipmi::RspType<uint8_t, std::array<uint8_t, maxEthSize>> 650 getManufacturingData(boost::asio::yield_context yield, uint8_t dataType) 651 { 652 // mfg filter logic will restrict this command executing only in mfg mode. 653 if (dataType >= maxSupportedEth) 654 { 655 return ipmi::responseParmOutOfRange(); 656 } 657 std::array<uint8_t, maxEthSize> ethData{0}; 658 constexpr uint8_t invalidData = 0; 659 constexpr uint8_t validData = 1; 660 661 std::ifstream iEthFile(factoryEthAddrBaseFileName + 662 std::to_string(dataType), 663 std::ifstream::in); 664 if (!iEthFile.good()) 665 { 666 return ipmi::responseSuccess(invalidData, ethData); 667 } 668 std::string ethStr; 669 iEthFile >> ethStr; 670 uint8_t* data = ethData.data(); 671 std::sscanf(ethStr.c_str(), "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx", 672 data, (data + 1), (data + 2), (data + 3), (data + 4), 673 (data + 5)); 674 675 resetMtmTimer(yield); 676 return ipmi::responseSuccess(validData, ethData); 677 } 678 679 } // namespace ipmi 680 681 void register_mtm_commands() __attribute__((constructor)); 682 void register_mtm_commands() 683 { 684 // <Get SM Signal> 685 ipmi::registerHandler( 686 ipmi::prioOemBase, ipmi::netFnOemOne, 687 static_cast<ipmi::Cmd>(IPMINetfnIntelOEMGeneralCmd::cmdGetSmSignal), 688 ipmi::Privilege::Admin, ipmi::appMTMGetSignal); 689 690 ipmi::registerHandler( 691 ipmi::prioOemBase, ipmi::netFnOemOne, 692 static_cast<ipmi::Cmd>(IPMINetfnIntelOEMGeneralCmd::cmdSetSmSignal), 693 ipmi::Privilege::Admin, ipmi::appMTMSetSignal); 694 695 ipmi::registerHandler( 696 ipmi::prioOemBase, ipmi::netFnOemOne, 697 static_cast<ipmi::Cmd>(IPMINetfnIntelOEMGeneralCmd::cmdMtmKeepAlive), 698 ipmi::Privilege::Admin, ipmi::mtmKeepAlive); 699 700 ipmi::registerHandler( 701 ipmi::prioOemBase, ipmi::netFnOemOne, 702 static_cast<ipmi::Cmd>( 703 IPMINetfnIntelOEMGeneralCmd::cmdSetManufacturingData), 704 ipmi::Privilege::Admin, ipmi::setManufacturingData); 705 706 ipmi::registerHandler( 707 ipmi::prioOemBase, ipmi::netFnOemOne, 708 static_cast<ipmi::Cmd>( 709 IPMINetfnIntelOEMGeneralCmd::cmdGetManufacturingData), 710 ipmi::Privilege::Admin, ipmi::getManufacturingData); 711 712 ipmi::registerFilter(ipmi::netFnOemOne, 713 [](ipmi::message::Request::ptr request) { 714 return ipmi::mfgFilterMessage(request); 715 }); 716 717 return; 718 } 719