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 437 try 438 { 439 sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()}; 440 auto service = ipmi::getService(bus, TIME_INTERFACE, HOST_TIME_PATH); 441 sdbusplus::message::variant<uint64_t> value; 442 443 // Get host time 444 auto method = bus.new_method_call(service.c_str(), 445 HOST_TIME_PATH, 446 DBUS_PROPERTIES, 447 "Get"); 448 449 method.append(TIME_INTERFACE, PROPERTY_ELAPSED); 450 auto reply = bus.call(method); 451 if (reply.is_method_error()) 452 { 453 log<level::ERR>("Error getting time", 454 entry("SERVICE=%s", service.c_str()), 455 entry("PATH=%s", HOST_TIME_PATH)); 456 return IPMI_CC_UNSPECIFIED_ERROR; 457 } 458 reply.read(value); 459 host_time_usec = value.get<uint64_t>(); 460 } 461 catch (InternalFailure& e) 462 { 463 log<level::ERR>(e.what()); 464 return IPMI_CC_UNSPECIFIED_ERROR; 465 } 466 catch (const std::runtime_error& e) 467 { 468 log<level::ERR>(e.what()); 469 return IPMI_CC_UNSPECIFIED_ERROR; 470 } 471 472 log<level::DEBUG>("Host time:", 473 entry("HOST_TIME=%s", getTimeString(host_time_usec))); 474 475 // Time is really long int but IPMI wants just uint32. This works okay until 476 // the number of seconds since 1970 overflows uint32 size.. Still a whole 477 // lot of time here to even think about that. 478 resp = duration_cast<seconds>(microseconds(host_time_usec)).count(); 479 resp = htole32(resp); 480 481 // From the IPMI Spec 2.0, response should be a 32-bit value 482 *data_len = sizeof(resp); 483 484 // Pack the actual response 485 memcpy(response, &resp, *data_len); 486 487 return IPMI_CC_OK; 488 } 489 490 ipmi_ret_t ipmi_storage_set_sel_time(ipmi_netfn_t netfn, ipmi_cmd_t cmd, 491 ipmi_request_t request, ipmi_response_t response, 492 ipmi_data_len_t data_len, ipmi_context_t context) 493 { 494 using namespace std::chrono; 495 ipmi_ret_t rc = IPMI_CC_OK; 496 uint32_t secs = *static_cast<uint32_t*>(request); 497 *data_len = 0; 498 499 secs = le32toh(secs); 500 microseconds usec{seconds(secs)}; 501 502 try 503 { 504 sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()}; 505 auto service = ipmi::getService(bus, TIME_INTERFACE, HOST_TIME_PATH); 506 sdbusplus::message::variant<uint64_t> value{usec.count()}; 507 508 // Set host time 509 auto method = bus.new_method_call(service.c_str(), 510 HOST_TIME_PATH, 511 DBUS_PROPERTIES, 512 "Set"); 513 514 method.append(TIME_INTERFACE, PROPERTY_ELAPSED, value); 515 auto reply = bus.call(method); 516 if (reply.is_method_error()) 517 { 518 log<level::ERR>("Error setting time", 519 entry("SERVICE=%s", service.c_str()), 520 entry("PATH=%s", HOST_TIME_PATH)); 521 rc = IPMI_CC_UNSPECIFIED_ERROR; 522 } 523 } 524 catch (InternalFailure& e) 525 { 526 log<level::ERR>(e.what()); 527 rc = IPMI_CC_UNSPECIFIED_ERROR; 528 } 529 catch (const std::runtime_error& e) 530 { 531 log<level::ERR>(e.what()); 532 rc = IPMI_CC_UNSPECIFIED_ERROR; 533 } 534 535 return rc; 536 } 537 538 ipmi_ret_t ipmi_storage_reserve_sel(ipmi_netfn_t netfn, ipmi_cmd_t cmd, 539 ipmi_request_t request, ipmi_response_t response, 540 ipmi_data_len_t data_len, ipmi_context_t context) 541 { 542 ipmi_ret_t rc = IPMI_CC_OK; 543 544 // IPMI spec, Reservation ID, the value simply increases against each execution of reserve_sel command. 545 if( ++g_sel_reserve == 0) 546 g_sel_reserve = 1; 547 548 *data_len = sizeof(g_sel_reserve); 549 550 // Pack the actual response 551 memcpy(response, &g_sel_reserve, *data_len); 552 553 return rc; 554 } 555 556 ipmi_ret_t ipmi_storage_add_sel(ipmi_netfn_t netfn, ipmi_cmd_t cmd, 557 ipmi_request_t request, ipmi_response_t response, 558 ipmi_data_len_t data_len, ipmi_context_t context) 559 { 560 561 ipmi_ret_t rc = IPMI_CC_OK; 562 ipmi_add_sel_request_t *p = (ipmi_add_sel_request_t*) request; 563 uint16_t recordid; 564 565 recordid = ((uint16_t)p->eventdata[1] << 8) | p->eventdata[2]; 566 567 *data_len = sizeof(g_sel_reserve); 568 569 // Pack the actual response 570 memcpy(response, &p->eventdata[1], 2); 571 572 // Hostboot sends SEL with OEM record type 0xDE to indicate that there is 573 // a maintenance procedure associated with eSEL record. 574 static constexpr auto procedureType = 0xDE; 575 if (p->recordtype == procedureType) 576 { 577 // In the OEM record type 0xDE, byte 11 in the SEL record indicate the 578 // procedure number. 579 createProcedureLogEntry(p->sensortype); 580 } 581 else 582 { 583 send_esel(recordid); 584 } 585 586 return rc; 587 } 588 589 //Read FRU info area 590 ipmi_ret_t ipmi_storage_get_fru_inv_area_info( 591 ipmi_netfn_t netfn, ipmi_cmd_t cmd, ipmi_request_t request, 592 ipmi_response_t response, ipmi_data_len_t data_len, 593 ipmi_context_t context) 594 { 595 ipmi_ret_t rc = IPMI_CC_OK; 596 const FruInvenAreaInfoRequest* reqptr = 597 reinterpret_cast<const FruInvenAreaInfoRequest*>(request); 598 try 599 { 600 const auto& fruArea = getFruAreaData(reqptr->fruID); 601 auto size = static_cast<uint16_t>(fruArea.size()); 602 FruInvenAreaInfoResponse resp; 603 resp.sizems = size >> 8; 604 resp.sizels = size; 605 resp.access = static_cast<uint8_t>(AccessMode::bytes); 606 607 *data_len = sizeof(resp); 608 609 // Pack the actual response 610 memcpy(response, &resp, *data_len); 611 } 612 catch(const InternalFailure& e) 613 { 614 rc = IPMI_CC_UNSPECIFIED_ERROR; 615 *data_len = 0; 616 log<level::ERR>(e.what()); 617 report<InternalFailure>(); 618 } 619 return rc; 620 } 621 622 //Read FRU data 623 ipmi_ret_t ipmi_storage_read_fru_data( 624 ipmi_netfn_t netfn, ipmi_cmd_t cmd, ipmi_request_t request, 625 ipmi_response_t response, ipmi_data_len_t data_len, 626 ipmi_context_t context) 627 { 628 ipmi_ret_t rc = IPMI_CC_OK; 629 const ReadFruDataRequest* reqptr = 630 reinterpret_cast<const ReadFruDataRequest*>(request); 631 auto resptr = 632 reinterpret_cast<ReadFruDataResponse*>(response); 633 auto offset = 634 static_cast<uint16_t>(reqptr->offsetMS << 8 | reqptr->offsetLS); 635 try 636 { 637 const auto& fruArea = getFruAreaData(reqptr->fruID); 638 auto size = fruArea.size(); 639 640 // Write the count of response data. 641 if ((offset + reqptr->count) <= size) 642 { 643 resptr->count = reqptr->count; 644 } 645 else 646 { 647 resptr->count = size - offset; 648 } 649 650 std::copy((fruArea.begin() + offset), 651 (fruArea.begin() + offset + resptr->count), 652 resptr->data); 653 654 *data_len = resptr->count + 1; // additional one byte for count 655 } 656 catch (const InternalFailure& e) 657 { 658 rc = IPMI_CC_UNSPECIFIED_ERROR; 659 *data_len = 0; 660 log<level::ERR>(e.what()); 661 report<InternalFailure>(); 662 } 663 return rc; 664 } 665 666 ipmi_ret_t ipmi_get_repository_info(ipmi_netfn_t netfn, ipmi_cmd_t cmd, 667 ipmi_request_t request, ipmi_response_t response, 668 ipmi_data_len_t data_len, ipmi_context_t context) 669 { 670 constexpr auto sdrVersion = 0x51; 671 auto responseData = 672 reinterpret_cast<GetRepositoryInfoResponse*>(response); 673 674 memset(responseData, 0 , sizeof(GetRepositoryInfoResponse)); 675 676 responseData->sdrVersion = sdrVersion; 677 678 uint16_t records = frus.size() + sensors.size(); 679 responseData->recordCountMs = records >> 8; 680 responseData->recordCountLs = records; 681 682 responseData->freeSpace[0] = 0xFF; 683 responseData->freeSpace[1] = 0xFF; 684 685 *data_len = sizeof(GetRepositoryInfoResponse); 686 687 return IPMI_CC_OK; 688 } 689 690 void register_netfn_storage_functions() 691 { 692 // <Wildcard Command> 693 ipmi_register_callback(NETFUN_STORAGE, IPMI_CMD_WILDCARD, NULL, ipmi_storage_wildcard, 694 PRIVILEGE_USER); 695 696 // <Get SEL Info> 697 ipmi_register_callback(NETFUN_STORAGE, IPMI_CMD_GET_SEL_INFO, NULL, getSELInfo, 698 PRIVILEGE_USER); 699 700 // <Get SEL Time> 701 ipmi_register_callback(NETFUN_STORAGE, IPMI_CMD_GET_SEL_TIME, NULL, ipmi_storage_get_sel_time, 702 PRIVILEGE_USER); 703 704 // <Set SEL Time> 705 ipmi_register_callback(NETFUN_STORAGE, IPMI_CMD_SET_SEL_TIME, NULL, ipmi_storage_set_sel_time, 706 PRIVILEGE_OPERATOR); 707 708 // <Reserve SEL> 709 ipmi_register_callback(NETFUN_STORAGE, IPMI_CMD_RESERVE_SEL, NULL, ipmi_storage_reserve_sel, 710 PRIVILEGE_USER); 711 712 // <Get SEL Entry> 713 ipmi_register_callback(NETFUN_STORAGE, IPMI_CMD_GET_SEL_ENTRY, NULL, getSELEntry, 714 PRIVILEGE_USER); 715 716 // <Delete SEL Entry> 717 ipmi_register_callback(NETFUN_STORAGE, IPMI_CMD_DELETE_SEL, NULL, deleteSELEntry, 718 PRIVILEGE_OPERATOR); 719 720 // <Add SEL Entry> 721 ipmi_register_callback(NETFUN_STORAGE, IPMI_CMD_ADD_SEL, NULL, ipmi_storage_add_sel, 722 PRIVILEGE_OPERATOR); 723 // <Clear SEL> 724 ipmi_register_callback(NETFUN_STORAGE, IPMI_CMD_CLEAR_SEL, NULL, clearSEL, 725 PRIVILEGE_OPERATOR); 726 // <Get FRU Inventory Area Info> 727 ipmi_register_callback(NETFUN_STORAGE, IPMI_CMD_GET_FRU_INV_AREA_INFO, NULL, 728 ipmi_storage_get_fru_inv_area_info, PRIVILEGE_OPERATOR); 729 730 // <Add READ FRU Data 731 ipmi_register_callback(NETFUN_STORAGE, IPMI_CMD_READ_FRU_DATA, NULL, 732 ipmi_storage_read_fru_data, PRIVILEGE_OPERATOR); 733 734 // <Get Repository Info> 735 ipmi_register_callback(NETFUN_STORAGE, IPMI_CMD_GET_REPOSITORY_INFO, 736 nullptr, ipmi_get_repository_info, 737 PRIVILEGE_USER); 738 739 // <Reserve SDR Repository> 740 ipmi_register_callback(NETFUN_STORAGE, IPMI_CMD_RESERVE_SDR, 741 nullptr, ipmi_sen_reserve_sdr, 742 PRIVILEGE_USER); 743 744 // <Get SDR> 745 ipmi_register_callback(NETFUN_STORAGE, IPMI_CMD_GET_SDR, 746 nullptr, ipmi_sen_get_sdr, 747 PRIVILEGE_USER); 748 749 ipmi::fru::registerCallbackHandler(); 750 return; 751 } 752 753