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