1 /* 2 * Copyright (c) 2018 Intel Corporation. 3 * Copyright (c) 2018-present Facebook. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 #include <ipmid/api.hpp> 19 20 #include <boost/algorithm/string/join.hpp> 21 #include <nlohmann/json.hpp> 22 #include <iostream> 23 #include <sstream> 24 #include <fstream> 25 #include <phosphor-logging/log.hpp> 26 #include <sdbusplus/message/types.hpp> 27 #include <sdbusplus/timer.hpp> 28 #include <storagecommands.hpp> 29 30 //---------------------------------------------------------------------- 31 // Platform specific functions for storing app data 32 //---------------------------------------------------------------------- 33 34 static std::string byteToStr(uint8_t byte) 35 { 36 std::stringstream ss; 37 38 ss << std::hex << std::uppercase << std::setfill('0'); 39 ss << std::setw(2) << (int)byte; 40 41 return ss.str(); 42 } 43 44 static void toHexStr(std::vector<uint8_t> &bytes, std::string &hexStr) 45 { 46 std::stringstream stream; 47 stream << std::hex << std::uppercase << std::setfill('0'); 48 for (const uint8_t byte : bytes) 49 { 50 stream << std::setw(2) << static_cast<int>(byte); 51 } 52 hexStr = stream.str(); 53 } 54 55 static int fromHexStr(const std::string hexStr, std::vector<uint8_t> &data) 56 { 57 for (unsigned int i = 0; i < hexStr.size(); i += 2) 58 { 59 try 60 { 61 data.push_back(static_cast<uint8_t>( 62 std::stoul(hexStr.substr(i, 2), nullptr, 16))); 63 } 64 catch (std::invalid_argument &e) 65 { 66 phosphor::logging::log<phosphor::logging::level::ERR>(e.what()); 67 return -1; 68 } 69 catch (std::out_of_range &e) 70 { 71 phosphor::logging::log<phosphor::logging::level::ERR>(e.what()); 72 return -1; 73 } 74 } 75 return 0; 76 } 77 78 namespace fb_oem::ipmi::sel 79 { 80 81 class SELData 82 { 83 private: 84 nlohmann::json selDataObj; 85 86 void flush() 87 { 88 std::ofstream file(SEL_JSON_DATA_FILE); 89 file << selDataObj; 90 file.close(); 91 } 92 93 void init() 94 { 95 selDataObj[KEY_SEL_VER] = 0x51; 96 selDataObj[KEY_SEL_COUNT] = 0; 97 selDataObj[KEY_ADD_TIME] = 0xFFFFFFFF; 98 selDataObj[KEY_ERASE_TIME] = 0xFFFFFFFF; 99 selDataObj[KEY_OPER_SUPP] = 0x02; 100 /* Spec indicates that more than 64kB is free */ 101 selDataObj[KEY_FREE_SPACE] = 0xFFFF; 102 } 103 104 public: 105 SELData() 106 { 107 /* Get App data stored in json file */ 108 std::ifstream file(SEL_JSON_DATA_FILE); 109 if (file) 110 { 111 file >> selDataObj; 112 file.close(); 113 } 114 115 /* Initialize SelData object if no entries. */ 116 if (selDataObj.find(KEY_SEL_COUNT) == selDataObj.end()) 117 { 118 init(); 119 } 120 } 121 122 int clear() 123 { 124 /* Clear the complete Sel Json object */ 125 selDataObj.clear(); 126 /* Reinitialize it with basic data */ 127 init(); 128 /* Save the erase time */ 129 struct timespec selTime = {}; 130 if (clock_gettime(CLOCK_REALTIME, &selTime) < 0) 131 { 132 return -1; 133 } 134 selDataObj[KEY_ERASE_TIME] = selTime.tv_sec; 135 flush(); 136 return 0; 137 } 138 139 uint32_t getCount() 140 { 141 return selDataObj[KEY_SEL_COUNT]; 142 } 143 144 void getInfo(GetSELInfoData &info) 145 { 146 info.selVersion = selDataObj[KEY_SEL_VER]; 147 info.entries = selDataObj[KEY_SEL_COUNT]; 148 info.freeSpace = selDataObj[KEY_FREE_SPACE]; 149 info.addTimeStamp = selDataObj[KEY_ADD_TIME]; 150 info.eraseTimeStamp = selDataObj[KEY_ERASE_TIME]; 151 info.operationSupport = selDataObj[KEY_OPER_SUPP]; 152 } 153 154 int getEntry(uint32_t index, std::string &rawStr) 155 { 156 std::stringstream ss; 157 ss << std::hex; 158 ss << std::setw(2) << std::setfill('0') << index; 159 160 /* Check or the requested SEL Entry, if record is available */ 161 if (selDataObj.find(ss.str()) == selDataObj.end()) 162 { 163 return -1; 164 } 165 166 rawStr = selDataObj[ss.str()][KEY_SEL_ENTRY_RAW]; 167 return 0; 168 } 169 170 int addEntry(std::string keyStr) 171 { 172 struct timespec selTime = {}; 173 174 if (clock_gettime(CLOCK_REALTIME, &selTime) < 0) 175 { 176 return -1; 177 } 178 179 selDataObj[KEY_ADD_TIME] = selTime.tv_sec; 180 181 int selCount = selDataObj[KEY_SEL_COUNT]; 182 selDataObj[KEY_SEL_COUNT] = ++selCount; 183 184 std::stringstream ss; 185 ss << std::hex; 186 ss << std::setw(2) << std::setfill('0') << selCount; 187 188 selDataObj[ss.str()][KEY_SEL_ENTRY_RAW] = keyStr; 189 flush(); 190 return selCount; 191 } 192 }; 193 194 /* 195 * A Function to parse common SEL message, a helper funciton 196 * for parseStdSel. 197 * 198 * Note that this function __CANNOT__ be overriden. 199 * To add board specific routine, please override parseStdSel. 200 */ 201 202 /*Used by decoding ME event*/ 203 std::vector<std::string> nmDomName = { 204 "Entire Platform", "CPU Subsystem", 205 "Memory Subsystem", "HW Protection", 206 "High Power I/O subsystem", "Unknown"}; 207 208 /* Default log message for unknown type */ 209 static void logDefault(uint8_t *data, std::string &errLog) 210 { 211 errLog = "Unknown"; 212 } 213 214 static void logSysEvent(uint8_t *data, std::string &errLog) 215 { 216 if (data[0] == 0xE5) 217 { 218 errLog = "Cause of Time change - "; 219 switch (data[2]) 220 { 221 case 0x00: 222 errLog += "NTP"; 223 break; 224 case 0x01: 225 errLog += "Host RTL"; 226 break; 227 case 0x02: 228 errLog += "Set SEL time cmd"; 229 break; 230 case 0x03: 231 errLog += "Set SEL time UTC offset cmd"; 232 break; 233 default: 234 errLog += "Unknown"; 235 } 236 237 if (data[1] == 0x00) 238 errLog += " - First Time"; 239 else if (data[1] == 0x80) 240 errLog += " - Second Time"; 241 } 242 else 243 { 244 errLog = "Unknown"; 245 } 246 } 247 248 static void logThermalEvent(uint8_t *data, std::string &errLog) 249 { 250 if (data[0] == 0x1) 251 { 252 errLog = "Limit Exceeded"; 253 } 254 else 255 { 256 errLog = "Unknown"; 257 } 258 } 259 260 static void logCritIrq(uint8_t *data, std::string &errLog) 261 { 262 263 if (data[0] == 0x0) 264 { 265 errLog = "NMI / Diagnostic Interrupt"; 266 } 267 else if (data[0] == 0x03) 268 { 269 errLog = "Software NMI"; 270 } 271 else 272 { 273 errLog = "Unknown"; 274 } 275 276 /* TODO: Call add_cri_sel for CRITICAL_IRQ */ 277 } 278 279 static void logPostErr(uint8_t *data, std::string &errLog) 280 { 281 282 if ((data[0] & 0x0F) == 0x0) 283 { 284 errLog = "System Firmware Error"; 285 } 286 else 287 { 288 errLog = "Unknown"; 289 } 290 291 if (((data[0] >> 6) & 0x03) == 0x3) 292 { 293 // TODO: Need to implement IPMI spec based Post Code 294 errLog += ", IPMI Post Code"; 295 } 296 else if (((data[0] >> 6) & 0x03) == 0x2) 297 { 298 errLog += 299 ", OEM Post Code 0x" + byteToStr(data[2]) + byteToStr(data[1]); 300 301 switch ((data[2] << 8) | data[1]) 302 { 303 case 0xA105: 304 errLog += ", BMC Failed (No Response)"; 305 break; 306 case 0xA106: 307 errLog += ", BMC Failed (Self Test Fail)"; 308 break; 309 case 0xA10A: 310 errLog += ", System Firmware Corruption Detected"; 311 break; 312 case 0xA10B: 313 errLog += ", TPM Self-Test FAIL Detected"; 314 } 315 } 316 } 317 318 static void logMchChkErr(uint8_t *data, std::string &errLog) 319 { 320 /* TODO: Call add_cri_sel for CRITICAL_IRQ */ 321 if ((data[0] & 0x0F) == 0x0B) 322 { 323 errLog = "Uncorrectable"; 324 } 325 else if ((data[0] & 0x0F) == 0x0C) 326 { 327 errLog = "Correctable"; 328 } 329 else 330 { 331 errLog = "Unknown"; 332 } 333 334 errLog += ", Machine Check bank Number " + std::to_string(data[1]) + 335 ", CPU " + std::to_string(data[2] >> 5) + ", Core " + 336 std::to_string(data[2] & 0x1F); 337 } 338 339 static void logPcieErr(uint8_t *data, std::string &errLog) 340 { 341 std::stringstream tmp1, tmp2; 342 tmp1 << std::hex << std::uppercase << std::setfill('0'); 343 tmp2 << std::hex << std::uppercase << std::setfill('0'); 344 tmp1 << " (Bus " << std::setw(2) << (int)(data[2]) << " / Dev " 345 << std::setw(2) << (int)(data[1] >> 3) << " / Fun " << std::setw(2) 346 << (int)(data[1] & 0x7) << ")"; 347 348 switch (data[0] & 0xF) 349 { 350 case 0x4: 351 errLog = "PCI PERR" + tmp1.str(); 352 break; 353 case 0x5: 354 errLog = "PCI SERR" + tmp1.str(); 355 break; 356 case 0x7: 357 errLog = "Correctable" + tmp1.str(); 358 break; 359 case 0x8: 360 errLog = "Uncorrectable" + tmp1.str(); 361 break; 362 case 0xA: 363 errLog = "Bus Fatal" + tmp1.str(); 364 break; 365 case 0xD: 366 { 367 uint32_t venId = (uint32_t)data[1] << 8 | (uint32_t)data[2]; 368 tmp2 << "Vendor ID: 0x" << std::setw(4) << venId; 369 errLog = tmp2.str(); 370 } 371 break; 372 case 0xE: 373 { 374 uint32_t devId = (uint32_t)data[1] << 8 | (uint32_t)data[2]; 375 tmp2 << "Device ID: 0x" << std::setw(4) << devId; 376 errLog = tmp2.str(); 377 } 378 break; 379 case 0xF: 380 tmp2 << "Error ID from downstream: 0x" << std::setw(2) 381 << (int)(data[1]) << std::setw(2) << (int)(data[2]); 382 errLog = tmp2.str(); 383 break; 384 default: 385 errLog = "Unknown"; 386 } 387 } 388 389 static void logIioErr(uint8_t *data, std::string &errLog) 390 { 391 std::vector<std::string> tmpStr = { 392 "IRP0", "IRP1", " IIO-Core", "VT-d", "Intel Quick Data", 393 "Misc", " DMA", "ITC", "OTC", "CI"}; 394 395 if ((data[0] & 0xF) == 0) 396 { 397 errLog += "CPU " + std::to_string(data[2] >> 5) + ", Error ID 0x" + 398 byteToStr(data[1]) + " - "; 399 400 if ((data[2] & 0xF) <= 0x9) 401 { 402 errLog += tmpStr[(data[2] & 0xF)]; 403 } 404 else 405 { 406 errLog += "Reserved"; 407 } 408 } 409 else 410 { 411 errLog = "Unknown"; 412 } 413 } 414 415 static void logMemErr(uint8_t *dataPtr, std::string &errLog) 416 { 417 uint8_t snrType = dataPtr[0]; 418 uint8_t snrNum = dataPtr[1]; 419 uint8_t *data = &(dataPtr[3]); 420 421 /* TODO: add pal_add_cri_sel */ 422 423 if (snrNum == memoryEccError) 424 { 425 /* SEL from MEMORY_ECC_ERR Sensor */ 426 switch (data[0] & 0x0F) 427 { 428 case 0x0: 429 if (snrType == 0x0C) 430 { 431 errLog = "Correctable"; 432 } 433 else if (snrType == 0x10) 434 { 435 errLog = "Correctable ECC error Logging Disabled"; 436 } 437 break; 438 case 0x1: 439 errLog = "Uncorrectable"; 440 break; 441 case 0x5: 442 errLog = "Correctable ECC error Logging Limit Disabled"; 443 break; 444 default: 445 errLog = "Unknown"; 446 } 447 } 448 else if (snrNum == memoryErrLogDIS) 449 { 450 // SEL from MEMORY_ERR_LOG_DIS Sensor 451 if ((data[0] & 0x0F) == 0x0) 452 { 453 errLog = "Correctable Memory Error Logging Disabled"; 454 } 455 else 456 { 457 errLog = "Unknown"; 458 } 459 } 460 else 461 { 462 errLog = "Unknown"; 463 return; 464 } 465 466 /* Common routine for both MEM_ECC_ERR and MEMORY_ERR_LOG_DIS */ 467 468 errLog += " (DIMM " + byteToStr(data[2]) + ") Logical Rank " + 469 std::to_string(data[1] & 0x03); 470 471 /* DIMM number (data[2]): 472 * Bit[7:5]: Socket number (Range: 0-7) 473 * Bit[4:3]: Channel number (Range: 0-3) 474 * Bit[2:0]: DIMM number (Range: 0-7) 475 */ 476 477 /* TODO: Verify these bits */ 478 std::string cpuStr = "CPU# " + std::to_string((data[2] & 0xE0) >> 5); 479 std::string chStr = "CHN# " + std::to_string((data[2] & 0x18) >> 3); 480 std::string dimmStr = "DIMM# " + std::to_string(data[2] & 0x7); 481 482 switch ((data[1] & 0xC) >> 2) 483 { 484 case 0x0: 485 { 486 487 /* All Info Valid */ 488 uint8_t chnNum = (data[2] & 0x1C) >> 2; 489 uint8_t dimmNum = data[2] & 0x3; 490 491 /* TODO: If critical SEL logging is available, do it */ 492 if (snrType == 0x0C) 493 { 494 if ((data[0] & 0x0F) == 0x0) 495 { 496 /* TODO: add_cri_sel */ 497 /* "DIMM"+ 'A'+ chnNum + dimmNum + " ECC err,FRU:1" 498 */ 499 } 500 else if ((data[0] & 0x0F) == 0x1) 501 { 502 /* TODO: add_cri_sel */ 503 /* "DIMM"+ 'A'+ chnNum + dimmNum + " UECC err,FRU:1" 504 */ 505 } 506 } 507 /* Continue to parse the error into a string. All Info Valid 508 */ 509 errLog += " (" + cpuStr + ", " + chStr + ", " + dimmStr + ")"; 510 } 511 512 break; 513 case 0x1: 514 515 /* DIMM info not valid */ 516 errLog += " (" + cpuStr + ", " + chStr + ")"; 517 break; 518 case 0x2: 519 520 /* CHN info not valid */ 521 errLog += " (" + cpuStr + ", " + dimmStr + ")"; 522 break; 523 case 0x3: 524 525 /* CPU info not valid */ 526 errLog += " (" + chStr + ", " + dimmStr + ")"; 527 break; 528 } 529 } 530 531 static void logPwrErr(uint8_t *data, std::string &errLog) 532 { 533 534 if (data[0] == 0x1) 535 { 536 errLog = "SYS_PWROK failure"; 537 /* Also try logging to Critial log file, if available */ 538 /* "SYS_PWROK failure,FRU:1" */ 539 } 540 else if (data[0] == 0x2) 541 { 542 errLog = "PCH_PWROK failure"; 543 /* Also try logging to Critial log file, if available */ 544 /* "PCH_PWROK failure,FRU:1" */ 545 } 546 else 547 { 548 errLog = "Unknown"; 549 } 550 } 551 552 static void logCatErr(uint8_t *data, std::string &errLog) 553 { 554 555 if (data[0] == 0x0) 556 { 557 errLog = "IERR/CATERR"; 558 /* Also try logging to Critial log file, if available */ 559 /* "IERR,FRU:1 */ 560 } 561 else if (data[0] == 0xB) 562 { 563 errLog = "MCERR/CATERR"; 564 /* Also try logging to Critial log file, if available */ 565 /* "MCERR,FRU:1 */ 566 } 567 else 568 { 569 errLog = "Unknown"; 570 } 571 } 572 573 static void logDimmHot(uint8_t *data, std::string &errLog) 574 { 575 if ((data[0] << 16 | data[1] << 8 | data[2]) == 0x01FFFF) 576 { 577 errLog = "SOC MEMHOT"; 578 } 579 else 580 { 581 errLog = "Unknown"; 582 /* Also try logging to Critial log file, if available */ 583 /* ""CPU_DIMM_HOT %s,FRU:1" */ 584 } 585 } 586 587 static void logSwNMI(uint8_t *data, std::string &errLog) 588 { 589 if ((data[0] << 16 | data[1] << 8 | data[2]) == 0x03FFFF) 590 { 591 errLog = "Software NMI"; 592 } 593 else 594 { 595 errLog = "Unknown SW NMI"; 596 } 597 } 598 599 static void logCPUThermalSts(uint8_t *data, std::string &errLog) 600 { 601 switch (data[0]) 602 { 603 case 0x0: 604 errLog = "CPU Critical Temperature"; 605 break; 606 case 0x1: 607 errLog = "PROCHOT#"; 608 break; 609 case 0x2: 610 errLog = "TCC Activation"; 611 break; 612 default: 613 errLog = "Unknown"; 614 } 615 } 616 617 static void logMEPwrState(uint8_t *data, std::string &errLog) 618 { 619 switch (data[0]) 620 { 621 case 0: 622 errLog = "RUNNING"; 623 break; 624 case 2: 625 errLog = "POWER_OFF"; 626 break; 627 default: 628 errLog = "Unknown[" + std::to_string(data[0]) + "]"; 629 break; 630 } 631 } 632 633 static void logSPSFwHealth(uint8_t *data, std::string &errLog) 634 { 635 if ((data[0] & 0x0F) == 0x00) 636 { 637 const std::vector<std::string> tmpStr = { 638 "Recovery GPIO forced", 639 "Image execution failed", 640 "Flash erase error", 641 "Flash state information", 642 "Internal error", 643 "BMC did not respond", 644 "Direct Flash update", 645 "Manufacturing error", 646 "Automatic Restore to Factory Presets", 647 "Firmware Exception", 648 "Flash Wear-Out Protection Warning", 649 "Unknown", 650 "Unknown", 651 "DMI interface error", 652 "MCTP interface error", 653 "Auto-configuration finished", 654 "Unsupported Segment Defined Feature", 655 "Unknown", 656 "CPU Debug Capability Disabled", 657 "UMA operation error"}; 658 659 if (data[1] < 0x14) 660 { 661 errLog = tmpStr[data[1]]; 662 } 663 else 664 { 665 errLog = "Unknown"; 666 } 667 } 668 else if ((data[0] & 0x0F) == 0x01) 669 { 670 errLog = "SMBus link failure"; 671 } 672 else 673 { 674 errLog = "Unknown"; 675 } 676 } 677 678 static void logNmExcA(uint8_t *data, std::string &errLog) 679 { 680 /*NM4.0 #550710, Revision 1.95, and turn to p.155*/ 681 if (data[0] == 0xA8) 682 { 683 errLog = "Policy Correction Time Exceeded"; 684 } 685 else 686 { 687 errLog = "Unknown"; 688 } 689 } 690 691 static void logPCHThermal(uint8_t *data, std::string &errLog) 692 { 693 const std::vector<std::string> thresEvtName = {"Lower Non-critical", 694 "Unknown", 695 "Lower Critical", 696 "Unknown", 697 "Lower Non-recoverable", 698 "Unknown", 699 "Unknown", 700 "Upper Non-critical", 701 "Unknown", 702 "Upper Critical", 703 "Unknown", 704 "Upper Non-recoverable"}; 705 706 if ((data[0] & 0x0f) < 12) 707 { 708 errLog = thresEvtName[(data[0] & 0x0f)]; 709 } 710 else 711 { 712 errLog = "Unknown"; 713 } 714 715 errLog += ", curr_val: " + std::to_string(data[1]) + 716 " C, thresh_val: " + std::to_string(data[2]) + " C"; 717 } 718 719 static void logNmHealth(uint8_t *data, std::string &errLog) 720 { 721 std::vector<std::string> nmErrType = { 722 "Unknown", 723 "Unknown", 724 "Unknown", 725 "Unknown", 726 "Unknown", 727 "Unknown", 728 "Unknown", 729 "Extended Telemetry Device Reading Failure", 730 "Outlet Temperature Reading Failure", 731 "Volumetric Airflow Reading Failure", 732 "Policy Misconfiguration", 733 "Power Sensor Reading Failure", 734 "Inlet Temperature Reading Failure", 735 "Host Communication Error", 736 "Real-time Clock Synchronization Failure", 737 "Platform Shutdown Initiated by Intel NM Policy", 738 "Unknown"}; 739 uint8_t nmTypeIdx = (data[0] & 0xf); 740 uint8_t domIdx = (data[1] & 0xf); 741 uint8_t errIdx = ((data[1] >> 4) & 0xf); 742 743 if (nmTypeIdx == 2) 744 { 745 errLog = "SensorIntelNM"; 746 } 747 else 748 { 749 errLog = "Unknown"; 750 } 751 752 errLog += ", Domain:" + nmDomName[domIdx] + 753 ", ErrType:" + nmErrType[errIdx] + ", Err:0x" + 754 byteToStr(data[2]); 755 } 756 757 static void logNmCap(uint8_t *data, std::string &errLog) 758 { 759 760 const std::vector<std::string> nmCapStsStr = {"Not Available", "Available"}; 761 if (data[0] & 0x7) // BIT1=policy, BIT2=monitoring, BIT3=pwr 762 // limit and the others are reserved 763 { 764 errLog = "PolicyInterface:" + nmCapStsStr[BIT(data[0], 0)] + 765 ",Monitoring:" + nmCapStsStr[BIT(data[0], 1)] + 766 ",PowerLimit:" + nmCapStsStr[BIT(data[0], 2)]; 767 } 768 else 769 { 770 errLog = "Unknown"; 771 } 772 } 773 774 static void logNmThreshold(uint8_t *data, std::string &errLog) 775 { 776 uint8_t thresNum = (data[0] & 0x3); 777 uint8_t domIdx = (data[1] & 0xf); 778 uint8_t polId = data[2]; 779 uint8_t polEvtIdx = BIT(data[0], 3); 780 const std::vector<std::string> polEvtStr = { 781 "Threshold Exceeded", "Policy Correction Time Exceeded"}; 782 783 errLog = "Threshold Number:" + std::to_string(thresNum) + "-" + 784 polEvtStr[polEvtIdx] + ", Domain:" + nmDomName[domIdx] + 785 ", PolicyID:0x" + byteToStr(polId); 786 } 787 788 static void logPwrThreshold(uint8_t *data, std::string &errLog) 789 { 790 if (data[0] == 0x00) 791 { 792 errLog = "Limit Not Exceeded"; 793 } 794 else if (data[0] == 0x01) 795 { 796 errLog = "Limit Exceeded"; 797 } 798 else 799 { 800 errLog = "Unknown"; 801 } 802 } 803 804 static void logMSMI(uint8_t *data, std::string &errLog) 805 { 806 807 if (data[0] == 0x0) 808 { 809 errLog = "IERR/MSMI"; 810 } 811 else if (data[0] == 0x0B) 812 { 813 errLog = "MCERR/MSMI"; 814 } 815 else 816 { 817 errLog = "Unknown"; 818 } 819 } 820 821 static void logHprWarn(uint8_t *data, std::string &errLog) 822 { 823 if (data[2] == 0x01) 824 { 825 if (data[1] == 0xFF) 826 { 827 errLog = "Infinite Time"; 828 } 829 else 830 { 831 errLog = std::to_string(data[1]) + " minutes"; 832 } 833 } 834 else 835 { 836 errLog = "Unknown"; 837 } 838 } 839 840 static const boost::container::flat_map< 841 uint8_t, 842 std::pair<std::string, std::function<void(uint8_t *, std::string &)>>> 843 sensorNameTable = {{0xE9, {"SYSTEM_EVENT", logSysEvent}}, 844 {0x7D, {"THERM_THRESH_EVT", logThermalEvent}}, 845 {0xAA, {"BUTTON", logDefault}}, 846 {0xAB, {"POWER_STATE", logDefault}}, 847 {0xEA, {"CRITICAL_IRQ", logCritIrq}}, 848 {0x2B, {"POST_ERROR", logPostErr}}, 849 {0x40, {"MACHINE_CHK_ERR", logMchChkErr}}, 850 {0x41, {"PCIE_ERR", logPcieErr}}, 851 {0x43, {"IIO_ERR", logIioErr}}, 852 {0X63, {"MEMORY_ECC_ERR", logDefault}}, 853 {0X87, {"MEMORY_ERR_LOG_DIS", logDefault}}, 854 {0X51, {"PROCHOT_EXT", logDefault}}, 855 {0X56, {"PWR_ERR", logPwrErr}}, 856 {0xE6, {"CATERR_A", logCatErr}}, 857 {0xEB, {"CATERR_B", logCatErr}}, 858 {0xB3, {"CPU_DIMM_HOT", logDimmHot}}, 859 {0x90, {"SOFTWARE_NMI", logSwNMI}}, 860 {0x1C, {"CPU0_THERM_STATUS", logCPUThermalSts}}, 861 {0x1D, {"CPU1_THERM_STATUS", logCPUThermalSts}}, 862 {0x16, {"ME_POWER_STATE", logMEPwrState}}, 863 {0x17, {"SPS_FW_HEALTH", logSPSFwHealth}}, 864 {0x18, {"NM_EXCEPTION_A", logNmExcA}}, 865 {0x08, {"PCH_THERM_THRESHOLD", logPCHThermal}}, 866 {0x19, {"NM_HEALTH", logNmHealth}}, 867 {0x1A, {"NM_CAPABILITIES", logNmCap}}, 868 {0x1B, {"NM_THRESHOLD", logNmThreshold}}, 869 {0x3B, {"PWR_THRESH_EVT", logPwrThreshold}}, 870 {0xE7, {"MSMI", logMSMI}}, 871 {0xC5, {"HPR_WARNING", logHprWarn}}}; 872 873 static void parseSelHelper(StdSELEntry *data, std::string &errStr) 874 { 875 876 /* Check if sensor type is OS_BOOT (0x1f) */ 877 if (data->sensorType == 0x1F) 878 { 879 /* OS_BOOT used by OS */ 880 switch (data->eventData1 & 0xF) 881 { 882 case 0x07: 883 errStr = "Base OS/Hypervisor Installation started"; 884 break; 885 case 0x08: 886 errStr = "Base OS/Hypervisor Installation completed"; 887 break; 888 case 0x09: 889 errStr = "Base OS/Hypervisor Installation aborted"; 890 break; 891 case 0x0A: 892 errStr = "Base OS/Hypervisor Installation failed"; 893 break; 894 default: 895 errStr = "Unknown"; 896 } 897 return; 898 } 899 900 auto findSensorName = sensorNameTable.find(data->sensorNum); 901 if (findSensorName == sensorNameTable.end()) 902 { 903 errStr = "Unknown"; 904 return; 905 } 906 else 907 { 908 switch (data->sensorNum) 909 { 910 /* logMemErr function needs data from sensor type */ 911 case memoryEccError: 912 case memoryErrLogDIS: 913 findSensorName->second.second(&(data->sensorType), errStr); 914 break; 915 /* Other sensor function needs only event data for parsing */ 916 default: 917 findSensorName->second.second(&(data->eventData1), errStr); 918 } 919 } 920 921 if (((data->eventData3 & 0x80) >> 7) == 0) 922 { 923 errStr += " Assertion"; 924 } 925 else 926 { 927 errStr += " Deassertion"; 928 } 929 } 930 931 static void parseStdSel(StdSELEntry *data, std::string &errStr) 932 { 933 std::stringstream tmpStream; 934 tmpStream << std::hex << std::uppercase; 935 936 /* TODO: add pal_add_cri_sel */ 937 switch (data->sensorNum) 938 { 939 case memoryEccError: 940 switch (data->eventData1 & 0x0F) 941 { 942 case 0x00: 943 errStr = "Correctable"; 944 tmpStream << "DIMM" << std::setw(2) << std::setfill('0') 945 << data->eventData3 << " ECC err"; 946 break; 947 case 0x01: 948 errStr = "Uncorrectable"; 949 tmpStream << "DIMM" << std::setw(2) << std::setfill('0') 950 << data->eventData3 << " UECC err"; 951 break; 952 case 0x02: 953 errStr = "Parity"; 954 break; 955 case 0x05: 956 errStr = "Correctable ECC error Logging Limit Reached"; 957 break; 958 default: 959 errStr = "Unknown"; 960 } 961 break; 962 case memoryErrLogDIS: 963 if ((data->eventData1 & 0x0F) == 0) 964 { 965 errStr = "Correctable Memory Error Logging Disabled"; 966 } 967 else 968 { 969 errStr = "Unknown"; 970 } 971 break; 972 default: 973 parseSelHelper(data, errStr); 974 return; 975 } 976 977 errStr += " (DIMM " + std::to_string(data->eventData3) + ")"; 978 errStr += " Logical Rank " + std::to_string(data->eventData2 & 0x03); 979 980 switch ((data->eventData2 & 0x0C) >> 2) 981 { 982 case 0x00: 983 // Ignore when " All info available" 984 break; 985 case 0x01: 986 errStr += " DIMM info not valid"; 987 break; 988 case 0x02: 989 errStr += " CHN info not valid"; 990 break; 991 case 0x03: 992 errStr += " CPU info not valid"; 993 break; 994 default: 995 errStr += " Unknown"; 996 } 997 998 if (((data->eventType & 0x80) >> 7) == 0) 999 { 1000 errStr += " Assertion"; 1001 } 1002 else 1003 { 1004 errStr += " Deassertion"; 1005 } 1006 1007 return; 1008 } 1009 1010 static void parseOemSel(TsOemSELEntry *data, std::string &errStr) 1011 { 1012 std::stringstream tmpStream; 1013 tmpStream << std::hex << std::uppercase << std::setfill('0'); 1014 1015 switch (data->recordType) 1016 { 1017 case 0xC0: 1018 tmpStream << "VID:0x" << std::setw(2) << (int)data->oemData[1] 1019 << std::setw(2) << (int)data->oemData[0] << " DID:0x" 1020 << std::setw(2) << (int)data->oemData[3] << std::setw(2) 1021 << (int)data->oemData[2] << " Slot:0x" << std::setw(2) 1022 << (int)data->oemData[4] << " Error ID:0x" << std::setw(2) 1023 << (int)data->oemData[5]; 1024 break; 1025 case 0xC2: 1026 tmpStream << "Extra info:0x" << std::setw(2) 1027 << (int)data->oemData[1] << " MSCOD:0x" << std::setw(2) 1028 << (int)data->oemData[3] << std::setw(2) 1029 << (int)data->oemData[2] << " MCACOD:0x" << std::setw(2) 1030 << (int)data->oemData[5] << std::setw(2) 1031 << (int)data->oemData[4]; 1032 break; 1033 case 0xC3: 1034 int bank = (data->oemData[1] & 0xf0) >> 4; 1035 int col = ((data->oemData[1] & 0x0f) << 8) | data->oemData[2]; 1036 1037 tmpStream << "Fail Device:0x" << std::setw(2) 1038 << (int)data->oemData[0] << " Bank:0x" << std::setw(2) 1039 << bank << " Column:0x" << std::setw(2) << col 1040 << " Failed Row:0x" << std::setw(2) 1041 << (int)data->oemData[3] << std::setw(2) 1042 << (int)data->oemData[4] << std::setw(2) 1043 << (int)data->oemData[5]; 1044 } 1045 1046 errStr = tmpStream.str(); 1047 1048 return; 1049 } 1050 1051 static void parseOemUnifiedSel(NtsOemSELEntry *data, std::string &errStr) 1052 { 1053 uint8_t *ptr = data->oemData; 1054 int genInfo = ptr[0]; 1055 int errType = genInfo & 0x0f; 1056 std::vector<std::string> dimmEvent = { 1057 "Memory training failure", "Memory correctable error", 1058 "Memory uncorrectable error", "Reserved"}; 1059 1060 std::stringstream tmpStream; 1061 tmpStream << std::hex << std::uppercase << std::setfill('0'); 1062 1063 switch (errType) 1064 { 1065 case unifiedPcieErr: 1066 if (((genInfo & 0x10) >> 4) == 0) // x86 1067 { 1068 tmpStream << "GeneralInfo: x86/PCIeErr(0x" << std::setw(2) 1069 << genInfo << "),"; 1070 } 1071 1072 tmpStream << " Bus " << std::setw(2) << (int)(ptr[8]) << "/Dev " 1073 << std::setw(2) << (int)(ptr[7] >> 3) << "/Fun " 1074 << std::setw(2) << (int)(ptr[7] & 0x7) 1075 << ", TotalErrID1Cnt: 0x" << std::setw(4) 1076 << (int)((ptr[10] << 8) | ptr[9]) << ", ErrID2: 0x" 1077 << std::setw(2) << (int)(ptr[11]) << ", ErrID1: 0x" 1078 << std::setw(2) << (int)(ptr[12]); 1079 1080 break; 1081 case unifiedMemErr: 1082 tmpStream << "GeneralInfo: MemErr(0x" << std::setw(2) << genInfo 1083 << "), DIMM Slot Location: Sled " << std::setw(2) 1084 << (int)((ptr[5] >> 4) & 0x03) << "/Socket " 1085 << std::setw(2) << (int)(ptr[5] & 0x0f) << ", Channel " 1086 << std::setw(2) << (int)(ptr[6] & 0x0f) << ", Slot " 1087 << std::setw(2) << (int)(ptr[7] & 0x0f) 1088 << ", DIMM Failure Event: " << dimmEvent[(ptr[9] & 0x03)] 1089 << ", Major Code: 0x" << std::setw(2) << (int)(ptr[10]) 1090 << ", Minor Code: 0x" << std::setw(2) << (int)(ptr[11]); 1091 1092 break; 1093 default: 1094 std::vector<uint8_t> oemData(ptr, ptr + 13); 1095 std::string oemDataStr; 1096 toHexStr(oemData, oemDataStr); 1097 tmpStream << "Undefined Error Type(0x" << std::setw(2) << errType 1098 << "), Raw: " << oemDataStr; 1099 } 1100 1101 errStr = tmpStream.str(); 1102 1103 return; 1104 } 1105 1106 static void parseSelData(std::vector<uint8_t> &reqData, std::string &msgLog) 1107 { 1108 1109 /* Get record type */ 1110 int recType = reqData[2]; 1111 std::string errType, errLog; 1112 1113 uint8_t *ptr = NULL; 1114 1115 std::stringstream recTypeStream; 1116 recTypeStream << std::hex << std::uppercase << std::setfill('0') 1117 << std::setw(2) << recType; 1118 1119 msgLog = "SEL Entry: FRU: 1, Record: "; 1120 1121 if (recType == stdErrType) 1122 { 1123 StdSELEntry *data = reinterpret_cast<StdSELEntry *>(&reqData[0]); 1124 std::string sensorName; 1125 1126 errType = stdErr; 1127 if (data->sensorType == 0x1F) 1128 { 1129 sensorName = "OS"; 1130 } 1131 else 1132 { 1133 auto findSensorName = sensorNameTable.find(data->sensorNum); 1134 if (findSensorName == sensorNameTable.end()) 1135 { 1136 sensorName = "Unknown"; 1137 } 1138 else 1139 { 1140 sensorName = findSensorName->second.first; 1141 } 1142 } 1143 1144 std::tm *ts = localtime((time_t *)(&(data->timeStamp))); 1145 std::string timeStr = std::asctime(ts); 1146 1147 parseStdSel(data, errLog); 1148 ptr = &(data->eventData1); 1149 std::vector<uint8_t> evtData(ptr, ptr + 3); 1150 std::string eventData; 1151 toHexStr(evtData, eventData); 1152 1153 std::stringstream senNumStream; 1154 senNumStream << std::hex << std::uppercase << std::setfill('0') 1155 << std::setw(2) << (int)(data->sensorNum); 1156 1157 msgLog += errType + " (0x" + recTypeStream.str() + 1158 "), Time: " + timeStr + ", Sensor: " + sensorName + " (0x" + 1159 senNumStream.str() + "), Event Data: (" + eventData + ") " + 1160 errLog; 1161 } 1162 else if ((recType >= oemTSErrTypeMin) && (recType <= oemTSErrTypeMax)) 1163 { 1164 /* timestamped OEM SEL records */ 1165 TsOemSELEntry *data = reinterpret_cast<TsOemSELEntry *>(&reqData[0]); 1166 ptr = data->mfrId; 1167 std::vector<uint8_t> mfrIdData(ptr, ptr + 3); 1168 std::string mfrIdStr; 1169 toHexStr(mfrIdData, mfrIdStr); 1170 1171 ptr = data->oemData; 1172 std::vector<uint8_t> oemData(ptr, ptr + 6); 1173 std::string oemDataStr; 1174 toHexStr(oemData, oemDataStr); 1175 1176 std::tm *ts = localtime((time_t *)(&(data->timeStamp))); 1177 std::string timeStr = std::asctime(ts); 1178 1179 errType = oemTSErr; 1180 parseOemSel(data, errLog); 1181 1182 msgLog += errType + " (0x" + recTypeStream.str() + 1183 "), Time: " + timeStr + ", MFG ID: " + mfrIdStr + 1184 ", OEM Data: (" + oemDataStr + ") " + errLog; 1185 } 1186 else if (recType == fbUniErrType) 1187 { 1188 NtsOemSELEntry *data = reinterpret_cast<NtsOemSELEntry *>(&reqData[0]); 1189 errType = fbUniSELErr; 1190 parseOemUnifiedSel(data, errLog); 1191 msgLog += errType + " (0x" + recTypeStream.str() + "), " + errLog; 1192 } 1193 else if ((recType >= oemNTSErrTypeMin) && (recType <= oemNTSErrTypeMax)) 1194 { 1195 /* Non timestamped OEM SEL records */ 1196 NtsOemSELEntry *data = reinterpret_cast<NtsOemSELEntry *>(&reqData[0]); 1197 errType = oemNTSErr; 1198 1199 ptr = data->oemData; 1200 std::vector<uint8_t> oemData(ptr, ptr + 13); 1201 std::string oemDataStr; 1202 toHexStr(oemData, oemDataStr); 1203 1204 parseOemSel((TsOemSELEntry *)data, errLog); 1205 msgLog += errType + " (0x" + recTypeStream.str() + "), OEM Data: (" + 1206 oemDataStr + ") " + errLog; 1207 } 1208 else 1209 { 1210 errType = unknownErr; 1211 toHexStr(reqData, errLog); 1212 msgLog += 1213 errType + " (0x" + recTypeStream.str() + ") RawData: " + errLog; 1214 } 1215 } 1216 1217 } // namespace fb_oem::ipmi::sel 1218 1219 namespace ipmi 1220 { 1221 1222 namespace storage 1223 { 1224 1225 static void registerSELFunctions() __attribute__((constructor)); 1226 static fb_oem::ipmi::sel::SELData selObj __attribute__((init_priority(101))); 1227 1228 ipmi::RspType<uint8_t, // SEL version 1229 uint16_t, // SEL entry count 1230 uint16_t, // free space 1231 uint32_t, // last add timestamp 1232 uint32_t, // last erase timestamp 1233 uint8_t> // operation support 1234 ipmiStorageGetSELInfo() 1235 { 1236 1237 fb_oem::ipmi::sel::GetSELInfoData info; 1238 1239 selObj.getInfo(info); 1240 return ipmi::responseSuccess(info.selVersion, info.entries, info.freeSpace, 1241 info.addTimeStamp, info.eraseTimeStamp, 1242 info.operationSupport); 1243 } 1244 1245 ipmi::RspType<uint16_t, std::vector<uint8_t>> 1246 ipmiStorageGetSELEntry(std::vector<uint8_t> data) 1247 { 1248 1249 if (data.size() != sizeof(fb_oem::ipmi::sel::GetSELEntryRequest)) 1250 { 1251 return ipmi::responseReqDataLenInvalid(); 1252 } 1253 1254 fb_oem::ipmi::sel::GetSELEntryRequest *reqData = 1255 reinterpret_cast<fb_oem::ipmi::sel::GetSELEntryRequest *>(&data[0]); 1256 1257 if (reqData->reservID != 0) 1258 { 1259 if (!checkSELReservation(reqData->reservID)) 1260 { 1261 return ipmi::responseInvalidReservationId(); 1262 } 1263 } 1264 1265 uint16_t selCnt = selObj.getCount(); 1266 if (selCnt == 0) 1267 { 1268 return ipmi::responseSensorInvalid(); 1269 } 1270 1271 /* If it is asked for first entry */ 1272 if (reqData->recordID == fb_oem::ipmi::sel::firstEntry) 1273 { 1274 /* First Entry (0x0000) as per Spec */ 1275 reqData->recordID = 1; 1276 } 1277 else if (reqData->recordID == fb_oem::ipmi::sel::lastEntry) 1278 { 1279 /* Last entry (0xFFFF) as per Spec */ 1280 reqData->recordID = selCnt; 1281 } 1282 1283 std::string ipmiRaw; 1284 1285 if (selObj.getEntry(reqData->recordID, ipmiRaw) < 0) 1286 { 1287 return ipmi::responseSensorInvalid(); 1288 } 1289 1290 std::vector<uint8_t> recDataBytes; 1291 if (fromHexStr(ipmiRaw, recDataBytes) < 0) 1292 { 1293 return ipmi::responseUnspecifiedError(); 1294 } 1295 1296 /* Identify the next SEL record ID. If recordID is same as 1297 * total SeL count then next id should be last entry else 1298 * it should be incremented by 1 to current RecordID 1299 */ 1300 uint16_t nextRecord; 1301 if (reqData->recordID == selCnt) 1302 { 1303 nextRecord = fb_oem::ipmi::sel::lastEntry; 1304 } 1305 else 1306 { 1307 nextRecord = reqData->recordID + 1; 1308 } 1309 1310 if (reqData->readLen == fb_oem::ipmi::sel::entireRecord) 1311 { 1312 return ipmi::responseSuccess(nextRecord, recDataBytes); 1313 } 1314 else 1315 { 1316 if (reqData->offset >= fb_oem::ipmi::sel::selRecordSize || 1317 reqData->readLen > fb_oem::ipmi::sel::selRecordSize) 1318 { 1319 return ipmi::responseUnspecifiedError(); 1320 } 1321 std::vector<uint8_t> recPartData; 1322 1323 auto diff = fb_oem::ipmi::sel::selRecordSize - reqData->offset; 1324 auto readLength = std::min(diff, static_cast<int>(reqData->readLen)); 1325 1326 for (int i = 0; i < readLength; i++) 1327 { 1328 recPartData.push_back(recDataBytes[i + reqData->offset]); 1329 } 1330 return ipmi::responseSuccess(nextRecord, recPartData); 1331 } 1332 } 1333 1334 ipmi::RspType<uint16_t> ipmiStorageAddSELEntry(std::vector<uint8_t> data) 1335 { 1336 /* Per the IPMI spec, need to cancel any reservation when a 1337 * SEL entry is added 1338 */ 1339 cancelSELReservation(); 1340 1341 if (data.size() != fb_oem::ipmi::sel::selRecordSize) 1342 { 1343 return ipmi::responseReqDataLenInvalid(); 1344 } 1345 1346 std::string ipmiRaw, logErr; 1347 toHexStr(data, ipmiRaw); 1348 1349 /* Parse sel data and get an error log to be filed */ 1350 fb_oem::ipmi::sel::parseSelData(data, logErr); 1351 1352 static const std::string openBMCMessageRegistryVersion("0.1"); 1353 std::string messageID = 1354 "OpenBMC." + openBMCMessageRegistryVersion + ".SELEntryAdded"; 1355 1356 /* Log the Raw SEL message to the journal */ 1357 std::string journalMsg = "SEL Entry Added: " + ipmiRaw; 1358 1359 phosphor::logging::log<phosphor::logging::level::INFO>( 1360 journalMsg.c_str(), 1361 phosphor::logging::entry("IPMISEL_MESSAGE_ID=%s", messageID.c_str()), 1362 phosphor::logging::entry("IPMISEL_MESSAGE_ARGS=%s", logErr.c_str())); 1363 1364 int responseID = selObj.addEntry(ipmiRaw.c_str()); 1365 if (responseID < 0) 1366 { 1367 return ipmi::responseUnspecifiedError(); 1368 } 1369 return ipmi::responseSuccess((uint16_t)responseID); 1370 } 1371 1372 ipmi::RspType<uint8_t> ipmiStorageClearSEL(uint16_t reservationID, 1373 const std::array<uint8_t, 3> &clr, 1374 uint8_t eraseOperation) 1375 { 1376 if (!checkSELReservation(reservationID)) 1377 { 1378 return ipmi::responseInvalidReservationId(); 1379 } 1380 1381 static constexpr std::array<uint8_t, 3> clrExpected = {'C', 'L', 'R'}; 1382 if (clr != clrExpected) 1383 { 1384 return ipmi::responseInvalidFieldRequest(); 1385 } 1386 1387 /* If there is no sel then return erase complete */ 1388 if (selObj.getCount() == 0) 1389 { 1390 return ipmi::responseSuccess(fb_oem::ipmi::sel::eraseComplete); 1391 } 1392 1393 /* Erasure status cannot be fetched, so always return erasure 1394 * status as `erase completed`. 1395 */ 1396 if (eraseOperation == fb_oem::ipmi::sel::getEraseStatus) 1397 { 1398 return ipmi::responseSuccess(fb_oem::ipmi::sel::eraseComplete); 1399 } 1400 1401 /* Check that initiate erase is correct */ 1402 if (eraseOperation != fb_oem::ipmi::sel::initiateErase) 1403 { 1404 return ipmi::responseInvalidFieldRequest(); 1405 } 1406 1407 /* Per the IPMI spec, need to cancel any reservation when the 1408 * SEL is cleared 1409 */ 1410 cancelSELReservation(); 1411 1412 /* Clear the complete Sel Json object */ 1413 if (selObj.clear() < 0) 1414 { 1415 return ipmi::responseUnspecifiedError(); 1416 } 1417 1418 return ipmi::responseSuccess(fb_oem::ipmi::sel::eraseComplete); 1419 } 1420 1421 ipmi::RspType<uint32_t> ipmiStorageGetSELTime() 1422 { 1423 struct timespec selTime = {}; 1424 1425 if (clock_gettime(CLOCK_REALTIME, &selTime) < 0) 1426 { 1427 return ipmi::responseUnspecifiedError(); 1428 } 1429 1430 return ipmi::responseSuccess(selTime.tv_sec); 1431 } 1432 1433 ipmi::RspType<> ipmiStorageSetSELTime(uint32_t selTime) 1434 { 1435 // Set SEL Time is not supported 1436 return ipmi::responseInvalidCommand(); 1437 } 1438 1439 ipmi::RspType<uint16_t> ipmiStorageGetSELTimeUtcOffset() 1440 { 1441 /* TODO: For now, the SEL time stamp is based on UTC time, 1442 * so return 0x0000 as offset. Might need to change once 1443 * supporting zones in SEL time stamps 1444 */ 1445 1446 uint16_t utcOffset = 0x0000; 1447 return ipmi::responseSuccess(utcOffset); 1448 } 1449 1450 void registerSELFunctions() 1451 { 1452 // <Get SEL Info> 1453 ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage, 1454 ipmi::storage::cmdGetSelInfo, ipmi::Privilege::User, 1455 ipmiStorageGetSELInfo); 1456 1457 // <Get SEL Entry> 1458 ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage, 1459 ipmi::storage::cmdGetSelEntry, ipmi::Privilege::User, 1460 ipmiStorageGetSELEntry); 1461 1462 // <Add SEL Entry> 1463 ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage, 1464 ipmi::storage::cmdAddSelEntry, 1465 ipmi::Privilege::Operator, ipmiStorageAddSELEntry); 1466 1467 // <Clear SEL> 1468 ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage, 1469 ipmi::storage::cmdClearSel, ipmi::Privilege::Operator, 1470 ipmiStorageClearSEL); 1471 1472 // <Get SEL Time> 1473 ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage, 1474 ipmi::storage::cmdGetSelTime, ipmi::Privilege::User, 1475 ipmiStorageGetSELTime); 1476 1477 // <Set SEL Time> 1478 ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage, 1479 ipmi::storage::cmdSetSelTime, 1480 ipmi::Privilege::Operator, ipmiStorageSetSELTime); 1481 1482 // <Get SEL Time UTC Offset> 1483 ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage, 1484 ipmi::storage::cmdGetSelTimeUtcOffset, 1485 ipmi::Privilege::User, 1486 ipmiStorageGetSELTimeUtcOffset); 1487 1488 return; 1489 } 1490 1491 } // namespace storage 1492 } // namespace ipmi 1493