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