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 <boost/algorithm/string/join.hpp> 19 #include <ipmid/api.hpp> 20 #include <nlohmann/json.hpp> 21 #include <phosphor-logging/log.hpp> 22 #include <sdbusplus/message/types.hpp> 23 #include <sdbusplus/timer.hpp> 24 #include <storagecommands.hpp> 25 26 #include <fstream> 27 #include <iostream> 28 #include <sstream> 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 uint32_t venId = (uint32_t)data[1] << 8 | (uint32_t)data[2]; 367 tmp2 << "Vendor ID: 0x" << std::setw(4) << venId; 368 errLog = tmp2.str(); 369 } 370 break; 371 case 0xE: { 372 uint32_t devId = (uint32_t)data[1] << 8 | (uint32_t)data[2]; 373 tmp2 << "Device ID: 0x" << std::setw(4) << devId; 374 errLog = tmp2.str(); 375 } 376 break; 377 case 0xF: 378 tmp2 << "Error ID from downstream: 0x" << std::setw(2) 379 << (int)(data[1]) << std::setw(2) << (int)(data[2]); 380 errLog = tmp2.str(); 381 break; 382 default: 383 errLog = "Unknown"; 384 } 385 } 386 387 static void logIioErr(uint8_t* data, std::string& errLog) 388 { 389 std::vector<std::string> tmpStr = { 390 "IRP0", "IRP1", " IIO-Core", "VT-d", "Intel Quick Data", 391 "Misc", " DMA", "ITC", "OTC", "CI"}; 392 393 if ((data[0] & 0xF) == 0) 394 { 395 errLog += "CPU " + std::to_string(data[2] >> 5) + ", Error ID 0x" + 396 byteToStr(data[1]) + " - "; 397 398 if ((data[2] & 0xF) <= 0x9) 399 { 400 errLog += tmpStr[(data[2] & 0xF)]; 401 } 402 else 403 { 404 errLog += "Reserved"; 405 } 406 } 407 else 408 { 409 errLog = "Unknown"; 410 } 411 } 412 413 static void logMemErr(uint8_t* dataPtr, std::string& errLog) 414 { 415 uint8_t snrType = dataPtr[0]; 416 uint8_t snrNum = dataPtr[1]; 417 uint8_t* data = &(dataPtr[3]); 418 419 /* TODO: add pal_add_cri_sel */ 420 421 if (snrNum == memoryEccError) 422 { 423 /* SEL from MEMORY_ECC_ERR Sensor */ 424 switch (data[0] & 0x0F) 425 { 426 case 0x0: 427 if (snrType == 0x0C) 428 { 429 errLog = "Correctable"; 430 } 431 else if (snrType == 0x10) 432 { 433 errLog = "Correctable ECC error Logging Disabled"; 434 } 435 break; 436 case 0x1: 437 errLog = "Uncorrectable"; 438 break; 439 case 0x5: 440 errLog = "Correctable ECC error Logging Limit Disabled"; 441 break; 442 default: 443 errLog = "Unknown"; 444 } 445 } 446 else if (snrNum == memoryErrLogDIS) 447 { 448 // SEL from MEMORY_ERR_LOG_DIS Sensor 449 if ((data[0] & 0x0F) == 0x0) 450 { 451 errLog = "Correctable Memory Error Logging Disabled"; 452 } 453 else 454 { 455 errLog = "Unknown"; 456 } 457 } 458 else 459 { 460 errLog = "Unknown"; 461 return; 462 } 463 464 /* Common routine for both MEM_ECC_ERR and MEMORY_ERR_LOG_DIS */ 465 466 errLog += " (DIMM " + byteToStr(data[2]) + ") Logical Rank " + 467 std::to_string(data[1] & 0x03); 468 469 /* DIMM number (data[2]): 470 * Bit[7:5]: Socket number (Range: 0-7) 471 * Bit[4:3]: Channel number (Range: 0-3) 472 * Bit[2:0]: DIMM number (Range: 0-7) 473 */ 474 475 /* TODO: Verify these bits */ 476 std::string cpuStr = "CPU# " + std::to_string((data[2] & 0xE0) >> 5); 477 std::string chStr = "CHN# " + std::to_string((data[2] & 0x18) >> 3); 478 std::string dimmStr = "DIMM# " + std::to_string(data[2] & 0x7); 479 480 switch ((data[1] & 0xC) >> 2) 481 { 482 case 0x0: { 483 484 /* All Info Valid */ 485 uint8_t chnNum = (data[2] & 0x1C) >> 2; 486 uint8_t dimmNum = data[2] & 0x3; 487 488 /* TODO: If critical SEL logging is available, do it */ 489 if (snrType == 0x0C) 490 { 491 if ((data[0] & 0x0F) == 0x0) 492 { 493 /* TODO: add_cri_sel */ 494 /* "DIMM"+ 'A'+ chnNum + dimmNum + " ECC err,FRU:1" 495 */ 496 } 497 else if ((data[0] & 0x0F) == 0x1) 498 { 499 /* TODO: add_cri_sel */ 500 /* "DIMM"+ 'A'+ chnNum + dimmNum + " UECC err,FRU:1" 501 */ 502 } 503 } 504 /* Continue to parse the error into a string. All Info Valid 505 */ 506 errLog += " (" + cpuStr + ", " + chStr + ", " + dimmStr + ")"; 507 } 508 509 break; 510 case 0x1: 511 512 /* DIMM info not valid */ 513 errLog += " (" + cpuStr + ", " + chStr + ")"; 514 break; 515 case 0x2: 516 517 /* CHN info not valid */ 518 errLog += " (" + cpuStr + ", " + dimmStr + ")"; 519 break; 520 case 0x3: 521 522 /* CPU info not valid */ 523 errLog += " (" + chStr + ", " + dimmStr + ")"; 524 break; 525 } 526 } 527 528 static void logPwrErr(uint8_t* data, std::string& errLog) 529 { 530 531 if (data[0] == 0x1) 532 { 533 errLog = "SYS_PWROK failure"; 534 /* Also try logging to Critial log file, if available */ 535 /* "SYS_PWROK failure,FRU:1" */ 536 } 537 else if (data[0] == 0x2) 538 { 539 errLog = "PCH_PWROK failure"; 540 /* Also try logging to Critial log file, if available */ 541 /* "PCH_PWROK failure,FRU:1" */ 542 } 543 else 544 { 545 errLog = "Unknown"; 546 } 547 } 548 549 static void logCatErr(uint8_t* data, std::string& errLog) 550 { 551 552 if (data[0] == 0x0) 553 { 554 errLog = "IERR/CATERR"; 555 /* Also try logging to Critial log file, if available */ 556 /* "IERR,FRU:1 */ 557 } 558 else if (data[0] == 0xB) 559 { 560 errLog = "MCERR/CATERR"; 561 /* Also try logging to Critial log file, if available */ 562 /* "MCERR,FRU:1 */ 563 } 564 else 565 { 566 errLog = "Unknown"; 567 } 568 } 569 570 static void logDimmHot(uint8_t* data, std::string& errLog) 571 { 572 if ((data[0] << 16 | data[1] << 8 | data[2]) == 0x01FFFF) 573 { 574 errLog = "SOC MEMHOT"; 575 } 576 else 577 { 578 errLog = "Unknown"; 579 /* Also try logging to Critial log file, if available */ 580 /* ""CPU_DIMM_HOT %s,FRU:1" */ 581 } 582 } 583 584 static void logSwNMI(uint8_t* data, std::string& errLog) 585 { 586 if ((data[0] << 16 | data[1] << 8 | data[2]) == 0x03FFFF) 587 { 588 errLog = "Software NMI"; 589 } 590 else 591 { 592 errLog = "Unknown SW NMI"; 593 } 594 } 595 596 static void logCPUThermalSts(uint8_t* data, std::string& errLog) 597 { 598 switch (data[0]) 599 { 600 case 0x0: 601 errLog = "CPU Critical Temperature"; 602 break; 603 case 0x1: 604 errLog = "PROCHOT#"; 605 break; 606 case 0x2: 607 errLog = "TCC Activation"; 608 break; 609 default: 610 errLog = "Unknown"; 611 } 612 } 613 614 static void logMEPwrState(uint8_t* data, std::string& errLog) 615 { 616 switch (data[0]) 617 { 618 case 0: 619 errLog = "RUNNING"; 620 break; 621 case 2: 622 errLog = "POWER_OFF"; 623 break; 624 default: 625 errLog = "Unknown[" + std::to_string(data[0]) + "]"; 626 break; 627 } 628 } 629 630 static void logSPSFwHealth(uint8_t* data, std::string& errLog) 631 { 632 if ((data[0] & 0x0F) == 0x00) 633 { 634 const std::vector<std::string> tmpStr = { 635 "Recovery GPIO forced", 636 "Image execution failed", 637 "Flash erase error", 638 "Flash state information", 639 "Internal error", 640 "BMC did not respond", 641 "Direct Flash update", 642 "Manufacturing error", 643 "Automatic Restore to Factory Presets", 644 "Firmware Exception", 645 "Flash Wear-Out Protection Warning", 646 "Unknown", 647 "Unknown", 648 "DMI interface error", 649 "MCTP interface error", 650 "Auto-configuration finished", 651 "Unsupported Segment Defined Feature", 652 "Unknown", 653 "CPU Debug Capability Disabled", 654 "UMA operation error"}; 655 656 if (data[1] < 0x14) 657 { 658 errLog = tmpStr[data[1]]; 659 } 660 else 661 { 662 errLog = "Unknown"; 663 } 664 } 665 else if ((data[0] & 0x0F) == 0x01) 666 { 667 errLog = "SMBus link failure"; 668 } 669 else 670 { 671 errLog = "Unknown"; 672 } 673 } 674 675 static void logNmExcA(uint8_t* data, std::string& errLog) 676 { 677 /*NM4.0 #550710, Revision 1.95, and turn to p.155*/ 678 if (data[0] == 0xA8) 679 { 680 errLog = "Policy Correction Time Exceeded"; 681 } 682 else 683 { 684 errLog = "Unknown"; 685 } 686 } 687 688 static void logPCHThermal(uint8_t* data, std::string& errLog) 689 { 690 const std::vector<std::string> thresEvtName = {"Lower Non-critical", 691 "Unknown", 692 "Lower Critical", 693 "Unknown", 694 "Lower Non-recoverable", 695 "Unknown", 696 "Unknown", 697 "Upper Non-critical", 698 "Unknown", 699 "Upper Critical", 700 "Unknown", 701 "Upper Non-recoverable"}; 702 703 if ((data[0] & 0x0f) < 12) 704 { 705 errLog = thresEvtName[(data[0] & 0x0f)]; 706 } 707 else 708 { 709 errLog = "Unknown"; 710 } 711 712 errLog += ", curr_val: " + std::to_string(data[1]) + 713 " C, thresh_val: " + std::to_string(data[2]) + " C"; 714 } 715 716 static void logNmHealth(uint8_t* data, std::string& errLog) 717 { 718 std::vector<std::string> nmErrType = { 719 "Unknown", 720 "Unknown", 721 "Unknown", 722 "Unknown", 723 "Unknown", 724 "Unknown", 725 "Unknown", 726 "Extended Telemetry Device Reading Failure", 727 "Outlet Temperature Reading Failure", 728 "Volumetric Airflow Reading Failure", 729 "Policy Misconfiguration", 730 "Power Sensor Reading Failure", 731 "Inlet Temperature Reading Failure", 732 "Host Communication Error", 733 "Real-time Clock Synchronization Failure", 734 "Platform Shutdown Initiated by Intel NM Policy", 735 "Unknown"}; 736 uint8_t nmTypeIdx = (data[0] & 0xf); 737 uint8_t domIdx = (data[1] & 0xf); 738 uint8_t errIdx = ((data[1] >> 4) & 0xf); 739 740 if (nmTypeIdx == 2) 741 { 742 errLog = "SensorIntelNM"; 743 } 744 else 745 { 746 errLog = "Unknown"; 747 } 748 749 errLog += ", Domain:" + nmDomName[domIdx] + 750 ", ErrType:" + nmErrType[errIdx] + ", Err:0x" + 751 byteToStr(data[2]); 752 } 753 754 static void logNmCap(uint8_t* data, std::string& errLog) 755 { 756 757 const std::vector<std::string> nmCapStsStr = {"Not Available", "Available"}; 758 if (data[0] & 0x7) // BIT1=policy, BIT2=monitoring, BIT3=pwr 759 // limit and the others are reserved 760 { 761 errLog = "PolicyInterface:" + nmCapStsStr[BIT(data[0], 0)] + 762 ",Monitoring:" + nmCapStsStr[BIT(data[0], 1)] + 763 ",PowerLimit:" + nmCapStsStr[BIT(data[0], 2)]; 764 } 765 else 766 { 767 errLog = "Unknown"; 768 } 769 } 770 771 static void logNmThreshold(uint8_t* data, std::string& errLog) 772 { 773 uint8_t thresNum = (data[0] & 0x3); 774 uint8_t domIdx = (data[1] & 0xf); 775 uint8_t polId = data[2]; 776 uint8_t polEvtIdx = BIT(data[0], 3); 777 const std::vector<std::string> polEvtStr = { 778 "Threshold Exceeded", "Policy Correction Time Exceeded"}; 779 780 errLog = "Threshold Number:" + std::to_string(thresNum) + "-" + 781 polEvtStr[polEvtIdx] + ", Domain:" + nmDomName[domIdx] + 782 ", PolicyID:0x" + byteToStr(polId); 783 } 784 785 static void logPwrThreshold(uint8_t* data, std::string& errLog) 786 { 787 if (data[0] == 0x00) 788 { 789 errLog = "Limit Not Exceeded"; 790 } 791 else if (data[0] == 0x01) 792 { 793 errLog = "Limit Exceeded"; 794 } 795 else 796 { 797 errLog = "Unknown"; 798 } 799 } 800 801 static void logMSMI(uint8_t* data, std::string& errLog) 802 { 803 804 if (data[0] == 0x0) 805 { 806 errLog = "IERR/MSMI"; 807 } 808 else if (data[0] == 0x0B) 809 { 810 errLog = "MCERR/MSMI"; 811 } 812 else 813 { 814 errLog = "Unknown"; 815 } 816 } 817 818 static void logHprWarn(uint8_t* data, std::string& errLog) 819 { 820 if (data[2] == 0x01) 821 { 822 if (data[1] == 0xFF) 823 { 824 errLog = "Infinite Time"; 825 } 826 else 827 { 828 errLog = std::to_string(data[1]) + " minutes"; 829 } 830 } 831 else 832 { 833 errLog = "Unknown"; 834 } 835 } 836 837 static const boost::container::flat_map< 838 uint8_t, 839 std::pair<std::string, std::function<void(uint8_t*, std::string&)>>> 840 sensorNameTable = {{0xE9, {"SYSTEM_EVENT", logSysEvent}}, 841 {0x7D, {"THERM_THRESH_EVT", logThermalEvent}}, 842 {0xAA, {"BUTTON", logDefault}}, 843 {0xAB, {"POWER_STATE", logDefault}}, 844 {0xEA, {"CRITICAL_IRQ", logCritIrq}}, 845 {0x2B, {"POST_ERROR", logPostErr}}, 846 {0x40, {"MACHINE_CHK_ERR", logMchChkErr}}, 847 {0x41, {"PCIE_ERR", logPcieErr}}, 848 {0x43, {"IIO_ERR", logIioErr}}, 849 {0X63, {"MEMORY_ECC_ERR", logDefault}}, 850 {0X87, {"MEMORY_ERR_LOG_DIS", logDefault}}, 851 {0X51, {"PROCHOT_EXT", logDefault}}, 852 {0X56, {"PWR_ERR", logPwrErr}}, 853 {0xE6, {"CATERR_A", logCatErr}}, 854 {0xEB, {"CATERR_B", logCatErr}}, 855 {0xB3, {"CPU_DIMM_HOT", logDimmHot}}, 856 {0x90, {"SOFTWARE_NMI", logSwNMI}}, 857 {0x1C, {"CPU0_THERM_STATUS", logCPUThermalSts}}, 858 {0x1D, {"CPU1_THERM_STATUS", logCPUThermalSts}}, 859 {0x16, {"ME_POWER_STATE", logMEPwrState}}, 860 {0x17, {"SPS_FW_HEALTH", logSPSFwHealth}}, 861 {0x18, {"NM_EXCEPTION_A", logNmExcA}}, 862 {0x08, {"PCH_THERM_THRESHOLD", logPCHThermal}}, 863 {0x19, {"NM_HEALTH", logNmHealth}}, 864 {0x1A, {"NM_CAPABILITIES", logNmCap}}, 865 {0x1B, {"NM_THRESHOLD", logNmThreshold}}, 866 {0x3B, {"PWR_THRESH_EVT", logPwrThreshold}}, 867 {0xE7, {"MSMI", logMSMI}}, 868 {0xC5, {"HPR_WARNING", logHprWarn}}}; 869 870 static void parseSelHelper(StdSELEntry* data, std::string& errStr) 871 { 872 873 /* Check if sensor type is OS_BOOT (0x1f) */ 874 if (data->sensorType == 0x1F) 875 { 876 /* OS_BOOT used by OS */ 877 switch (data->eventData1 & 0xF) 878 { 879 case 0x07: 880 errStr = "Base OS/Hypervisor Installation started"; 881 break; 882 case 0x08: 883 errStr = "Base OS/Hypervisor Installation completed"; 884 break; 885 case 0x09: 886 errStr = "Base OS/Hypervisor Installation aborted"; 887 break; 888 case 0x0A: 889 errStr = "Base OS/Hypervisor Installation failed"; 890 break; 891 default: 892 errStr = "Unknown"; 893 } 894 return; 895 } 896 897 auto findSensorName = sensorNameTable.find(data->sensorNum); 898 if (findSensorName == sensorNameTable.end()) 899 { 900 errStr = "Unknown"; 901 return; 902 } 903 else 904 { 905 switch (data->sensorNum) 906 { 907 /* logMemErr function needs data from sensor type */ 908 case memoryEccError: 909 case memoryErrLogDIS: 910 findSensorName->second.second(&(data->sensorType), errStr); 911 break; 912 /* Other sensor function needs only event data for parsing */ 913 default: 914 findSensorName->second.second(&(data->eventData1), errStr); 915 } 916 } 917 918 if (((data->eventData3 & 0x80) >> 7) == 0) 919 { 920 errStr += " Assertion"; 921 } 922 else 923 { 924 errStr += " Deassertion"; 925 } 926 } 927 928 static void parseStdSel(StdSELEntry* data, std::string& errStr) 929 { 930 std::stringstream tmpStream; 931 tmpStream << std::hex << std::uppercase; 932 933 /* TODO: add pal_add_cri_sel */ 934 switch (data->sensorNum) 935 { 936 case memoryEccError: 937 switch (data->eventData1 & 0x0F) 938 { 939 case 0x00: 940 errStr = "Correctable"; 941 tmpStream << "DIMM" << std::setw(2) << std::setfill('0') 942 << data->eventData3 << " ECC err"; 943 break; 944 case 0x01: 945 errStr = "Uncorrectable"; 946 tmpStream << "DIMM" << std::setw(2) << std::setfill('0') 947 << data->eventData3 << " UECC err"; 948 break; 949 case 0x02: 950 errStr = "Parity"; 951 break; 952 case 0x05: 953 errStr = "Correctable ECC error Logging Limit Reached"; 954 break; 955 default: 956 errStr = "Unknown"; 957 } 958 break; 959 case memoryErrLogDIS: 960 if ((data->eventData1 & 0x0F) == 0) 961 { 962 errStr = "Correctable Memory Error Logging Disabled"; 963 } 964 else 965 { 966 errStr = "Unknown"; 967 } 968 break; 969 default: 970 parseSelHelper(data, errStr); 971 return; 972 } 973 974 errStr += " (DIMM " + std::to_string(data->eventData3) + ")"; 975 errStr += " Logical Rank " + std::to_string(data->eventData2 & 0x03); 976 977 switch ((data->eventData2 & 0x0C) >> 2) 978 { 979 case 0x00: 980 // Ignore when " All info available" 981 break; 982 case 0x01: 983 errStr += " DIMM info not valid"; 984 break; 985 case 0x02: 986 errStr += " CHN info not valid"; 987 break; 988 case 0x03: 989 errStr += " CPU info not valid"; 990 break; 991 default: 992 errStr += " Unknown"; 993 } 994 995 if (((data->eventType & 0x80) >> 7) == 0) 996 { 997 errStr += " Assertion"; 998 } 999 else 1000 { 1001 errStr += " Deassertion"; 1002 } 1003 1004 return; 1005 } 1006 1007 static void parseOemSel(TsOemSELEntry* data, std::string& errStr) 1008 { 1009 std::stringstream tmpStream; 1010 tmpStream << std::hex << std::uppercase << std::setfill('0'); 1011 1012 switch (data->recordType) 1013 { 1014 case 0xC0: 1015 tmpStream << "VID:0x" << std::setw(2) << (int)data->oemData[1] 1016 << std::setw(2) << (int)data->oemData[0] << " DID:0x" 1017 << std::setw(2) << (int)data->oemData[3] << std::setw(2) 1018 << (int)data->oemData[2] << " Slot:0x" << std::setw(2) 1019 << (int)data->oemData[4] << " Error ID:0x" << std::setw(2) 1020 << (int)data->oemData[5]; 1021 break; 1022 case 0xC2: 1023 tmpStream << "Extra info:0x" << std::setw(2) 1024 << (int)data->oemData[1] << " MSCOD:0x" << std::setw(2) 1025 << (int)data->oemData[3] << std::setw(2) 1026 << (int)data->oemData[2] << " MCACOD:0x" << std::setw(2) 1027 << (int)data->oemData[5] << std::setw(2) 1028 << (int)data->oemData[4]; 1029 break; 1030 case 0xC3: 1031 int bank = (data->oemData[1] & 0xf0) >> 4; 1032 int col = ((data->oemData[1] & 0x0f) << 8) | data->oemData[2]; 1033 1034 tmpStream << "Fail Device:0x" << std::setw(2) 1035 << (int)data->oemData[0] << " Bank:0x" << std::setw(2) 1036 << bank << " Column:0x" << std::setw(2) << col 1037 << " Failed Row:0x" << std::setw(2) 1038 << (int)data->oemData[3] << std::setw(2) 1039 << (int)data->oemData[4] << std::setw(2) 1040 << (int)data->oemData[5]; 1041 } 1042 1043 errStr = tmpStream.str(); 1044 1045 return; 1046 } 1047 1048 static void parseOemUnifiedSel(NtsOemSELEntry* data, std::string& errStr) 1049 { 1050 uint8_t* ptr = data->oemData; 1051 int genInfo = ptr[0]; 1052 int errType = genInfo & 0x0f; 1053 std::vector<std::string> dimmEvent = { 1054 "Memory training failure", "Memory correctable error", 1055 "Memory uncorrectable error", "Reserved"}; 1056 1057 std::stringstream tmpStream; 1058 tmpStream << std::hex << std::uppercase << std::setfill('0'); 1059 1060 switch (errType) 1061 { 1062 case unifiedPcieErr: 1063 if (((genInfo & 0x10) >> 4) == 0) // x86 1064 { 1065 tmpStream << "GeneralInfo: x86/PCIeErr(0x" << std::setw(2) 1066 << genInfo << "),"; 1067 } 1068 1069 tmpStream << " Bus " << std::setw(2) << (int)(ptr[8]) << "/Dev " 1070 << std::setw(2) << (int)(ptr[7] >> 3) << "/Fun " 1071 << std::setw(2) << (int)(ptr[7] & 0x7) 1072 << ", TotalErrID1Cnt: 0x" << std::setw(4) 1073 << (int)((ptr[10] << 8) | ptr[9]) << ", ErrID2: 0x" 1074 << std::setw(2) << (int)(ptr[11]) << ", ErrID1: 0x" 1075 << std::setw(2) << (int)(ptr[12]); 1076 1077 break; 1078 case unifiedMemErr: 1079 tmpStream << "GeneralInfo: MemErr(0x" << std::setw(2) << genInfo 1080 << "), DIMM Slot Location: Sled " << std::setw(2) 1081 << (int)((ptr[5] >> 4) & 0x03) << "/Socket " 1082 << std::setw(2) << (int)(ptr[5] & 0x0f) << ", Channel " 1083 << std::setw(2) << (int)(ptr[6] & 0x0f) << ", Slot " 1084 << std::setw(2) << (int)(ptr[7] & 0x0f) 1085 << ", DIMM Failure Event: " << dimmEvent[(ptr[9] & 0x03)] 1086 << ", Major Code: 0x" << std::setw(2) << (int)(ptr[10]) 1087 << ", Minor Code: 0x" << std::setw(2) << (int)(ptr[11]); 1088 1089 break; 1090 default: 1091 std::vector<uint8_t> oemData(ptr, ptr + 13); 1092 std::string oemDataStr; 1093 toHexStr(oemData, oemDataStr); 1094 tmpStream << "Undefined Error Type(0x" << std::setw(2) << errType 1095 << "), Raw: " << oemDataStr; 1096 } 1097 1098 errStr = tmpStream.str(); 1099 1100 return; 1101 } 1102 1103 static void parseSelData(std::vector<uint8_t>& reqData, std::string& msgLog) 1104 { 1105 1106 /* Get record type */ 1107 int recType = reqData[2]; 1108 std::string errType, errLog; 1109 1110 uint8_t* ptr = NULL; 1111 1112 std::stringstream recTypeStream; 1113 recTypeStream << std::hex << std::uppercase << std::setfill('0') 1114 << std::setw(2) << recType; 1115 1116 msgLog = "SEL Entry: FRU: 1, Record: "; 1117 1118 if (recType == stdErrType) 1119 { 1120 StdSELEntry* data = reinterpret_cast<StdSELEntry*>(&reqData[0]); 1121 std::string sensorName; 1122 1123 errType = stdErr; 1124 if (data->sensorType == 0x1F) 1125 { 1126 sensorName = "OS"; 1127 } 1128 else 1129 { 1130 auto findSensorName = sensorNameTable.find(data->sensorNum); 1131 if (findSensorName == sensorNameTable.end()) 1132 { 1133 sensorName = "Unknown"; 1134 } 1135 else 1136 { 1137 sensorName = findSensorName->second.first; 1138 } 1139 } 1140 1141 std::tm* ts = localtime((time_t*)(&(data->timeStamp))); 1142 std::string timeStr = std::asctime(ts); 1143 1144 parseStdSel(data, errLog); 1145 ptr = &(data->eventData1); 1146 std::vector<uint8_t> evtData(ptr, ptr + 3); 1147 std::string eventData; 1148 toHexStr(evtData, eventData); 1149 1150 std::stringstream senNumStream; 1151 senNumStream << std::hex << std::uppercase << std::setfill('0') 1152 << std::setw(2) << (int)(data->sensorNum); 1153 1154 msgLog += errType + " (0x" + recTypeStream.str() + 1155 "), Time: " + timeStr + ", Sensor: " + sensorName + " (0x" + 1156 senNumStream.str() + "), Event Data: (" + eventData + ") " + 1157 errLog; 1158 } 1159 else if ((recType >= oemTSErrTypeMin) && (recType <= oemTSErrTypeMax)) 1160 { 1161 /* timestamped OEM SEL records */ 1162 TsOemSELEntry* data = reinterpret_cast<TsOemSELEntry*>(&reqData[0]); 1163 ptr = data->mfrId; 1164 std::vector<uint8_t> mfrIdData(ptr, ptr + 3); 1165 std::string mfrIdStr; 1166 toHexStr(mfrIdData, mfrIdStr); 1167 1168 ptr = data->oemData; 1169 std::vector<uint8_t> oemData(ptr, ptr + 6); 1170 std::string oemDataStr; 1171 toHexStr(oemData, oemDataStr); 1172 1173 std::tm* ts = localtime((time_t*)(&(data->timeStamp))); 1174 std::string timeStr = std::asctime(ts); 1175 1176 errType = oemTSErr; 1177 parseOemSel(data, errLog); 1178 1179 msgLog += errType + " (0x" + recTypeStream.str() + 1180 "), Time: " + timeStr + ", MFG ID: " + mfrIdStr + 1181 ", OEM Data: (" + oemDataStr + ") " + errLog; 1182 } 1183 else if (recType == fbUniErrType) 1184 { 1185 NtsOemSELEntry* data = reinterpret_cast<NtsOemSELEntry*>(&reqData[0]); 1186 errType = fbUniSELErr; 1187 parseOemUnifiedSel(data, errLog); 1188 msgLog += errType + " (0x" + recTypeStream.str() + "), " + errLog; 1189 } 1190 else if ((recType >= oemNTSErrTypeMin) && (recType <= oemNTSErrTypeMax)) 1191 { 1192 /* Non timestamped OEM SEL records */ 1193 NtsOemSELEntry* data = reinterpret_cast<NtsOemSELEntry*>(&reqData[0]); 1194 errType = oemNTSErr; 1195 1196 ptr = data->oemData; 1197 std::vector<uint8_t> oemData(ptr, ptr + 13); 1198 std::string oemDataStr; 1199 toHexStr(oemData, oemDataStr); 1200 1201 parseOemSel((TsOemSELEntry*)data, errLog); 1202 msgLog += errType + " (0x" + recTypeStream.str() + "), OEM Data: (" + 1203 oemDataStr + ") " + errLog; 1204 } 1205 else 1206 { 1207 errType = unknownErr; 1208 toHexStr(reqData, errLog); 1209 msgLog += 1210 errType + " (0x" + recTypeStream.str() + ") RawData: " + errLog; 1211 } 1212 } 1213 1214 } // namespace fb_oem::ipmi::sel 1215 1216 namespace ipmi 1217 { 1218 1219 namespace storage 1220 { 1221 1222 static void registerSELFunctions() __attribute__((constructor)); 1223 static fb_oem::ipmi::sel::SELData selObj __attribute__((init_priority(101))); 1224 1225 ipmi::RspType<uint8_t, // SEL version 1226 uint16_t, // SEL entry count 1227 uint16_t, // free space 1228 uint32_t, // last add timestamp 1229 uint32_t, // last erase timestamp 1230 uint8_t> // operation support 1231 ipmiStorageGetSELInfo() 1232 { 1233 1234 fb_oem::ipmi::sel::GetSELInfoData info; 1235 1236 selObj.getInfo(info); 1237 return ipmi::responseSuccess(info.selVersion, info.entries, info.freeSpace, 1238 info.addTimeStamp, info.eraseTimeStamp, 1239 info.operationSupport); 1240 } 1241 1242 ipmi::RspType<uint16_t, std::vector<uint8_t>> 1243 ipmiStorageGetSELEntry(std::vector<uint8_t> data) 1244 { 1245 1246 if (data.size() != sizeof(fb_oem::ipmi::sel::GetSELEntryRequest)) 1247 { 1248 return ipmi::responseReqDataLenInvalid(); 1249 } 1250 1251 fb_oem::ipmi::sel::GetSELEntryRequest* reqData = 1252 reinterpret_cast<fb_oem::ipmi::sel::GetSELEntryRequest*>(&data[0]); 1253 1254 if (reqData->reservID != 0) 1255 { 1256 if (!checkSELReservation(reqData->reservID)) 1257 { 1258 return ipmi::responseInvalidReservationId(); 1259 } 1260 } 1261 1262 uint16_t selCnt = selObj.getCount(); 1263 if (selCnt == 0) 1264 { 1265 return ipmi::responseSensorInvalid(); 1266 } 1267 1268 /* If it is asked for first entry */ 1269 if (reqData->recordID == fb_oem::ipmi::sel::firstEntry) 1270 { 1271 /* First Entry (0x0000) as per Spec */ 1272 reqData->recordID = 1; 1273 } 1274 else if (reqData->recordID == fb_oem::ipmi::sel::lastEntry) 1275 { 1276 /* Last entry (0xFFFF) as per Spec */ 1277 reqData->recordID = selCnt; 1278 } 1279 1280 std::string ipmiRaw; 1281 1282 if (selObj.getEntry(reqData->recordID, ipmiRaw) < 0) 1283 { 1284 return ipmi::responseSensorInvalid(); 1285 } 1286 1287 std::vector<uint8_t> recDataBytes; 1288 if (fromHexStr(ipmiRaw, recDataBytes) < 0) 1289 { 1290 return ipmi::responseUnspecifiedError(); 1291 } 1292 1293 /* Identify the next SEL record ID. If recordID is same as 1294 * total SeL count then next id should be last entry else 1295 * it should be incremented by 1 to current RecordID 1296 */ 1297 uint16_t nextRecord; 1298 if (reqData->recordID == selCnt) 1299 { 1300 nextRecord = fb_oem::ipmi::sel::lastEntry; 1301 } 1302 else 1303 { 1304 nextRecord = reqData->recordID + 1; 1305 } 1306 1307 if (reqData->readLen == fb_oem::ipmi::sel::entireRecord) 1308 { 1309 return ipmi::responseSuccess(nextRecord, recDataBytes); 1310 } 1311 else 1312 { 1313 if (reqData->offset >= fb_oem::ipmi::sel::selRecordSize || 1314 reqData->readLen > fb_oem::ipmi::sel::selRecordSize) 1315 { 1316 return ipmi::responseUnspecifiedError(); 1317 } 1318 std::vector<uint8_t> recPartData; 1319 1320 auto diff = fb_oem::ipmi::sel::selRecordSize - reqData->offset; 1321 auto readLength = std::min(diff, static_cast<int>(reqData->readLen)); 1322 1323 for (int i = 0; i < readLength; i++) 1324 { 1325 recPartData.push_back(recDataBytes[i + reqData->offset]); 1326 } 1327 return ipmi::responseSuccess(nextRecord, recPartData); 1328 } 1329 } 1330 1331 ipmi::RspType<uint16_t> ipmiStorageAddSELEntry(std::vector<uint8_t> data) 1332 { 1333 /* Per the IPMI spec, need to cancel any reservation when a 1334 * SEL entry is added 1335 */ 1336 cancelSELReservation(); 1337 1338 if (data.size() != fb_oem::ipmi::sel::selRecordSize) 1339 { 1340 return ipmi::responseReqDataLenInvalid(); 1341 } 1342 1343 std::string ipmiRaw, logErr; 1344 toHexStr(data, ipmiRaw); 1345 1346 /* Parse sel data and get an error log to be filed */ 1347 fb_oem::ipmi::sel::parseSelData(data, logErr); 1348 1349 static const std::string openBMCMessageRegistryVersion("0.1"); 1350 std::string messageID = 1351 "OpenBMC." + openBMCMessageRegistryVersion + ".SELEntryAdded"; 1352 1353 /* Log the Raw SEL message to the journal */ 1354 std::string journalMsg = "SEL Entry Added: " + ipmiRaw; 1355 1356 phosphor::logging::log<phosphor::logging::level::INFO>( 1357 journalMsg.c_str(), 1358 phosphor::logging::entry("IPMISEL_MESSAGE_ID=%s", messageID.c_str()), 1359 phosphor::logging::entry("IPMISEL_MESSAGE_ARGS=%s", logErr.c_str())); 1360 1361 int responseID = selObj.addEntry(ipmiRaw.c_str()); 1362 if (responseID < 0) 1363 { 1364 return ipmi::responseUnspecifiedError(); 1365 } 1366 return ipmi::responseSuccess((uint16_t)responseID); 1367 } 1368 1369 ipmi::RspType<uint8_t> ipmiStorageClearSEL(uint16_t reservationID, 1370 const std::array<uint8_t, 3>& clr, 1371 uint8_t eraseOperation) 1372 { 1373 if (!checkSELReservation(reservationID)) 1374 { 1375 return ipmi::responseInvalidReservationId(); 1376 } 1377 1378 static constexpr std::array<uint8_t, 3> clrExpected = {'C', 'L', 'R'}; 1379 if (clr != clrExpected) 1380 { 1381 return ipmi::responseInvalidFieldRequest(); 1382 } 1383 1384 /* If there is no sel then return erase complete */ 1385 if (selObj.getCount() == 0) 1386 { 1387 return ipmi::responseSuccess(fb_oem::ipmi::sel::eraseComplete); 1388 } 1389 1390 /* Erasure status cannot be fetched, so always return erasure 1391 * status as `erase completed`. 1392 */ 1393 if (eraseOperation == fb_oem::ipmi::sel::getEraseStatus) 1394 { 1395 return ipmi::responseSuccess(fb_oem::ipmi::sel::eraseComplete); 1396 } 1397 1398 /* Check that initiate erase is correct */ 1399 if (eraseOperation != fb_oem::ipmi::sel::initiateErase) 1400 { 1401 return ipmi::responseInvalidFieldRequest(); 1402 } 1403 1404 /* Per the IPMI spec, need to cancel any reservation when the 1405 * SEL is cleared 1406 */ 1407 cancelSELReservation(); 1408 1409 /* Clear the complete Sel Json object */ 1410 if (selObj.clear() < 0) 1411 { 1412 return ipmi::responseUnspecifiedError(); 1413 } 1414 1415 return ipmi::responseSuccess(fb_oem::ipmi::sel::eraseComplete); 1416 } 1417 1418 ipmi::RspType<uint32_t> ipmiStorageGetSELTime() 1419 { 1420 struct timespec selTime = {}; 1421 1422 if (clock_gettime(CLOCK_REALTIME, &selTime) < 0) 1423 { 1424 return ipmi::responseUnspecifiedError(); 1425 } 1426 1427 return ipmi::responseSuccess(selTime.tv_sec); 1428 } 1429 1430 ipmi::RspType<> ipmiStorageSetSELTime(uint32_t selTime) 1431 { 1432 // Set SEL Time is not supported 1433 return ipmi::responseInvalidCommand(); 1434 } 1435 1436 ipmi::RspType<uint16_t> ipmiStorageGetSELTimeUtcOffset() 1437 { 1438 /* TODO: For now, the SEL time stamp is based on UTC time, 1439 * so return 0x0000 as offset. Might need to change once 1440 * supporting zones in SEL time stamps 1441 */ 1442 1443 uint16_t utcOffset = 0x0000; 1444 return ipmi::responseSuccess(utcOffset); 1445 } 1446 1447 void registerSELFunctions() 1448 { 1449 // <Get SEL Info> 1450 ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage, 1451 ipmi::storage::cmdGetSelInfo, ipmi::Privilege::User, 1452 ipmiStorageGetSELInfo); 1453 1454 // <Get SEL Entry> 1455 ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage, 1456 ipmi::storage::cmdGetSelEntry, ipmi::Privilege::User, 1457 ipmiStorageGetSELEntry); 1458 1459 // <Add SEL Entry> 1460 ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage, 1461 ipmi::storage::cmdAddSelEntry, 1462 ipmi::Privilege::Operator, ipmiStorageAddSELEntry); 1463 1464 // <Clear SEL> 1465 ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage, 1466 ipmi::storage::cmdClearSel, ipmi::Privilege::Operator, 1467 ipmiStorageClearSEL); 1468 1469 // <Get SEL Time> 1470 ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage, 1471 ipmi::storage::cmdGetSelTime, ipmi::Privilege::User, 1472 ipmiStorageGetSELTime); 1473 1474 // <Set SEL Time> 1475 ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage, 1476 ipmi::storage::cmdSetSelTime, 1477 ipmi::Privilege::Operator, ipmiStorageSetSELTime); 1478 1479 // <Get SEL Time UTC Offset> 1480 ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage, 1481 ipmi::storage::cmdGetSelTimeUtcOffset, 1482 ipmi::Privilege::User, 1483 ipmiStorageGetSELTimeUtcOffset); 1484 1485 return; 1486 } 1487 1488 } // namespace storage 1489 } // namespace ipmi 1490