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 BMC_TIME_PATH = "/xyz/openbmc_project/time/bmc"; 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.event.eventRecord.recordID + requestData->offset, 267 readLength); 268 *data_len = sizeof(record.nextRecordID) + readLength; 269 } 270 271 return IPMI_CC_OK; 272 } 273 274 /** @brief implements the delete SEL entry command 275 * @request 276 * - reservationID; // reservation ID. 277 * - selRecordID; // SEL record ID. 278 * 279 * @returns ipmi completion code plus response data 280 * - Record ID of the deleted record 281 */ 282 ipmi::RspType<uint16_t // deleted record ID 283 > 284 deleteSELEntry(uint16_t reservationID, uint16_t selRecordID) 285 { 286 287 namespace fs = std::filesystem; 288 289 if (!checkSELReservation(reservationID)) 290 { 291 return ipmi::responseInvalidReservationId(); 292 } 293 294 // Per the IPMI spec, need to cancel the reservation when a SEL entry is 295 // deleted 296 cancelSELReservation(); 297 298 try 299 { 300 ipmi::sel::readLoggingObjectPaths(cache::paths); 301 } 302 catch (const sdbusplus::exception::SdBusError& e) 303 { 304 // readLoggingObjectPaths will throw exception if there are no error 305 // log entries. 306 return ipmi::responseSensorInvalid(); 307 } 308 309 if (cache::paths.empty()) 310 { 311 return ipmi::responseSensorInvalid(); 312 } 313 314 ipmi::sel::ObjectPaths::const_iterator iter; 315 uint16_t delRecordID = 0; 316 317 if (selRecordID == ipmi::sel::firstEntry) 318 { 319 iter = cache::paths.begin(); 320 fs::path path(*iter); 321 delRecordID = static_cast<uint16_t>( 322 std::stoul(std::string(path.filename().c_str()))); 323 } 324 else if (selRecordID == ipmi::sel::lastEntry) 325 { 326 iter = cache::paths.end(); 327 fs::path path(*iter); 328 delRecordID = static_cast<uint16_t>( 329 std::stoul(std::string(path.filename().c_str()))); 330 } 331 else 332 { 333 std::string objPath = std::string(ipmi::sel::logBasePath) + "/" + 334 std::to_string(selRecordID); 335 336 iter = std::find(cache::paths.begin(), cache::paths.end(), objPath); 337 if (iter == cache::paths.end()) 338 { 339 return ipmi::responseSensorInvalid(); 340 } 341 delRecordID = selRecordID; 342 } 343 344 sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()}; 345 std::string service; 346 347 try 348 { 349 service = ipmi::getService(bus, ipmi::sel::logDeleteIntf, *iter); 350 } 351 catch (const std::runtime_error& e) 352 { 353 log<level::ERR>(e.what()); 354 return ipmi::responseUnspecifiedError(); 355 } 356 357 auto methodCall = bus.new_method_call(service.c_str(), (*iter).c_str(), 358 ipmi::sel::logDeleteIntf, "Delete"); 359 auto reply = bus.call(methodCall); 360 if (reply.is_method_error()) 361 { 362 return ipmi::responseUnspecifiedError(); 363 } 364 365 // Invalidate the cache of dbus entry objects. 366 cache::paths.clear(); 367 368 return ipmi::responseSuccess(delRecordID); 369 } 370 371 /** @brief implements the Clear SEL command 372 * @request 373 * - reservationID // Reservation ID. 374 * - clr // char array { 'C'(0x43h), 'L'(0x4Ch), 'R'(0x52h) } 375 * - eraseOperation; // requested operation. 376 * 377 * @returns ipmi completion code plus response data 378 * - erase status 379 */ 380 381 ipmi::RspType<uint8_t // erase status 382 > 383 clearSEL(uint16_t reservationID, const std::array<char, 3>& clr, 384 uint8_t eraseOperation) 385 { 386 static constexpr std::array<char, 3> clrOk = {'C', 'L', 'R'}; 387 if (clr != clrOk) 388 { 389 return ipmi::responseInvalidFieldRequest(); 390 } 391 392 if (!checkSELReservation(reservationID)) 393 { 394 return ipmi::responseInvalidReservationId(); 395 } 396 397 /* 398 * Erasure status cannot be fetched from DBUS, so always return erasure 399 * status as `erase completed`. 400 */ 401 if (eraseOperation == ipmi::sel::getEraseStatus) 402 { 403 return ipmi::responseSuccess( 404 static_cast<uint8_t>(ipmi::sel::eraseComplete)); 405 } 406 407 // Per the IPMI spec, need to cancel any reservation when the SEL is cleared 408 cancelSELReservation(); 409 410 sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()}; 411 ipmi::sel::ObjectPaths objectPaths; 412 auto depth = 0; 413 414 auto mapperCall = 415 bus.new_method_call(ipmi::sel::mapperBusName, ipmi::sel::mapperObjPath, 416 ipmi::sel::mapperIntf, "GetSubTreePaths"); 417 mapperCall.append(ipmi::sel::logBasePath); 418 mapperCall.append(depth); 419 mapperCall.append(ipmi::sel::ObjectPaths({ipmi::sel::logEntryIntf})); 420 421 try 422 { 423 auto reply = bus.call(mapperCall); 424 if (reply.is_method_error()) 425 { 426 return ipmi::responseSuccess( 427 static_cast<uint8_t>(ipmi::sel::eraseComplete)); 428 } 429 430 reply.read(objectPaths); 431 if (objectPaths.empty()) 432 { 433 return ipmi::responseSuccess( 434 static_cast<uint8_t>(ipmi::sel::eraseComplete)); 435 } 436 } 437 catch (const sdbusplus::exception::SdBusError& e) 438 { 439 return ipmi::responseSuccess( 440 static_cast<uint8_t>(ipmi::sel::eraseComplete)); 441 } 442 443 std::string service; 444 445 try 446 { 447 service = ipmi::getService(bus, ipmi::sel::logDeleteIntf, 448 objectPaths.front()); 449 } 450 catch (const std::runtime_error& e) 451 { 452 log<level::ERR>(e.what()); 453 return ipmi::responseUnspecifiedError(); 454 } 455 456 for (const auto& iter : objectPaths) 457 { 458 auto methodCall = bus.new_method_call( 459 service.c_str(), iter.c_str(), ipmi::sel::logDeleteIntf, "Delete"); 460 461 auto reply = bus.call(methodCall); 462 if (reply.is_method_error()) 463 { 464 return ipmi::responseUnspecifiedError(); 465 } 466 } 467 468 // Invalidate the cache of dbus entry objects. 469 cache::paths.clear(); 470 return ipmi::responseSuccess( 471 static_cast<uint8_t>(ipmi::sel::eraseComplete)); 472 } 473 474 /** @brief implements the get SEL time command 475 * @returns IPMI completion code plus response data 476 * -current time 477 */ 478 ipmi::RspType<uint32_t> // current time 479 ipmiStorageGetSelTime() 480 { 481 using namespace std::chrono; 482 uint64_t bmc_time_usec = 0; 483 std::stringstream bmcTime; 484 485 try 486 { 487 sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()}; 488 auto service = ipmi::getService(bus, TIME_INTERFACE, BMC_TIME_PATH); 489 std::variant<uint64_t> value; 490 491 // Get bmc time 492 auto method = bus.new_method_call(service.c_str(), BMC_TIME_PATH, 493 DBUS_PROPERTIES, "Get"); 494 495 method.append(TIME_INTERFACE, PROPERTY_ELAPSED); 496 auto reply = bus.call(method); 497 if (reply.is_method_error()) 498 { 499 log<level::ERR>("Error getting time", 500 entry("SERVICE=%s", service.c_str()), 501 entry("PATH=%s", BMC_TIME_PATH)); 502 return ipmi::responseUnspecifiedError(); 503 } 504 reply.read(value); 505 bmc_time_usec = std::get<uint64_t>(value); 506 } 507 catch (InternalFailure& e) 508 { 509 log<level::ERR>(e.what()); 510 return ipmi::responseUnspecifiedError(); 511 } 512 catch (const std::exception& e) 513 { 514 log<level::ERR>(e.what()); 515 return ipmi::responseUnspecifiedError(); 516 } 517 518 bmcTime << "BMC time:" 519 << duration_cast<seconds>(microseconds(bmc_time_usec)).count(); 520 log<level::DEBUG>(bmcTime.str().c_str()); 521 522 // Time is really long int but IPMI wants just uint32. This works okay until 523 // the number of seconds since 1970 overflows uint32 size.. Still a whole 524 // lot of time here to even think about that. 525 return ipmi::responseSuccess( 526 duration_cast<seconds>(microseconds(bmc_time_usec)).count()); 527 } 528 529 /** @brief implements the set SEL time command 530 * @param selDeviceTime - epoch time 531 * -local time as the number of seconds from 00:00:00, January 1, 1970 532 * @returns IPMI completion code 533 */ 534 ipmi::RspType<> ipmiStorageSetSelTime(uint32_t selDeviceTime) 535 { 536 using namespace std::chrono; 537 microseconds usec{seconds(selDeviceTime)}; 538 539 try 540 { 541 sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()}; 542 auto service = ipmi::getService(bus, TIME_INTERFACE, BMC_TIME_PATH); 543 std::variant<uint64_t> value{(uint64_t)usec.count()}; 544 545 // Set bmc time 546 auto method = bus.new_method_call(service.c_str(), BMC_TIME_PATH, 547 DBUS_PROPERTIES, "Set"); 548 549 method.append(TIME_INTERFACE, PROPERTY_ELAPSED, value); 550 auto reply = bus.call(method); 551 if (reply.is_method_error()) 552 { 553 log<level::ERR>("Error setting time", 554 entry("SERVICE=%s", service.c_str()), 555 entry("PATH=%s", BMC_TIME_PATH)); 556 return ipmi::responseUnspecifiedError(); 557 } 558 } 559 catch (InternalFailure& e) 560 { 561 log<level::ERR>(e.what()); 562 return ipmi::responseUnspecifiedError(); 563 } 564 catch (const std::exception& e) 565 { 566 log<level::ERR>(e.what()); 567 return ipmi::responseUnspecifiedError(); 568 } 569 570 return ipmi::responseSuccess(); 571 } 572 573 /** @brief implements the reserve SEL command 574 * @returns IPMI completion code plus response data 575 * - SEL reservation ID. 576 */ 577 ipmi::RspType<uint16_t> ipmiStorageReserveSel() 578 { 579 return ipmi::responseSuccess(reserveSel()); 580 } 581 582 /** @brief implements the Add SEL entry command 583 * @request 584 * 585 * - recordID ID used for SEL Record access 586 * - recordType Record Type 587 * - timeStamp Time when event was logged. LS byte first 588 * - generatorID software ID if event was generated from 589 * system software 590 * - evmRev event message format version 591 * - sensorType sensor type code for service that generated 592 * the event 593 * - sensorNumber number of sensors that generated the event 594 * - eventDir event dir 595 * - eventData event data field contents 596 * 597 * @returns ipmi completion code plus response data 598 * - RecordID of the Added SEL entry 599 */ 600 ipmi::RspType<uint16_t // recordID of the Added SEL entry 601 > 602 ipmiStorageAddSEL(uint16_t recordID, uint8_t recordType, uint32_t timeStamp, 603 uint16_t generatorID, uint8_t evmRev, uint8_t sensorType, 604 uint8_t sensorNumber, uint8_t eventDir, 605 std::array<uint8_t, eventDataSize> eventData) 606 { 607 // Per the IPMI spec, need to cancel the reservation when a SEL entry is 608 // added 609 cancelSELReservation(); 610 // Hostboot sends SEL with OEM record type 0xDE to indicate that there is 611 // a maintenance procedure associated with eSEL record. 612 static constexpr auto procedureType = 0xDE; 613 if (recordType == procedureType) 614 { 615 // In the OEM record type 0xDE, byte 11 in the SEL record indicate the 616 // procedure number. 617 createProcedureLogEntry(sensorType); 618 } 619 620 return ipmi::responseSuccess(recordID); 621 } 622 623 bool isFruPresent(const std::string& fruPath) 624 { 625 using namespace ipmi::fru; 626 627 sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()}; 628 629 auto propValue = 630 ipmi::getDbusProperty(bus, invMgrInterface, invObjPath + fruPath, 631 invItemInterface, itemPresentProp); 632 633 return std::get<bool>(propValue); 634 } 635 636 /** @brief implements the get FRU Inventory Area Info command 637 * 638 * @returns IPMI completion code plus response data 639 * - FRU Inventory area size in bytes, 640 * - access bit 641 **/ 642 ipmi::RspType<uint16_t, // FRU Inventory area size in bytes, 643 uint8_t // access size (bytes / words) 644 > 645 ipmiStorageGetFruInvAreaInfo(uint8_t fruID) 646 { 647 648 auto iter = frus.find(fruID); 649 if (iter == frus.end()) 650 { 651 return ipmi::responseSensorInvalid(); 652 } 653 654 auto path = iter->second[0].path; 655 if (!isFruPresent(path)) 656 { 657 return ipmi::responseSensorInvalid(); 658 } 659 660 try 661 { 662 return ipmi::responseSuccess( 663 static_cast<uint16_t>(getFruAreaData(fruID).size()), 664 static_cast<uint8_t>(AccessMode::bytes)); 665 } 666 catch (const InternalFailure& e) 667 { 668 log<level::ERR>(e.what()); 669 return ipmi::responseUnspecifiedError(); 670 } 671 } 672 673 /**@brief implements the Read FRU Data command 674 * @param fruDeviceId - FRU device ID. FFh = reserved 675 * @param offset - FRU inventory offset to read 676 * @param readCount - count to read 677 * 678 * @return IPMI completion code plus response data 679 * - returnCount - response data count. 680 * - data - response data 681 */ 682 ipmi::RspType<uint8_t, // count returned 683 std::vector<uint8_t>> // FRU data 684 ipmiStorageReadFruData(uint8_t fruDeviceId, uint16_t offset, 685 uint8_t readCount) 686 { 687 if (fruDeviceId == 0xFF) 688 { 689 return ipmi::responseInvalidFieldRequest(); 690 } 691 692 auto iter = frus.find(fruDeviceId); 693 if (iter == frus.end()) 694 { 695 return ipmi::responseSensorInvalid(); 696 } 697 698 try 699 { 700 const auto& fruArea = getFruAreaData(fruDeviceId); 701 auto size = fruArea.size(); 702 703 if (offset >= size) 704 { 705 return ipmi::responseParmOutOfRange(); 706 } 707 708 // Write the count of response data. 709 uint8_t returnCount; 710 if ((offset + readCount) <= size) 711 { 712 returnCount = readCount; 713 } 714 else 715 { 716 returnCount = size - offset; 717 } 718 719 std::vector<uint8_t> fruData((fruArea.begin() + offset), 720 (fruArea.begin() + offset + returnCount)); 721 722 return ipmi::responseSuccess(returnCount, fruData); 723 } 724 catch (const InternalFailure& e) 725 { 726 log<level::ERR>(e.what()); 727 return ipmi::responseUnspecifiedError(); 728 } 729 } 730 731 ipmi::RspType<uint8_t, // SDR version 732 uint16_t, // record count LS first 733 uint16_t, // free space in bytes, LS first 734 uint32_t, // addition timestamp LS first 735 uint32_t, // deletion timestamp LS first 736 uint8_t> // operation Support 737 ipmiGetRepositoryInfo() 738 { 739 740 constexpr uint8_t sdrVersion = 0x51; 741 constexpr uint16_t freeSpace = 0xFFFF; 742 constexpr uint32_t additionTimestamp = 0x0; 743 constexpr uint32_t deletionTimestamp = 0x0; 744 constexpr uint8_t operationSupport = 0; 745 746 uint16_t records = frus.size() + ipmi::sensor::sensors.size(); 747 748 return ipmi::responseSuccess(sdrVersion, records, freeSpace, 749 additionTimestamp, deletionTimestamp, 750 operationSupport); 751 } 752 753 void register_netfn_storage_functions() 754 { 755 // <Get SEL Info> 756 ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage, 757 ipmi::storage::cmdGetSelInfo, ipmi::Privilege::User, 758 ipmiStorageGetSelInfo); 759 760 // <Get SEL Time> 761 ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage, 762 ipmi::storage::cmdGetSelTime, ipmi::Privilege::User, 763 ipmiStorageGetSelTime); 764 765 // <Set SEL Time> 766 ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage, 767 ipmi::storage::cmdSetSelTime, 768 ipmi::Privilege::Operator, ipmiStorageSetSelTime); 769 770 // <Reserve SEL> 771 ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage, 772 ipmi::storage::cmdReserveSel, ipmi::Privilege::User, 773 ipmiStorageReserveSel); 774 // <Get SEL Entry> 775 ipmi_register_callback(NETFUN_STORAGE, IPMI_CMD_GET_SEL_ENTRY, NULL, 776 getSELEntry, PRIVILEGE_USER); 777 778 // <Delete SEL Entry> 779 ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage, 780 ipmi::storage::cmdDeleteSelEntry, 781 ipmi::Privilege::Operator, deleteSELEntry); 782 783 // <Add SEL Entry> 784 ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage, 785 ipmi::storage::cmdAddSelEntry, 786 ipmi::Privilege::Operator, ipmiStorageAddSEL); 787 788 // <Clear SEL> 789 ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage, 790 ipmi::storage::cmdClearSel, ipmi::Privilege::Operator, 791 clearSEL); 792 793 // <Get FRU Inventory Area Info> 794 ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage, 795 ipmi::storage::cmdGetFruInventoryAreaInfo, 796 ipmi::Privilege::User, ipmiStorageGetFruInvAreaInfo); 797 798 // <READ FRU Data> 799 ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage, 800 ipmi::storage::cmdReadFruData, 801 ipmi::Privilege::Operator, ipmiStorageReadFruData); 802 803 // <Get Repository Info> 804 ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage, 805 ipmi::storage::cmdGetSdrRepositoryInfo, 806 ipmi::Privilege::User, ipmiGetRepositoryInfo); 807 808 // <Reserve SDR Repository> 809 ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage, 810 ipmi::storage::cmdReserveSdrRepository, 811 ipmi::Privilege::User, ipmiSensorReserveSdr); 812 813 // <Get SDR> 814 ipmi_register_callback(NETFUN_STORAGE, IPMI_CMD_GET_SDR, nullptr, 815 ipmi_sen_get_sdr, PRIVILEGE_USER); 816 817 ipmi::fru::registerCallbackHandler(); 818 return; 819 } 820