1 #include "storagehandler.hpp" 2 3 #include "fruread.hpp" 4 #include "read_fru_data.hpp" 5 #include "selutility.hpp" 6 #include "sensorhandler.hpp" 7 #include "storageaddsel.hpp" 8 9 #include <arpa/inet.h> 10 #include <mapper.h> 11 #include <systemd/sd-bus.h> 12 13 #include <algorithm> 14 #include <chrono> 15 #include <cstdio> 16 #include <cstring> 17 #include <filesystem> 18 #include <ipmid/api.hpp> 19 #include <ipmid/utils.hpp> 20 #include <phosphor-logging/elog-errors.hpp> 21 #include <phosphor-logging/log.hpp> 22 #include <sdbusplus/server.hpp> 23 #include <string> 24 #include <variant> 25 #include <xyz/openbmc_project/Common/error.hpp> 26 27 void register_netfn_storage_functions() __attribute__((constructor)); 28 29 unsigned int g_sel_time = 0xFFFFFFFF; 30 extern const ipmi::sensor::IdInfoMap sensors; 31 extern const FruMap frus; 32 constexpr uint8_t eventDataSize = 3; 33 namespace 34 { 35 constexpr auto TIME_INTERFACE = "xyz.openbmc_project.Time.EpochTime"; 36 constexpr auto HOST_TIME_PATH = "/xyz/openbmc_project/time/host"; 37 constexpr auto DBUS_PROPERTIES = "org.freedesktop.DBus.Properties"; 38 constexpr auto PROPERTY_ELAPSED = "Elapsed"; 39 40 } // namespace 41 42 namespace cache 43 { 44 /* 45 * This cache contains the object paths of the logging entries sorted in the 46 * order of the filename(numeric order). The cache is initialized by 47 * invoking readLoggingObjectPaths with the cache as the parameter. The 48 * cache is invoked in the execution of the Get SEL info and Delete SEL 49 * entry command. The Get SEL Info command is typically invoked before the 50 * Get SEL entry command, so the cache is utilized for responding to Get SEL 51 * entry command. The cache is invalidated by clearing after Delete SEL 52 * entry and Clear SEL command. 53 */ 54 ipmi::sel::ObjectPaths paths; 55 56 } // namespace cache 57 58 using InternalFailure = 59 sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure; 60 using namespace phosphor::logging; 61 using namespace ipmi::fru; 62 63 /** 64 * @enum Device access mode 65 */ 66 enum class AccessMode 67 { 68 bytes, ///< Device is accessed by bytes 69 words ///< Device is accessed by words 70 }; 71 72 ipmi_ret_t ipmi_storage_wildcard(ipmi_netfn_t netfn, ipmi_cmd_t cmd, 73 ipmi_request_t request, 74 ipmi_response_t response, 75 ipmi_data_len_t data_len, 76 ipmi_context_t context) 77 { 78 // Status code. 79 ipmi_ret_t rc = IPMI_CC_INVALID; 80 *data_len = 0; 81 return rc; 82 } 83 84 /** @brief implements the get SEL Info command 85 * @returns IPMI completion code plus response data 86 * - selVersion - SEL revision 87 * - entries - Number of log entries in SEL. 88 * - freeSpace - Free Space in bytes. 89 * - addTimeStamp - Most recent addition timestamp 90 * - eraseTimeStamp - Most recent erase timestamp 91 * - operationSupport - Reserve & Delete SEL operations supported 92 */ 93 94 ipmi::RspType<uint8_t, // SEL revision. 95 uint16_t, // number of log entries in SEL. 96 uint16_t, // free Space in bytes. 97 uint32_t, // most recent addition timestamp 98 uint32_t, // most recent erase timestamp. 99 100 bool, // SEL allocation info supported 101 bool, // reserve SEL supported 102 bool, // partial Add SEL Entry supported 103 bool, // delete SEL supported 104 uint3_t, // reserved 105 bool // overflow flag 106 > 107 ipmiStorageGetSelInfo() 108 { 109 uint16_t entries = 0; 110 // Most recent addition timestamp. 111 uint32_t addTimeStamp = ipmi::sel::invalidTimeStamp; 112 113 try 114 { 115 ipmi::sel::readLoggingObjectPaths(cache::paths); 116 } 117 catch (const sdbusplus::exception::SdBusError& e) 118 { 119 // No action if reading log objects have failed for this command. 120 // readLoggingObjectPaths will throw exception if there are no log 121 // entries. The command will be responded with number of SEL entries 122 // as 0. 123 } 124 125 if (!cache::paths.empty()) 126 { 127 entries = static_cast<uint16_t>(cache::paths.size()); 128 129 try 130 { 131 addTimeStamp = static_cast<uint32_t>( 132 (ipmi::sel::getEntryTimeStamp(cache::paths.back()).count())); 133 } 134 catch (InternalFailure& e) 135 { 136 } 137 catch (const std::runtime_error& e) 138 { 139 log<level::ERR>(e.what()); 140 } 141 } 142 143 constexpr uint8_t selVersion = ipmi::sel::selVersion; 144 constexpr uint16_t freeSpace = 0xFFFF; 145 constexpr uint32_t eraseTimeStamp = ipmi::sel::invalidTimeStamp; 146 constexpr uint3_t reserved{0}; 147 148 return ipmi::responseSuccess( 149 selVersion, entries, freeSpace, addTimeStamp, eraseTimeStamp, 150 ipmi::sel::operationSupport::getSelAllocationInfo, 151 ipmi::sel::operationSupport::reserveSel, 152 ipmi::sel::operationSupport::partialAddSelEntry, 153 ipmi::sel::operationSupport::deleteSel, reserved, 154 ipmi::sel::operationSupport::overflow); 155 } 156 157 ipmi_ret_t getSELEntry(ipmi_netfn_t netfn, ipmi_cmd_t cmd, 158 ipmi_request_t request, ipmi_response_t response, 159 ipmi_data_len_t data_len, ipmi_context_t context) 160 { 161 if (*data_len != sizeof(ipmi::sel::GetSELEntryRequest)) 162 { 163 *data_len = 0; 164 return IPMI_CC_REQ_DATA_LEN_INVALID; 165 } 166 167 auto requestData = 168 reinterpret_cast<const ipmi::sel::GetSELEntryRequest*>(request); 169 170 if (requestData->reservationID != 0) 171 { 172 if (!checkSELReservation(requestData->reservationID)) 173 { 174 *data_len = 0; 175 return IPMI_CC_INVALID_RESERVATION_ID; 176 } 177 } 178 179 if (cache::paths.empty()) 180 { 181 *data_len = 0; 182 return IPMI_CC_SENSOR_INVALID; 183 } 184 185 ipmi::sel::ObjectPaths::const_iterator iter; 186 187 // Check for the requested SEL Entry. 188 if (requestData->selRecordID == ipmi::sel::firstEntry) 189 { 190 iter = cache::paths.begin(); 191 } 192 else if (requestData->selRecordID == ipmi::sel::lastEntry) 193 { 194 iter = cache::paths.end(); 195 } 196 else 197 { 198 std::string objPath = std::string(ipmi::sel::logBasePath) + "/" + 199 std::to_string(requestData->selRecordID); 200 201 iter = std::find(cache::paths.begin(), cache::paths.end(), objPath); 202 if (iter == cache::paths.end()) 203 { 204 *data_len = 0; 205 return IPMI_CC_SENSOR_INVALID; 206 } 207 } 208 209 ipmi::sel::GetSELEntryResponse record{}; 210 211 // Convert the log entry into SEL record. 212 try 213 { 214 record = ipmi::sel::convertLogEntrytoSEL(*iter); 215 } 216 catch (InternalFailure& e) 217 { 218 *data_len = 0; 219 return IPMI_CC_UNSPECIFIED_ERROR; 220 } 221 catch (const std::runtime_error& e) 222 { 223 log<level::ERR>(e.what()); 224 *data_len = 0; 225 return IPMI_CC_UNSPECIFIED_ERROR; 226 } 227 228 // Identify the next SEL record ID 229 if (iter != cache::paths.end()) 230 { 231 ++iter; 232 if (iter == cache::paths.end()) 233 { 234 record.nextRecordID = ipmi::sel::lastEntry; 235 } 236 else 237 { 238 namespace fs = std::filesystem; 239 fs::path path(*iter); 240 record.nextRecordID = static_cast<uint16_t>( 241 std::stoul(std::string(path.filename().c_str()))); 242 } 243 } 244 else 245 { 246 record.nextRecordID = ipmi::sel::lastEntry; 247 } 248 249 if (requestData->readLength == ipmi::sel::entireRecord) 250 { 251 std::memcpy(response, &record, sizeof(record)); 252 *data_len = sizeof(record); 253 } 254 else 255 { 256 if (requestData->offset >= ipmi::sel::selRecordSize || 257 requestData->readLength > ipmi::sel::selRecordSize) 258 { 259 *data_len = 0; 260 return IPMI_CC_INVALID_FIELD_REQUEST; 261 } 262 263 auto diff = ipmi::sel::selRecordSize - requestData->offset; 264 auto readLength = 265 std::min(diff, static_cast<int>(requestData->readLength)); 266 267 std::memcpy(response, &record.nextRecordID, 268 sizeof(record.nextRecordID)); 269 std::memcpy(static_cast<uint8_t*>(response) + 270 sizeof(record.nextRecordID), 271 &record.recordID + requestData->offset, readLength); 272 *data_len = sizeof(record.nextRecordID) + readLength; 273 } 274 275 return IPMI_CC_OK; 276 } 277 278 /** @brief implements the delete SEL entry command 279 * @request 280 * - reservationID; // reservation ID. 281 * - selRecordID; // SEL record ID. 282 * 283 * @returns ipmi completion code plus response data 284 * - Record ID of the deleted record 285 */ 286 ipmi::RspType<uint16_t // deleted record ID 287 > 288 deleteSELEntry(uint16_t reservationID, uint16_t selRecordID) 289 { 290 291 namespace fs = std::filesystem; 292 293 if (!checkSELReservation(reservationID)) 294 { 295 return ipmi::responseInvalidReservationId(); 296 } 297 298 // Per the IPMI spec, need to cancel the reservation when a SEL entry is 299 // deleted 300 cancelSELReservation(); 301 302 try 303 { 304 ipmi::sel::readLoggingObjectPaths(cache::paths); 305 } 306 catch (const sdbusplus::exception::SdBusError& e) 307 { 308 // readLoggingObjectPaths will throw exception if there are no error 309 // log entries. 310 return ipmi::responseSensorInvalid(); 311 } 312 313 if (cache::paths.empty()) 314 { 315 return ipmi::responseSensorInvalid(); 316 } 317 318 ipmi::sel::ObjectPaths::const_iterator iter; 319 uint16_t delRecordID = 0; 320 321 if (selRecordID == ipmi::sel::firstEntry) 322 { 323 iter = cache::paths.begin(); 324 fs::path path(*iter); 325 delRecordID = static_cast<uint16_t>( 326 std::stoul(std::string(path.filename().c_str()))); 327 } 328 else if (selRecordID == ipmi::sel::lastEntry) 329 { 330 iter = cache::paths.end(); 331 fs::path path(*iter); 332 delRecordID = static_cast<uint16_t>( 333 std::stoul(std::string(path.filename().c_str()))); 334 } 335 else 336 { 337 std::string objPath = std::string(ipmi::sel::logBasePath) + "/" + 338 std::to_string(selRecordID); 339 340 iter = std::find(cache::paths.begin(), cache::paths.end(), objPath); 341 if (iter == cache::paths.end()) 342 { 343 return ipmi::responseSensorInvalid(); 344 } 345 delRecordID = selRecordID; 346 } 347 348 sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()}; 349 std::string service; 350 351 try 352 { 353 service = ipmi::getService(bus, ipmi::sel::logDeleteIntf, *iter); 354 } 355 catch (const std::runtime_error& e) 356 { 357 log<level::ERR>(e.what()); 358 return ipmi::responseUnspecifiedError(); 359 } 360 361 auto methodCall = bus.new_method_call(service.c_str(), (*iter).c_str(), 362 ipmi::sel::logDeleteIntf, "Delete"); 363 auto reply = bus.call(methodCall); 364 if (reply.is_method_error()) 365 { 366 return ipmi::responseUnspecifiedError(); 367 } 368 369 // Invalidate the cache of dbus entry objects. 370 cache::paths.clear(); 371 372 return ipmi::responseSuccess(delRecordID); 373 } 374 375 /** @brief implements the Clear SEL command 376 * @request 377 * - reservationID // Reservation ID. 378 * - clr // char array { 'C'(0x43h), 'L'(0x4Ch), 'R'(0x52h) } 379 * - eraseOperation; // requested operation. 380 * 381 * @returns ipmi completion code plus response data 382 * - erase status 383 */ 384 385 ipmi::RspType<uint8_t // erase status 386 > 387 clearSEL(uint16_t reservationID, const std::array<char, 3>& clr, 388 uint8_t eraseOperation) 389 { 390 static constexpr std::array<char, 3> clrOk = {'C', 'L', 'R'}; 391 if (clr != clrOk) 392 { 393 return ipmi::responseInvalidFieldRequest(); 394 } 395 396 if (!checkSELReservation(reservationID)) 397 { 398 return ipmi::responseInvalidReservationId(); 399 } 400 401 /* 402 * Erasure status cannot be fetched from DBUS, so always return erasure 403 * status as `erase completed`. 404 */ 405 if (eraseOperation == ipmi::sel::getEraseStatus) 406 { 407 return ipmi::responseSuccess( 408 static_cast<uint8_t>(ipmi::sel::eraseComplete)); 409 } 410 411 // Per the IPMI spec, need to cancel any reservation when the SEL is cleared 412 cancelSELReservation(); 413 414 sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()}; 415 ipmi::sel::ObjectPaths objectPaths; 416 auto depth = 0; 417 418 auto mapperCall = 419 bus.new_method_call(ipmi::sel::mapperBusName, ipmi::sel::mapperObjPath, 420 ipmi::sel::mapperIntf, "GetSubTreePaths"); 421 mapperCall.append(ipmi::sel::logBasePath); 422 mapperCall.append(depth); 423 mapperCall.append(ipmi::sel::ObjectPaths({ipmi::sel::logEntryIntf})); 424 425 try 426 { 427 auto reply = bus.call(mapperCall); 428 if (reply.is_method_error()) 429 { 430 return ipmi::responseSuccess( 431 static_cast<uint8_t>(ipmi::sel::eraseComplete)); 432 } 433 434 reply.read(objectPaths); 435 if (objectPaths.empty()) 436 { 437 return ipmi::responseSuccess( 438 static_cast<uint8_t>(ipmi::sel::eraseComplete)); 439 } 440 } 441 catch (const sdbusplus::exception::SdBusError& e) 442 { 443 return ipmi::responseSuccess( 444 static_cast<uint8_t>(ipmi::sel::eraseComplete)); 445 } 446 447 std::string service; 448 449 try 450 { 451 service = ipmi::getService(bus, ipmi::sel::logDeleteIntf, 452 objectPaths.front()); 453 } 454 catch (const std::runtime_error& e) 455 { 456 log<level::ERR>(e.what()); 457 return ipmi::responseUnspecifiedError(); 458 } 459 460 for (const auto& iter : objectPaths) 461 { 462 auto methodCall = bus.new_method_call( 463 service.c_str(), iter.c_str(), ipmi::sel::logDeleteIntf, "Delete"); 464 465 auto reply = bus.call(methodCall); 466 if (reply.is_method_error()) 467 { 468 return ipmi::responseUnspecifiedError(); 469 } 470 } 471 472 // Invalidate the cache of dbus entry objects. 473 cache::paths.clear(); 474 return ipmi::responseSuccess( 475 static_cast<uint8_t>(ipmi::sel::eraseComplete)); 476 } 477 478 /** @brief implements the get SEL time command 479 * @returns IPMI completion code plus response data 480 * -current time 481 */ 482 ipmi::RspType<uint32_t> // current time 483 ipmiStorageGetSelTime() 484 { 485 using namespace std::chrono; 486 uint64_t host_time_usec = 0; 487 std::stringstream hostTime; 488 489 try 490 { 491 sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()}; 492 auto service = ipmi::getService(bus, TIME_INTERFACE, HOST_TIME_PATH); 493 std::variant<uint64_t> value; 494 495 // Get host time 496 auto method = bus.new_method_call(service.c_str(), HOST_TIME_PATH, 497 DBUS_PROPERTIES, "Get"); 498 499 method.append(TIME_INTERFACE, PROPERTY_ELAPSED); 500 auto reply = bus.call(method); 501 if (reply.is_method_error()) 502 { 503 log<level::ERR>("Error getting time", 504 entry("SERVICE=%s", service.c_str()), 505 entry("PATH=%s", HOST_TIME_PATH)); 506 return ipmi::responseUnspecifiedError(); 507 } 508 reply.read(value); 509 host_time_usec = std::get<uint64_t>(value); 510 } 511 catch (InternalFailure& e) 512 { 513 log<level::ERR>(e.what()); 514 return ipmi::responseUnspecifiedError(); 515 } 516 catch (const std::runtime_error& e) 517 { 518 log<level::ERR>(e.what()); 519 return ipmi::responseUnspecifiedError(); 520 } 521 522 hostTime << "Host time:" 523 << duration_cast<seconds>(microseconds(host_time_usec)).count(); 524 log<level::DEBUG>(hostTime.str().c_str()); 525 526 // Time is really long int but IPMI wants just uint32. This works okay until 527 // the number of seconds since 1970 overflows uint32 size.. Still a whole 528 // lot of time here to even think about that. 529 return ipmi::responseSuccess( 530 duration_cast<seconds>(microseconds(host_time_usec)).count()); 531 } 532 533 /** @brief implements the set SEL time command 534 * @param selDeviceTime - epoch time 535 * -local time as the number of seconds from 00:00:00, January 1, 1970 536 * @returns IPMI completion code 537 */ 538 ipmi::RspType<> ipmiStorageSetSelTime(uint32_t selDeviceTime) 539 { 540 using namespace std::chrono; 541 microseconds usec{seconds(selDeviceTime)}; 542 543 try 544 { 545 sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()}; 546 auto service = ipmi::getService(bus, TIME_INTERFACE, HOST_TIME_PATH); 547 std::variant<uint64_t> value{usec.count()}; 548 549 // Set host time 550 auto method = bus.new_method_call(service.c_str(), HOST_TIME_PATH, 551 DBUS_PROPERTIES, "Set"); 552 553 method.append(TIME_INTERFACE, PROPERTY_ELAPSED, value); 554 auto reply = bus.call(method); 555 if (reply.is_method_error()) 556 { 557 log<level::ERR>("Error setting time", 558 entry("SERVICE=%s", service.c_str()), 559 entry("PATH=%s", HOST_TIME_PATH)); 560 return ipmi::responseUnspecifiedError(); 561 } 562 } 563 catch (InternalFailure& e) 564 { 565 log<level::ERR>(e.what()); 566 return ipmi::responseUnspecifiedError(); 567 } 568 catch (const std::runtime_error& e) 569 { 570 log<level::ERR>(e.what()); 571 return ipmi::responseUnspecifiedError(); 572 } 573 574 return ipmi::responseSuccess(); 575 } 576 577 /** @brief implements the reserve SEL command 578 * @returns IPMI completion code plus response data 579 * - SEL reservation ID. 580 */ 581 ipmi::RspType<uint16_t> ipmiStorageReserveSel() 582 { 583 return ipmi::responseSuccess(reserveSel()); 584 } 585 586 /** @brief implements the Add SEL entry command 587 * @request 588 * 589 * - recordID ID used for SEL Record access 590 * - recordType Record Type 591 * - timeStamp Time when event was logged. LS byte first 592 * - generatorID software ID if event was generated from 593 * system software 594 * - evmRev event message format version 595 * - sensorType sensor type code for service that generated 596 * the event 597 * - sensorNumber number of sensors that generated the event 598 * - eventDir event dir 599 * - eventData event data field contents 600 * 601 * @returns ipmi completion code plus response data 602 * - RecordID of the Added SEL entry 603 */ 604 ipmi::RspType<uint16_t // recordID of the Added SEL entry 605 > 606 ipmiStorageAddSEL(uint16_t recordID, uint8_t recordType, uint32_t timeStamp, 607 uint16_t generatorID, uint8_t evmRev, uint8_t sensorType, 608 uint8_t sensorNumber, uint8_t eventDir, 609 std::array<uint8_t, eventDataSize> eventData) 610 { 611 // Per the IPMI spec, need to cancel the reservation when a SEL entry is 612 // added 613 cancelSELReservation(); 614 // Hostboot sends SEL with OEM record type 0xDE to indicate that there is 615 // a maintenance procedure associated with eSEL record. 616 static constexpr auto procedureType = 0xDE; 617 if (recordType == procedureType) 618 { 619 // In the OEM record type 0xDE, byte 11 in the SEL record indicate the 620 // procedure number. 621 createProcedureLogEntry(sensorType); 622 } 623 624 return ipmi::responseSuccess(recordID); 625 } 626 627 /** @brief implements the get FRU Inventory Area Info command 628 * 629 * @returns IPMI completion code plus response data 630 * - FRU Inventory area size in bytes, 631 * - access bit 632 **/ 633 ipmi::RspType<uint16_t, // FRU Inventory area size in bytes, 634 uint8_t // access size (bytes / words) 635 > 636 ipmiStorageGetFruInvAreaInfo(uint8_t fruID) 637 { 638 639 auto iter = frus.find(fruID); 640 if (iter == frus.end()) 641 { 642 return ipmi::responseSensorInvalid(); 643 } 644 645 try 646 { 647 return ipmi::responseSuccess( 648 static_cast<uint16_t>(getFruAreaData(fruID).size()), 649 static_cast<uint8_t>(AccessMode::bytes)); 650 } 651 catch (const InternalFailure& e) 652 { 653 log<level::ERR>(e.what()); 654 return ipmi::responseUnspecifiedError(); 655 } 656 } 657 658 // Read FRU data 659 ipmi_ret_t ipmi_storage_read_fru_data(ipmi_netfn_t netfn, ipmi_cmd_t cmd, 660 ipmi_request_t request, 661 ipmi_response_t response, 662 ipmi_data_len_t data_len, 663 ipmi_context_t context) 664 { 665 ipmi_ret_t rc = IPMI_CC_OK; 666 const ReadFruDataRequest* reqptr = 667 reinterpret_cast<const ReadFruDataRequest*>(request); 668 auto resptr = reinterpret_cast<ReadFruDataResponse*>(response); 669 670 auto iter = frus.find(reqptr->fruID); 671 if (iter == frus.end()) 672 { 673 *data_len = 0; 674 return IPMI_CC_SENSOR_INVALID; 675 } 676 677 auto offset = 678 static_cast<uint16_t>(reqptr->offsetMS << 8 | reqptr->offsetLS); 679 try 680 { 681 const auto& fruArea = getFruAreaData(reqptr->fruID); 682 auto size = fruArea.size(); 683 684 if (offset >= size) 685 { 686 return IPMI_CC_PARM_OUT_OF_RANGE; 687 } 688 689 // Write the count of response data. 690 if ((offset + reqptr->count) <= size) 691 { 692 resptr->count = reqptr->count; 693 } 694 else 695 { 696 resptr->count = size - offset; 697 } 698 699 std::copy((fruArea.begin() + offset), 700 (fruArea.begin() + offset + resptr->count), resptr->data); 701 702 *data_len = resptr->count + 1; // additional one byte for count 703 } 704 catch (const InternalFailure& e) 705 { 706 rc = IPMI_CC_UNSPECIFIED_ERROR; 707 *data_len = 0; 708 log<level::ERR>(e.what()); 709 } 710 return rc; 711 } 712 713 ipmi::RspType<uint8_t, // SDR version 714 uint16_t, // record count LS first 715 uint16_t, // free space in bytes, LS first 716 uint32_t, // addition timestamp LS first 717 uint32_t, // deletion timestamp LS first 718 uint8_t> // operation Support 719 ipmiGetRepositoryInfo() 720 { 721 722 constexpr uint8_t sdrVersion = 0x51; 723 constexpr uint16_t freeSpace = 0xFFFF; 724 constexpr uint32_t additionTimestamp = 0x0; 725 constexpr uint32_t deletionTimestamp = 0x0; 726 constexpr uint8_t operationSupport = 0; 727 728 uint16_t records = frus.size() + sensors.size(); 729 730 return ipmi::responseSuccess(sdrVersion, records, freeSpace, 731 additionTimestamp, deletionTimestamp, 732 operationSupport); 733 } 734 735 void register_netfn_storage_functions() 736 { 737 // <Wildcard Command> 738 ipmi_register_callback(NETFUN_STORAGE, IPMI_CMD_WILDCARD, NULL, 739 ipmi_storage_wildcard, PRIVILEGE_USER); 740 741 // <Get SEL Info> 742 ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage, 743 ipmi::storage::cmdGetSelInfo, ipmi::Privilege::User, 744 ipmiStorageGetSelInfo); 745 746 // <Get SEL Time> 747 ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage, 748 ipmi::storage::cmdGetSelTime, ipmi::Privilege::User, 749 ipmiStorageGetSelTime); 750 751 // <Set SEL Time> 752 ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage, 753 ipmi::storage::cmdSetSelTime, 754 ipmi::Privilege::Operator, ipmiStorageSetSelTime); 755 756 // <Reserve SEL> 757 ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage, 758 ipmi::storage::cmdReserveSel, ipmi::Privilege::User, 759 ipmiStorageReserveSel); 760 // <Get SEL Entry> 761 ipmi_register_callback(NETFUN_STORAGE, IPMI_CMD_GET_SEL_ENTRY, NULL, 762 getSELEntry, PRIVILEGE_USER); 763 764 // <Delete SEL Entry> 765 ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage, 766 ipmi::storage::cmdDeleteSelEntry, 767 ipmi::Privilege::Operator, deleteSELEntry); 768 769 // <Add SEL Entry> 770 ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage, 771 ipmi::storage::cmdAddSelEntry, 772 ipmi::Privilege::Operator, ipmiStorageAddSEL); 773 774 // <Clear SEL> 775 ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage, 776 ipmi::storage::cmdClearSel, ipmi::Privilege::Operator, 777 clearSEL); 778 779 // <Get FRU Inventory Area Info> 780 ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage, 781 ipmi::storage::cmdGetFruInventoryAreaInfo, 782 ipmi::Privilege::User, ipmiStorageGetFruInvAreaInfo); 783 784 // <READ FRU Data> 785 ipmi_register_callback(NETFUN_STORAGE, IPMI_CMD_READ_FRU_DATA, NULL, 786 ipmi_storage_read_fru_data, PRIVILEGE_USER); 787 788 // <Get Repository Info> 789 ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage, 790 ipmi::storage::cmdGetSdrRepositoryInfo, 791 ipmi::Privilege::User, ipmiGetRepositoryInfo); 792 793 // <Reserve SDR Repository> 794 ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage, 795 ipmi::storage::cmdReserveSdrRepository, 796 ipmi::Privilege::User, ipmiSensorReserveSdr); 797 798 // <Get SDR> 799 ipmi_register_callback(NETFUN_STORAGE, IPMI_CMD_GET_SDR, nullptr, 800 ipmi_sen_get_sdr, PRIVILEGE_USER); 801 802 ipmi::fru::registerCallbackHandler(); 803 return; 804 } 805