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 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 ipmi_ret_t ipmi_storage_add_sel(ipmi_netfn_t netfn, ipmi_cmd_t cmd, 586 ipmi_request_t request, 587 ipmi_response_t response, 588 ipmi_data_len_t data_len, 589 ipmi_context_t context) 590 { 591 if (*data_len != sizeof(ipmi_add_sel_request_t)) 592 { 593 *data_len = 0; 594 return IPMI_CC_REQ_DATA_LEN_INVALID; 595 } 596 597 ipmi_ret_t rc = IPMI_CC_OK; 598 ipmi_add_sel_request_t* p = (ipmi_add_sel_request_t*)request; 599 uint16_t recordid; 600 601 // Per the IPMI spec, need to cancel the reservation when a SEL entry is 602 // added 603 cancelSELReservation(); 604 605 recordid = ((uint16_t)p->eventdata[1] << 8) | p->eventdata[2]; 606 607 *data_len = sizeof(recordid); 608 609 // Pack the actual response 610 std::memcpy(response, &p->eventdata[1], 2); 611 612 // Hostboot sends SEL with OEM record type 0xDE to indicate that there is 613 // a maintenance procedure associated with eSEL record. 614 static constexpr auto procedureType = 0xDE; 615 if (p->recordtype == procedureType) 616 { 617 // In the OEM record type 0xDE, byte 11 in the SEL record indicate the 618 // procedure number. 619 createProcedureLogEntry(p->sensortype); 620 } 621 622 return rc; 623 } 624 625 /** @brief implements the get FRU Inventory Area Info command 626 * 627 * @returns IPMI completion code plus response data 628 * - FRU Inventory area size in bytes, 629 * - access bit 630 **/ 631 ipmi::RspType<uint16_t, // FRU Inventory area size in bytes, 632 uint8_t // access size (bytes / words) 633 > 634 ipmiStorageGetFruInvAreaInfo(uint8_t fruID) 635 { 636 637 auto iter = frus.find(fruID); 638 if (iter == frus.end()) 639 { 640 return ipmi::responseSensorInvalid(); 641 } 642 643 try 644 { 645 return ipmi::responseSuccess( 646 static_cast<uint16_t>(getFruAreaData(fruID).size()), 647 static_cast<uint8_t>(AccessMode::bytes)); 648 } 649 catch (const InternalFailure& e) 650 { 651 log<level::ERR>(e.what()); 652 return ipmi::responseUnspecifiedError(); 653 } 654 } 655 656 // Read FRU data 657 ipmi_ret_t ipmi_storage_read_fru_data(ipmi_netfn_t netfn, ipmi_cmd_t cmd, 658 ipmi_request_t request, 659 ipmi_response_t response, 660 ipmi_data_len_t data_len, 661 ipmi_context_t context) 662 { 663 ipmi_ret_t rc = IPMI_CC_OK; 664 const ReadFruDataRequest* reqptr = 665 reinterpret_cast<const ReadFruDataRequest*>(request); 666 auto resptr = reinterpret_cast<ReadFruDataResponse*>(response); 667 668 auto iter = frus.find(reqptr->fruID); 669 if (iter == frus.end()) 670 { 671 *data_len = 0; 672 return IPMI_CC_SENSOR_INVALID; 673 } 674 675 auto offset = 676 static_cast<uint16_t>(reqptr->offsetMS << 8 | reqptr->offsetLS); 677 try 678 { 679 const auto& fruArea = getFruAreaData(reqptr->fruID); 680 auto size = fruArea.size(); 681 682 if (offset >= size) 683 { 684 return IPMI_CC_PARM_OUT_OF_RANGE; 685 } 686 687 // Write the count of response data. 688 if ((offset + reqptr->count) <= size) 689 { 690 resptr->count = reqptr->count; 691 } 692 else 693 { 694 resptr->count = size - offset; 695 } 696 697 std::copy((fruArea.begin() + offset), 698 (fruArea.begin() + offset + resptr->count), resptr->data); 699 700 *data_len = resptr->count + 1; // additional one byte for count 701 } 702 catch (const InternalFailure& e) 703 { 704 rc = IPMI_CC_UNSPECIFIED_ERROR; 705 *data_len = 0; 706 log<level::ERR>(e.what()); 707 } 708 return rc; 709 } 710 711 ipmi_ret_t ipmi_get_repository_info(ipmi_netfn_t netfn, ipmi_cmd_t cmd, 712 ipmi_request_t request, 713 ipmi_response_t response, 714 ipmi_data_len_t data_len, 715 ipmi_context_t context) 716 { 717 constexpr auto sdrVersion = 0x51; 718 auto responseData = reinterpret_cast<GetRepositoryInfoResponse*>(response); 719 720 std::memset(responseData, 0, sizeof(GetRepositoryInfoResponse)); 721 722 responseData->sdrVersion = sdrVersion; 723 724 uint16_t records = frus.size() + sensors.size(); 725 responseData->recordCountMs = records >> 8; 726 responseData->recordCountLs = records; 727 728 responseData->freeSpace[0] = 0xFF; 729 responseData->freeSpace[1] = 0xFF; 730 731 *data_len = sizeof(GetRepositoryInfoResponse); 732 733 return IPMI_CC_OK; 734 } 735 736 void register_netfn_storage_functions() 737 { 738 // <Wildcard Command> 739 ipmi_register_callback(NETFUN_STORAGE, IPMI_CMD_WILDCARD, NULL, 740 ipmi_storage_wildcard, PRIVILEGE_USER); 741 742 // <Get SEL Info> 743 ipmi_register_callback(NETFUN_STORAGE, IPMI_CMD_GET_SEL_INFO, NULL, 744 getSELInfo, PRIVILEGE_USER); 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_register_callback(NETFUN_STORAGE, IPMI_CMD_RESERVE_SEL, NULL, 758 ipmi_storage_reserve_sel, PRIVILEGE_USER); 759 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_register_callback(NETFUN_STORAGE, IPMI_CMD_ADD_SEL, NULL, 771 ipmi_storage_add_sel, PRIVILEGE_OPERATOR); 772 // <Clear SEL> 773 ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage, 774 ipmi::storage::cmdClearSel, ipmi::Privilege::Operator, 775 clearSEL); 776 777 // <Get FRU Inventory Area Info> 778 ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage, 779 ipmi::storage::cmdGetFruInventoryAreaInfo, 780 ipmi::Privilege::User, ipmiStorageGetFruInvAreaInfo); 781 782 // <Add READ FRU Data 783 ipmi_register_callback(NETFUN_STORAGE, IPMI_CMD_READ_FRU_DATA, NULL, 784 ipmi_storage_read_fru_data, PRIVILEGE_OPERATOR); 785 786 // <Get Repository Info> 787 ipmi_register_callback(NETFUN_STORAGE, IPMI_CMD_GET_REPOSITORY_INFO, 788 nullptr, ipmi_get_repository_info, PRIVILEGE_USER); 789 790 // <Reserve SDR Repository> 791 ipmi_register_callback(NETFUN_STORAGE, IPMI_CMD_RESERVE_SDR, nullptr, 792 ipmi_sen_reserve_sdr, PRIVILEGE_USER); 793 794 // <Get SDR> 795 ipmi_register_callback(NETFUN_STORAGE, IPMI_CMD_GET_SDR, nullptr, 796 ipmi_sen_get_sdr, PRIVILEGE_USER); 797 798 ipmi::fru::registerCallbackHandler(); 799 return; 800 } 801