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