1 #include <algorithm> 2 #include <arpa/inet.h> 3 #include <chrono> 4 #include <cstdio> 5 #include <experimental/filesystem> 6 #include <mapper.h> 7 #include <string> 8 #include <systemd/sd-bus.h> 9 10 #include <phosphor-logging/log.hpp> 11 #include <phosphor-logging/elog-errors.hpp> 12 #include <sdbusplus/server.hpp> 13 14 #include "fruread.hpp" 15 #include "host-ipmid/ipmid-api.h" 16 #include "read_fru_data.hpp" 17 #include "selutility.hpp" 18 #include "storageaddsel.h" 19 #include "storagehandler.h" 20 #include "utils.hpp" 21 #include "xyz/openbmc_project/Common/error.hpp" 22 #include "sensorhandler.h" 23 24 25 void register_netfn_storage_functions() __attribute__((constructor)); 26 27 unsigned int g_sel_time = 0xFFFFFFFF; 28 extern unsigned short g_sel_reserve; 29 extern const ipmi::sensor::IdInfoMap sensors; 30 extern const FruMap frus; 31 32 namespace { 33 constexpr auto TIME_INTERFACE = "xyz.openbmc_project.Time.EpochTime"; 34 constexpr auto HOST_TIME_PATH = "/xyz/openbmc_project/time/host"; 35 constexpr auto DBUS_PROPERTIES = "org.freedesktop.DBus.Properties"; 36 constexpr auto PROPERTY_ELAPSED= "Elapsed"; 37 38 const char* getTimeString(const uint64_t& usecSinceEpoch) 39 { 40 using namespace std::chrono; 41 system_clock::time_point tp{microseconds(usecSinceEpoch)}; 42 auto t = system_clock::to_time_t(tp); 43 return std::ctime(&t); 44 } 45 } 46 47 namespace cache 48 { 49 /* 50 * This cache contains the object paths of the logging entries sorted in the 51 * order of the filename(numeric order). The cache is initialized by 52 * invoking readLoggingObjectPaths with the cache as the parameter. The 53 * cache is invoked in the execution of the Get SEL info and Delete SEL 54 * entry command. The Get SEL Info command is typically invoked before the 55 * Get SEL entry command, so the cache is utilized for responding to Get SEL 56 * entry command. The cache is invalidated by clearing after Delete SEL 57 * entry and Clear SEL command. 58 */ 59 ipmi::sel::ObjectPaths paths; 60 61 } // namespace objectPathsCache 62 63 using InternalFailure = 64 sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure; 65 using namespace phosphor::logging; 66 using namespace ipmi::fru; 67 68 /** 69 * @enum Device access mode 70 */ 71 enum class AccessMode 72 { 73 bytes, ///< Device is accessed by bytes 74 words ///< Device is accessed by words 75 }; 76 77 78 ipmi_ret_t ipmi_storage_wildcard(ipmi_netfn_t netfn, ipmi_cmd_t cmd, 79 ipmi_request_t request, ipmi_response_t response, 80 ipmi_data_len_t data_len, ipmi_context_t context) 81 { 82 // Status code. 83 ipmi_ret_t rc = IPMI_CC_INVALID; 84 *data_len = 0; 85 return rc; 86 } 87 88 ipmi_ret_t getSELInfo(ipmi_netfn_t netfn, ipmi_cmd_t cmd, 89 ipmi_request_t request, ipmi_response_t response, 90 ipmi_data_len_t data_len, ipmi_context_t context) 91 { 92 std::vector<uint8_t> outPayload(sizeof(ipmi::sel::GetSELInfoResponse)); 93 auto responseData = reinterpret_cast<ipmi::sel::GetSELInfoResponse*> 94 (outPayload.data()); 95 96 responseData->selVersion = ipmi::sel::selVersion; 97 // Last erase timestamp is not available from log manager. 98 responseData->eraseTimeStamp = ipmi::sel::invalidTimeStamp; 99 responseData->operationSupport = ipmi::sel::operationSupport; 100 101 ipmi::sel::readLoggingObjectPaths(cache::paths); 102 responseData->entries = 0; 103 responseData->addTimeStamp = ipmi::sel::invalidTimeStamp; 104 105 if (!cache::paths.empty()) 106 { 107 responseData->entries = static_cast<uint16_t>(cache::paths.size()); 108 109 try 110 { 111 responseData->addTimeStamp = static_cast<uint32_t>( 112 (ipmi::sel::getEntryTimeStamp(cache::paths.back()) 113 .count())); 114 } 115 catch (InternalFailure& e) 116 { 117 } 118 catch (const std::runtime_error& e) 119 { 120 log<level::ERR>(e.what()); 121 } 122 } 123 124 memcpy(response, outPayload.data(), outPayload.size()); 125 *data_len = outPayload.size(); 126 127 return IPMI_CC_OK; 128 } 129 130 ipmi_ret_t getSELEntry(ipmi_netfn_t netfn, ipmi_cmd_t cmd, 131 ipmi_request_t request, ipmi_response_t response, 132 ipmi_data_len_t data_len, ipmi_context_t context) 133 { 134 auto requestData = reinterpret_cast<const ipmi::sel::GetSELEntryRequest*> 135 (request); 136 137 if (requestData->reservationID != 0) 138 { 139 if (g_sel_reserve != requestData->reservationID) 140 { 141 *data_len = 0; 142 return IPMI_CC_INVALID_RESERVATION_ID; 143 } 144 } 145 146 if (cache::paths.empty()) 147 { 148 *data_len = 0; 149 return IPMI_CC_SENSOR_INVALID; 150 } 151 152 ipmi::sel::ObjectPaths::const_iterator iter; 153 154 // Check for the requested SEL Entry. 155 if (requestData->selRecordID == ipmi::sel::firstEntry) 156 { 157 iter = cache::paths.begin(); 158 } 159 else if (requestData->selRecordID == ipmi::sel::lastEntry) 160 { 161 iter = cache::paths.end(); 162 } 163 else 164 { 165 std::string objPath = std::string(ipmi::sel::logBasePath) + "/" + 166 std::to_string(requestData->selRecordID); 167 168 iter = std::find(cache::paths.begin(), cache::paths.end(), objPath); 169 if (iter == cache::paths.end()) 170 { 171 *data_len = 0; 172 return IPMI_CC_SENSOR_INVALID; 173 } 174 } 175 176 ipmi::sel::GetSELEntryResponse record {}; 177 178 // Convert the log entry into SEL record. 179 try 180 { 181 record = ipmi::sel::convertLogEntrytoSEL(*iter); 182 } 183 catch (InternalFailure& e) 184 { 185 *data_len = 0; 186 return IPMI_CC_UNSPECIFIED_ERROR; 187 } 188 catch (const std::runtime_error& e) 189 { 190 log<level::ERR>(e.what()); 191 *data_len = 0; 192 return IPMI_CC_UNSPECIFIED_ERROR; 193 } 194 195 196 // Identify the next SEL record ID 197 if(iter != cache::paths.end()) 198 { 199 ++iter; 200 if (iter == cache::paths.end()) 201 { 202 record.nextRecordID = ipmi::sel::lastEntry; 203 } 204 else 205 { 206 namespace fs = std::experimental::filesystem; 207 fs::path path(*iter); 208 record.nextRecordID = static_cast<uint16_t> 209 (std::stoul(std::string(path.filename().c_str()))); 210 } 211 } 212 else 213 { 214 record.nextRecordID = ipmi::sel::lastEntry; 215 } 216 217 if (requestData->readLength == ipmi::sel::entireRecord) 218 { 219 memcpy(response, &record, sizeof(record)); 220 *data_len = sizeof(record); 221 } 222 else 223 { 224 if (requestData->offset >= ipmi::sel::selRecordSize || 225 requestData->readLength > ipmi::sel::selRecordSize) 226 { 227 *data_len = 0; 228 return IPMI_CC_INVALID_FIELD_REQUEST; 229 } 230 231 auto diff = ipmi::sel::selRecordSize - requestData->offset; 232 auto readLength = std::min(diff, 233 static_cast<int>(requestData->readLength)); 234 235 memcpy(response, &record.nextRecordID, sizeof(record.nextRecordID)); 236 memcpy(static_cast<uint8_t*>(response) + sizeof(record.nextRecordID), 237 &record.recordID + requestData->offset, readLength); 238 *data_len = sizeof(record.nextRecordID) + readLength; 239 } 240 241 return IPMI_CC_OK; 242 } 243 244 ipmi_ret_t deleteSELEntry(ipmi_netfn_t netfn, ipmi_cmd_t cmd, 245 ipmi_request_t request, ipmi_response_t response, 246 ipmi_data_len_t data_len, ipmi_context_t context) 247 { 248 namespace fs = std::experimental::filesystem; 249 auto requestData = reinterpret_cast<const ipmi::sel::DeleteSELEntryRequest*> 250 (request); 251 252 if (g_sel_reserve != requestData->reservationID) 253 { 254 *data_len = 0; 255 return IPMI_CC_INVALID_RESERVATION_ID; 256 } 257 258 ipmi::sel::readLoggingObjectPaths(cache::paths); 259 260 if (cache::paths.empty()) 261 { 262 *data_len = 0; 263 return IPMI_CC_SENSOR_INVALID; 264 } 265 266 ipmi::sel::ObjectPaths::const_iterator iter; 267 uint16_t delRecordID = 0; 268 269 if (requestData->selRecordID == ipmi::sel::firstEntry) 270 { 271 iter = cache::paths.begin(); 272 fs::path path(*iter); 273 delRecordID = static_cast<uint16_t> 274 (std::stoul(std::string(path.filename().c_str()))); 275 } 276 else if (requestData->selRecordID == ipmi::sel::lastEntry) 277 { 278 iter = cache::paths.end(); 279 fs::path path(*iter); 280 delRecordID = static_cast<uint16_t> 281 (std::stoul(std::string(path.filename().c_str()))); 282 } 283 else 284 { 285 std::string objPath = std::string(ipmi::sel::logBasePath) + "/" + 286 std::to_string(requestData->selRecordID); 287 288 iter = std::find(cache::paths.begin(), cache::paths.end(), objPath); 289 if (iter == cache::paths.end()) 290 { 291 *data_len = 0; 292 return IPMI_CC_SENSOR_INVALID; 293 } 294 delRecordID = requestData->selRecordID; 295 } 296 297 sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()}; 298 std::string service; 299 300 try 301 { 302 service = ipmi::getService(bus, ipmi::sel::logDeleteIntf, *iter); 303 } 304 catch (const std::runtime_error& e) 305 { 306 log<level::ERR>(e.what()); 307 *data_len = 0; 308 return IPMI_CC_UNSPECIFIED_ERROR; 309 } 310 311 auto methodCall = bus.new_method_call(service.c_str(), 312 (*iter).c_str(), 313 ipmi::sel::logDeleteIntf, 314 "Delete"); 315 auto reply = bus.call(methodCall); 316 if (reply.is_method_error()) 317 { 318 *data_len = 0; 319 return IPMI_CC_UNSPECIFIED_ERROR; 320 } 321 322 // Invalidate the cache of dbus entry objects. 323 cache::paths.clear(); 324 memcpy(response, &delRecordID, sizeof(delRecordID)); 325 *data_len = sizeof(delRecordID); 326 327 return IPMI_CC_OK; 328 } 329 330 ipmi_ret_t clearSEL(ipmi_netfn_t netfn, ipmi_cmd_t cmd, ipmi_request_t request, 331 ipmi_response_t response, ipmi_data_len_t data_len, 332 ipmi_context_t context) 333 { 334 auto requestData = reinterpret_cast<const ipmi::sel::ClearSELRequest*> 335 (request); 336 337 if (g_sel_reserve != requestData->reservationID) 338 { 339 *data_len = 0; 340 return IPMI_CC_INVALID_RESERVATION_ID; 341 } 342 343 if (requestData->charC != 'C' || 344 requestData->charL != 'L' || 345 requestData->charR != 'R') 346 { 347 *data_len = 0; 348 return IPMI_CC_INVALID_FIELD_REQUEST; 349 } 350 351 uint8_t eraseProgress = ipmi::sel::eraseComplete; 352 353 /* 354 * Erasure status cannot be fetched from DBUS, so always return erasure 355 * status as `erase completed`. 356 */ 357 if (requestData->eraseOperation == ipmi::sel::getEraseStatus) 358 { 359 memcpy(response, &eraseProgress, sizeof(eraseProgress)); 360 *data_len = sizeof(eraseProgress); 361 return IPMI_CC_OK; 362 } 363 364 sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()}; 365 auto depth = 0; 366 367 auto mapperCall = bus.new_method_call(ipmi::sel::mapperBusName, 368 ipmi::sel::mapperObjPath, 369 ipmi::sel::mapperIntf, 370 "GetSubTreePaths"); 371 mapperCall.append(ipmi::sel::logBasePath); 372 mapperCall.append(depth); 373 mapperCall.append(ipmi::sel::ObjectPaths({ipmi::sel::logEntryIntf})); 374 375 auto reply = bus.call(mapperCall); 376 if (reply.is_method_error()) 377 { 378 memcpy(response, &eraseProgress, sizeof(eraseProgress)); 379 *data_len = sizeof(eraseProgress); 380 return IPMI_CC_OK; 381 } 382 383 ipmi::sel::ObjectPaths objectPaths; 384 reply.read(objectPaths); 385 if (objectPaths.empty()) 386 { 387 memcpy(response, &eraseProgress, sizeof(eraseProgress)); 388 *data_len = sizeof(eraseProgress); 389 return IPMI_CC_OK; 390 } 391 392 std::string service; 393 394 try 395 { 396 service = ipmi::getService(bus, 397 ipmi::sel::logDeleteIntf, 398 objectPaths.front()); 399 } 400 catch (const std::runtime_error& e) 401 { 402 log<level::ERR>(e.what()); 403 *data_len = 0; 404 return IPMI_CC_UNSPECIFIED_ERROR; 405 } 406 407 for (const auto& iter : objectPaths) 408 { 409 auto methodCall = bus.new_method_call(service.c_str(), 410 iter.c_str(), 411 ipmi::sel::logDeleteIntf, 412 "Delete"); 413 414 auto reply = bus.call(methodCall); 415 if (reply.is_method_error()) 416 { 417 *data_len = 0; 418 return IPMI_CC_UNSPECIFIED_ERROR; 419 } 420 } 421 422 // Invalidate the cache of dbus entry objects. 423 cache::paths.clear(); 424 memcpy(response, &eraseProgress, sizeof(eraseProgress)); 425 *data_len = sizeof(eraseProgress); 426 return IPMI_CC_OK; 427 } 428 429 ipmi_ret_t ipmi_storage_get_sel_time(ipmi_netfn_t netfn, ipmi_cmd_t cmd, 430 ipmi_request_t request, ipmi_response_t response, 431 ipmi_data_len_t data_len, ipmi_context_t context) 432 { 433 using namespace std::chrono; 434 uint64_t host_time_usec = 0; 435 uint32_t resp = 0; 436 std::stringstream hostTime; 437 438 try 439 { 440 sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()}; 441 auto service = ipmi::getService(bus, TIME_INTERFACE, HOST_TIME_PATH); 442 sdbusplus::message::variant<uint64_t> value; 443 444 // Get host time 445 auto method = bus.new_method_call(service.c_str(), 446 HOST_TIME_PATH, 447 DBUS_PROPERTIES, 448 "Get"); 449 450 method.append(TIME_INTERFACE, PROPERTY_ELAPSED); 451 auto reply = bus.call(method); 452 if (reply.is_method_error()) 453 { 454 log<level::ERR>("Error getting time", 455 entry("SERVICE=%s", service.c_str()), 456 entry("PATH=%s", HOST_TIME_PATH)); 457 return IPMI_CC_UNSPECIFIED_ERROR; 458 } 459 reply.read(value); 460 host_time_usec = value.get<uint64_t>(); 461 } 462 catch (InternalFailure& e) 463 { 464 log<level::ERR>(e.what()); 465 return IPMI_CC_UNSPECIFIED_ERROR; 466 } 467 catch (const std::runtime_error& e) 468 { 469 log<level::ERR>(e.what()); 470 return IPMI_CC_UNSPECIFIED_ERROR; 471 } 472 473 hostTime << "Host time:" << getTimeString(host_time_usec); 474 log<level::DEBUG>(hostTime.str().c_str()); 475 476 477 // Time is really long int but IPMI wants just uint32. This works okay until 478 // the number of seconds since 1970 overflows uint32 size.. Still a whole 479 // lot of time here to even think about that. 480 resp = duration_cast<seconds>(microseconds(host_time_usec)).count(); 481 resp = htole32(resp); 482 483 // From the IPMI Spec 2.0, response should be a 32-bit value 484 *data_len = sizeof(resp); 485 486 // Pack the actual response 487 memcpy(response, &resp, *data_len); 488 489 return IPMI_CC_OK; 490 } 491 492 ipmi_ret_t ipmi_storage_set_sel_time(ipmi_netfn_t netfn, ipmi_cmd_t cmd, 493 ipmi_request_t request, ipmi_response_t response, 494 ipmi_data_len_t data_len, ipmi_context_t context) 495 { 496 using namespace std::chrono; 497 ipmi_ret_t rc = IPMI_CC_OK; 498 uint32_t secs = *static_cast<uint32_t*>(request); 499 *data_len = 0; 500 501 secs = le32toh(secs); 502 microseconds usec{seconds(secs)}; 503 504 try 505 { 506 sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()}; 507 auto service = ipmi::getService(bus, TIME_INTERFACE, HOST_TIME_PATH); 508 sdbusplus::message::variant<uint64_t> value{usec.count()}; 509 510 // Set host time 511 auto method = bus.new_method_call(service.c_str(), 512 HOST_TIME_PATH, 513 DBUS_PROPERTIES, 514 "Set"); 515 516 method.append(TIME_INTERFACE, PROPERTY_ELAPSED, value); 517 auto reply = bus.call(method); 518 if (reply.is_method_error()) 519 { 520 log<level::ERR>("Error setting time", 521 entry("SERVICE=%s", service.c_str()), 522 entry("PATH=%s", HOST_TIME_PATH)); 523 rc = IPMI_CC_UNSPECIFIED_ERROR; 524 } 525 } 526 catch (InternalFailure& e) 527 { 528 log<level::ERR>(e.what()); 529 rc = IPMI_CC_UNSPECIFIED_ERROR; 530 } 531 catch (const std::runtime_error& e) 532 { 533 log<level::ERR>(e.what()); 534 rc = IPMI_CC_UNSPECIFIED_ERROR; 535 } 536 537 return rc; 538 } 539 540 ipmi_ret_t ipmi_storage_reserve_sel(ipmi_netfn_t netfn, ipmi_cmd_t cmd, 541 ipmi_request_t request, ipmi_response_t response, 542 ipmi_data_len_t data_len, ipmi_context_t context) 543 { 544 ipmi_ret_t rc = IPMI_CC_OK; 545 546 // IPMI spec, Reservation ID, the value simply increases against each execution of reserve_sel command. 547 if( ++g_sel_reserve == 0) 548 g_sel_reserve = 1; 549 550 *data_len = sizeof(g_sel_reserve); 551 552 // Pack the actual response 553 memcpy(response, &g_sel_reserve, *data_len); 554 555 return rc; 556 } 557 558 ipmi_ret_t ipmi_storage_add_sel(ipmi_netfn_t netfn, ipmi_cmd_t cmd, 559 ipmi_request_t request, ipmi_response_t response, 560 ipmi_data_len_t data_len, ipmi_context_t context) 561 { 562 563 ipmi_ret_t rc = IPMI_CC_OK; 564 ipmi_add_sel_request_t *p = (ipmi_add_sel_request_t*) request; 565 uint16_t recordid; 566 567 recordid = ((uint16_t)p->eventdata[1] << 8) | p->eventdata[2]; 568 569 *data_len = sizeof(g_sel_reserve); 570 571 // Pack the actual response 572 memcpy(response, &p->eventdata[1], 2); 573 574 // Hostboot sends SEL with OEM record type 0xDE to indicate that there is 575 // a maintenance procedure associated with eSEL record. 576 static constexpr auto procedureType = 0xDE; 577 if (p->recordtype == procedureType) 578 { 579 // In the OEM record type 0xDE, byte 11 in the SEL record indicate the 580 // procedure number. 581 createProcedureLogEntry(p->sensortype); 582 } 583 else 584 { 585 send_esel(recordid); 586 } 587 588 return rc; 589 } 590 591 //Read FRU info area 592 ipmi_ret_t ipmi_storage_get_fru_inv_area_info( 593 ipmi_netfn_t netfn, ipmi_cmd_t cmd, ipmi_request_t request, 594 ipmi_response_t response, ipmi_data_len_t data_len, 595 ipmi_context_t context) 596 { 597 ipmi_ret_t rc = IPMI_CC_OK; 598 const FruInvenAreaInfoRequest* reqptr = 599 reinterpret_cast<const FruInvenAreaInfoRequest*>(request); 600 601 auto iter = frus.find(reqptr->fruID); 602 if (iter == frus.end()) 603 { 604 *data_len = 0; 605 return IPMI_CC_SENSOR_INVALID; 606 } 607 608 try 609 { 610 const auto& fruArea = getFruAreaData(reqptr->fruID); 611 auto size = static_cast<uint16_t>(fruArea.size()); 612 FruInvenAreaInfoResponse resp; 613 resp.sizems = size >> 8; 614 resp.sizels = size; 615 resp.access = static_cast<uint8_t>(AccessMode::bytes); 616 617 *data_len = sizeof(resp); 618 619 // Pack the actual response 620 memcpy(response, &resp, *data_len); 621 } 622 catch(const InternalFailure& e) 623 { 624 rc = IPMI_CC_UNSPECIFIED_ERROR; 625 *data_len = 0; 626 log<level::ERR>(e.what()); 627 } 628 return rc; 629 } 630 631 //Read FRU data 632 ipmi_ret_t ipmi_storage_read_fru_data( 633 ipmi_netfn_t netfn, ipmi_cmd_t cmd, ipmi_request_t request, 634 ipmi_response_t response, ipmi_data_len_t data_len, 635 ipmi_context_t context) 636 { 637 ipmi_ret_t rc = IPMI_CC_OK; 638 const ReadFruDataRequest* reqptr = 639 reinterpret_cast<const ReadFruDataRequest*>(request); 640 auto resptr = 641 reinterpret_cast<ReadFruDataResponse*>(response); 642 643 auto iter = frus.find(reqptr->fruID); 644 if (iter == frus.end()) 645 { 646 *data_len = 0; 647 return IPMI_CC_SENSOR_INVALID; 648 } 649 650 auto offset = 651 static_cast<uint16_t>(reqptr->offsetMS << 8 | reqptr->offsetLS); 652 try 653 { 654 const auto& fruArea = getFruAreaData(reqptr->fruID); 655 auto size = fruArea.size(); 656 657 if (offset >= size) 658 { 659 return IPMI_CC_PARM_OUT_OF_RANGE; 660 } 661 662 // Write the count of response data. 663 if ((offset + reqptr->count) <= size) 664 { 665 resptr->count = reqptr->count; 666 } 667 else 668 { 669 resptr->count = size - offset; 670 } 671 672 std::copy((fruArea.begin() + offset), 673 (fruArea.begin() + offset + resptr->count), 674 resptr->data); 675 676 *data_len = resptr->count + 1; // additional one byte for count 677 } 678 catch (const InternalFailure& e) 679 { 680 rc = IPMI_CC_UNSPECIFIED_ERROR; 681 *data_len = 0; 682 log<level::ERR>(e.what()); 683 } 684 return rc; 685 } 686 687 ipmi_ret_t ipmi_get_repository_info(ipmi_netfn_t netfn, ipmi_cmd_t cmd, 688 ipmi_request_t request, ipmi_response_t response, 689 ipmi_data_len_t data_len, ipmi_context_t context) 690 { 691 constexpr auto sdrVersion = 0x51; 692 auto responseData = 693 reinterpret_cast<GetRepositoryInfoResponse*>(response); 694 695 memset(responseData, 0 , sizeof(GetRepositoryInfoResponse)); 696 697 responseData->sdrVersion = sdrVersion; 698 699 uint16_t records = frus.size() + sensors.size(); 700 responseData->recordCountMs = records >> 8; 701 responseData->recordCountLs = records; 702 703 responseData->freeSpace[0] = 0xFF; 704 responseData->freeSpace[1] = 0xFF; 705 706 *data_len = sizeof(GetRepositoryInfoResponse); 707 708 return IPMI_CC_OK; 709 } 710 711 void register_netfn_storage_functions() 712 { 713 // <Wildcard Command> 714 ipmi_register_callback(NETFUN_STORAGE, IPMI_CMD_WILDCARD, NULL, ipmi_storage_wildcard, 715 PRIVILEGE_USER); 716 717 // <Get SEL Info> 718 ipmi_register_callback(NETFUN_STORAGE, IPMI_CMD_GET_SEL_INFO, NULL, getSELInfo, 719 PRIVILEGE_USER); 720 721 // <Get SEL Time> 722 ipmi_register_callback(NETFUN_STORAGE, IPMI_CMD_GET_SEL_TIME, NULL, ipmi_storage_get_sel_time, 723 PRIVILEGE_USER); 724 725 // <Set SEL Time> 726 ipmi_register_callback(NETFUN_STORAGE, IPMI_CMD_SET_SEL_TIME, NULL, ipmi_storage_set_sel_time, 727 PRIVILEGE_OPERATOR); 728 729 // <Reserve SEL> 730 ipmi_register_callback(NETFUN_STORAGE, IPMI_CMD_RESERVE_SEL, NULL, ipmi_storage_reserve_sel, 731 PRIVILEGE_USER); 732 733 // <Get SEL Entry> 734 ipmi_register_callback(NETFUN_STORAGE, IPMI_CMD_GET_SEL_ENTRY, NULL, getSELEntry, 735 PRIVILEGE_USER); 736 737 // <Delete SEL Entry> 738 ipmi_register_callback(NETFUN_STORAGE, IPMI_CMD_DELETE_SEL, NULL, deleteSELEntry, 739 PRIVILEGE_OPERATOR); 740 741 // <Add SEL Entry> 742 ipmi_register_callback(NETFUN_STORAGE, IPMI_CMD_ADD_SEL, NULL, ipmi_storage_add_sel, 743 PRIVILEGE_OPERATOR); 744 // <Clear SEL> 745 ipmi_register_callback(NETFUN_STORAGE, IPMI_CMD_CLEAR_SEL, NULL, clearSEL, 746 PRIVILEGE_OPERATOR); 747 // <Get FRU Inventory Area Info> 748 ipmi_register_callback(NETFUN_STORAGE, IPMI_CMD_GET_FRU_INV_AREA_INFO, NULL, 749 ipmi_storage_get_fru_inv_area_info, PRIVILEGE_OPERATOR); 750 751 // <Add READ FRU Data 752 ipmi_register_callback(NETFUN_STORAGE, IPMI_CMD_READ_FRU_DATA, NULL, 753 ipmi_storage_read_fru_data, PRIVILEGE_OPERATOR); 754 755 // <Get Repository Info> 756 ipmi_register_callback(NETFUN_STORAGE, IPMI_CMD_GET_REPOSITORY_INFO, 757 nullptr, ipmi_get_repository_info, 758 PRIVILEGE_USER); 759 760 // <Reserve SDR Repository> 761 ipmi_register_callback(NETFUN_STORAGE, IPMI_CMD_RESERVE_SDR, 762 nullptr, ipmi_sen_reserve_sdr, 763 PRIVILEGE_USER); 764 765 // <Get SDR> 766 ipmi_register_callback(NETFUN_STORAGE, IPMI_CMD_GET_SDR, 767 nullptr, ipmi_sen_get_sdr, 768 PRIVILEGE_USER); 769 770 ipmi::fru::registerCallbackHandler(); 771 return; 772 } 773 774