1 /** 2 * Copyright © 2019 IBM Corporation 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 #include "elog_entry.hpp" 17 #include "extensions/openpower-pels/generic.hpp" 18 #include "extensions/openpower-pels/pel.hpp" 19 #include "mocks.hpp" 20 #include "pel_utils.hpp" 21 22 #include <filesystem> 23 #include <fstream> 24 #include <optional> 25 26 #include <gtest/gtest.h> 27 28 namespace fs = std::filesystem; 29 using namespace openpower::pels; 30 using ::testing::_; 31 using ::testing::DoAll; 32 using ::testing::NiceMock; 33 using ::testing::Return; 34 using ::testing::SetArgReferee; 35 36 class PELTest : public CleanLogID 37 {}; 38 39 fs::path makeTempDir() 40 { 41 char path[] = "/tmp/tempdirXXXXXX"; 42 std::filesystem::path dir = mkdtemp(path); 43 return dir; 44 } 45 46 int writeFileAndGetFD(const fs::path& dir, const std::vector<uint8_t>& data) 47 { 48 static size_t count = 0; 49 fs::path path = dir / (std::string{"file"} + std::to_string(count)); 50 std::ofstream stream{path}; 51 count++; 52 53 stream.write(reinterpret_cast<const char*>(data.data()), data.size()); 54 stream.close(); 55 56 FILE* fp = fopen(path.c_str(), "r"); 57 return fileno(fp); 58 } 59 60 TEST_F(PELTest, FlattenTest) 61 { 62 auto data = pelDataFactory(TestPELType::pelSimple); 63 auto pel = std::make_unique<PEL>(data); 64 65 // Check a few fields 66 EXPECT_TRUE(pel->valid()); 67 EXPECT_EQ(pel->id(), 0x80818283); 68 EXPECT_EQ(pel->plid(), 0x50515253); 69 EXPECT_EQ(pel->userHeader().subsystem(), 0x10); 70 EXPECT_EQ(pel->userHeader().actionFlags(), 0x80C0); 71 72 // Test that data in == data out 73 auto flattenedData = pel->data(); 74 EXPECT_EQ(data, flattenedData); 75 EXPECT_EQ(flattenedData.size(), pel->size()); 76 } 77 78 TEST_F(PELTest, CommitTimeTest) 79 { 80 auto data = pelDataFactory(TestPELType::pelSimple); 81 auto pel = std::make_unique<PEL>(data); 82 83 auto origTime = pel->commitTime(); 84 pel->setCommitTime(); 85 auto newTime = pel->commitTime(); 86 87 EXPECT_NE(origTime, newTime); 88 89 // Make a new PEL and check new value is still there 90 auto newData = pel->data(); 91 auto newPel = std::make_unique<PEL>(newData); 92 EXPECT_EQ(newTime, newPel->commitTime()); 93 } 94 95 TEST_F(PELTest, AssignIDTest) 96 { 97 auto data = pelDataFactory(TestPELType::pelSimple); 98 auto pel = std::make_unique<PEL>(data); 99 100 auto origID = pel->id(); 101 pel->assignID(); 102 auto newID = pel->id(); 103 104 EXPECT_NE(origID, newID); 105 106 // Make a new PEL and check new value is still there 107 auto newData = pel->data(); 108 auto newPel = std::make_unique<PEL>(newData); 109 EXPECT_EQ(newID, newPel->id()); 110 } 111 112 TEST_F(PELTest, WithLogIDTest) 113 { 114 auto data = pelDataFactory(TestPELType::pelSimple); 115 auto pel = std::make_unique<PEL>(data, 0x42); 116 117 EXPECT_TRUE(pel->valid()); 118 EXPECT_EQ(pel->obmcLogID(), 0x42); 119 } 120 121 TEST_F(PELTest, InvalidPELTest) 122 { 123 auto data = pelDataFactory(TestPELType::pelSimple); 124 125 // Too small 126 data.resize(PrivateHeader::flattenedSize()); 127 128 auto pel = std::make_unique<PEL>(data); 129 130 EXPECT_TRUE(pel->privateHeader().valid()); 131 EXPECT_FALSE(pel->userHeader().valid()); 132 EXPECT_FALSE(pel->valid()); 133 134 // Now corrupt the private header 135 data = pelDataFactory(TestPELType::pelSimple); 136 data.at(0) = 0; 137 pel = std::make_unique<PEL>(data); 138 139 EXPECT_FALSE(pel->privateHeader().valid()); 140 EXPECT_TRUE(pel->userHeader().valid()); 141 EXPECT_FALSE(pel->valid()); 142 } 143 144 TEST_F(PELTest, EmptyDataTest) 145 { 146 std::vector<uint8_t> data; 147 auto pel = std::make_unique<PEL>(data); 148 149 EXPECT_FALSE(pel->privateHeader().valid()); 150 EXPECT_FALSE(pel->userHeader().valid()); 151 EXPECT_FALSE(pel->valid()); 152 } 153 154 TEST_F(PELTest, CreateFromRegistryTest) 155 { 156 message::Entry regEntry; 157 uint64_t timestamp = 5; 158 159 regEntry.name = "test"; 160 regEntry.subsystem = 5; 161 regEntry.actionFlags = 0xC000; 162 regEntry.src.type = 0xBD; 163 regEntry.src.reasonCode = 0x1234; 164 165 std::vector<std::string> data{"KEY1=VALUE1"}; 166 AdditionalData ad{data}; 167 NiceMock<MockDataInterface> dataIface; 168 NiceMock<MockJournal> journal; 169 PelFFDC ffdc; 170 171 PEL pel{regEntry, 42, timestamp, phosphor::logging::Entry::Level::Error, 172 ad, ffdc, dataIface, journal}; 173 174 EXPECT_TRUE(pel.valid()); 175 EXPECT_EQ(pel.privateHeader().obmcLogID(), 42); 176 EXPECT_EQ(pel.userHeader().severity(), 0x40); 177 178 EXPECT_EQ(pel.primarySRC().value()->asciiString(), 179 "BD051234 "); 180 181 // Check that certain optional sections have been created 182 size_t mtmsCount = 0; 183 size_t euhCount = 0; 184 size_t udCount = 0; 185 186 for (const auto& section : pel.optionalSections()) 187 { 188 if (section->header().id == 189 static_cast<uint16_t>(SectionID::failingMTMS)) 190 { 191 mtmsCount++; 192 } 193 else if (section->header().id == 194 static_cast<uint16_t>(SectionID::extendedUserHeader)) 195 { 196 euhCount++; 197 } 198 else if (section->header().id == 199 static_cast<uint16_t>(SectionID::userData)) 200 { 201 udCount++; 202 } 203 } 204 205 EXPECT_EQ(mtmsCount, 1); 206 EXPECT_EQ(euhCount, 1); 207 EXPECT_EQ(udCount, 2); // AD section and sysInfo section 208 ASSERT_FALSE(pel.isHwCalloutPresent()); 209 210 { 211 // The same thing, but without the action flags specified 212 // in the registry, so the constructor should set them. 213 regEntry.actionFlags = std::nullopt; 214 215 PEL pel2{regEntry, 42, 216 timestamp, phosphor::logging::Entry::Level::Error, 217 ad, ffdc, 218 dataIface, journal}; 219 220 EXPECT_EQ(pel2.userHeader().actionFlags(), 0xA800); 221 } 222 } 223 224 // Test that when the AdditionalData size is over 16KB that 225 // the PEL that's created is exactly 16KB since the UserData 226 // section that contains all that data was pruned. 227 TEST_F(PELTest, CreateTooBigADTest) 228 { 229 message::Entry regEntry; 230 uint64_t timestamp = 5; 231 232 regEntry.name = "test"; 233 regEntry.subsystem = 5; 234 regEntry.actionFlags = 0xC000; 235 regEntry.src.type = 0xBD; 236 regEntry.src.reasonCode = 0x1234; 237 PelFFDC ffdc; 238 239 // Over the 16KB max PEL size 240 std::string bigAD{"KEY1="}; 241 bigAD += std::string(17000, 'G'); 242 243 std::vector<std::string> data{bigAD}; 244 AdditionalData ad{data}; 245 NiceMock<MockDataInterface> dataIface; 246 NiceMock<MockJournal> journal; 247 248 PEL pel{regEntry, 42, timestamp, phosphor::logging::Entry::Level::Error, 249 ad, ffdc, dataIface, journal}; 250 251 EXPECT_TRUE(pel.valid()); 252 EXPECT_EQ(pel.size(), 16384); 253 254 // Make sure that there are still 2 UD sections. 255 const auto& optSections = pel.optionalSections(); 256 auto udCount = std::count_if( 257 optSections.begin(), optSections.end(), [](const auto& section) { 258 return section->header().id == 259 static_cast<uint16_t>(SectionID::userData); 260 }); 261 262 EXPECT_EQ(udCount, 2); // AD section and sysInfo section 263 } 264 265 // Test that we'll create Generic optional sections for sections that 266 // there aren't explicit classes for. 267 TEST_F(PELTest, GenericSectionTest) 268 { 269 auto data = pelDataFactory(TestPELType::pelSimple); 270 271 std::vector<uint8_t> section1{ 272 0x58, 0x58, // ID 'XX' 273 0x00, 0x18, // Size 274 0x01, 0x02, // version, subtype 275 0x03, 0x04, // comp ID 276 277 // some data 278 0x20, 0x30, 0x05, 0x09, 0x11, 0x1E, 0x1, 0x63, 0x20, 0x31, 0x06, 0x0F, 279 0x09, 0x22, 0x3A, 0x00}; 280 281 std::vector<uint8_t> section2{ 282 0x59, 0x59, // ID 'YY' 283 0x00, 0x20, // Size 284 0x01, 0x02, // version, subtype 285 0x03, 0x04, // comp ID 286 287 // some data 288 0x20, 0x30, 0x05, 0x09, 0x11, 0x1E, 0x1, 0x63, 0x20, 0x31, 0x06, 0x0F, 289 0x09, 0x22, 0x3A, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}; 290 291 // Add the new sections at the end 292 data.insert(data.end(), section1.begin(), section1.end()); 293 data.insert(data.end(), section2.begin(), section2.end()); 294 295 // Increment the section count 296 data.at(27) += 2; 297 auto origData = data; 298 299 PEL pel{data}; 300 301 const auto& sections = pel.optionalSections(); 302 303 bool foundXX = false; 304 bool foundYY = false; 305 306 // Check that we can find these 2 Generic sections 307 for (const auto& section : sections) 308 { 309 if (section->header().id == 0x5858) 310 { 311 foundXX = true; 312 EXPECT_NE(dynamic_cast<Generic*>(section.get()), nullptr); 313 } 314 else if (section->header().id == 0x5959) 315 { 316 foundYY = true; 317 EXPECT_NE(dynamic_cast<Generic*>(section.get()), nullptr); 318 } 319 } 320 321 EXPECT_TRUE(foundXX); 322 EXPECT_TRUE(foundYY); 323 324 // Now flatten and check 325 auto newData = pel.data(); 326 327 EXPECT_EQ(origData, newData); 328 } 329 330 // Test that an invalid section will still get a Generic object 331 TEST_F(PELTest, InvalidGenericTest) 332 { 333 auto data = pelDataFactory(TestPELType::pelSimple); 334 335 // Not a valid section 336 std::vector<uint8_t> section1{0x01, 0x02, 0x03}; 337 338 data.insert(data.end(), section1.begin(), section1.end()); 339 340 // Increment the section count 341 data.at(27) += 1; 342 343 PEL pel{data}; 344 EXPECT_FALSE(pel.valid()); 345 346 const auto& sections = pel.optionalSections(); 347 348 bool foundGeneric = false; 349 for (const auto& section : sections) 350 { 351 if (dynamic_cast<Generic*>(section.get()) != nullptr) 352 { 353 foundGeneric = true; 354 EXPECT_EQ(section->valid(), false); 355 break; 356 } 357 } 358 359 EXPECT_TRUE(foundGeneric); 360 } 361 362 // Create a UserData section out of AdditionalData 363 TEST_F(PELTest, MakeUDSectionTest) 364 { 365 std::vector<std::string> ad{"KEY1=VALUE1", "KEY2=VALUE2", "KEY3=VALUE3", 366 "ESEL=TEST"}; 367 AdditionalData additionalData{ad}; 368 369 auto ud = util::makeADUserDataSection(additionalData); 370 371 EXPECT_TRUE(ud->valid()); 372 EXPECT_EQ(ud->header().id, 0x5544); 373 EXPECT_EQ(ud->header().version, 0x01); 374 EXPECT_EQ(ud->header().subType, 0x01); 375 EXPECT_EQ(ud->header().componentID, 0x2000); 376 377 const auto& d = ud->data(); 378 379 std::string jsonString{d.begin(), d.end()}; 380 381 std::string expectedJSON = 382 R"({"KEY1":"VALUE1","KEY2":"VALUE2","KEY3":"VALUE3"})"; 383 384 // The actual data is null padded to a 4B boundary. 385 std::vector<uint8_t> expectedData; 386 expectedData.resize(52, '\0'); 387 memcpy(expectedData.data(), expectedJSON.data(), expectedJSON.size()); 388 389 EXPECT_EQ(d, expectedData); 390 391 // Ensure we can read this as JSON 392 auto newJSON = nlohmann::json::parse(jsonString); 393 EXPECT_EQ(newJSON["KEY1"], "VALUE1"); 394 EXPECT_EQ(newJSON["KEY2"], "VALUE2"); 395 EXPECT_EQ(newJSON["KEY3"], "VALUE3"); 396 } 397 398 // Create the UserData section that contains system info 399 TEST_F(PELTest, SysInfoSectionTest) 400 { 401 MockDataInterface dataIface; 402 403 EXPECT_CALL(dataIface, getBMCFWVersionID()).WillOnce(Return("ABCD1234")); 404 EXPECT_CALL(dataIface, getBMCState()).WillOnce(Return("State.Ready")); 405 EXPECT_CALL(dataIface, getChassisState()).WillOnce(Return("State.On")); 406 EXPECT_CALL(dataIface, getHostState()).WillOnce(Return("State.Off")); 407 EXPECT_CALL(dataIface, getBootState()) 408 .WillOnce(Return("State.SystemInitComplete")); 409 EXPECT_CALL(dataIface, getSystemIMKeyword()) 410 .WillOnce(Return(std::vector<uint8_t>{0, 1, 0x55, 0xAA})); 411 412 std::string pid = "_PID=" + std::to_string(getpid()); 413 std::vector<std::string> ad{pid}; 414 AdditionalData additionalData{ad}; 415 416 auto ud = util::makeSysInfoUserDataSection(additionalData, dataIface); 417 418 EXPECT_TRUE(ud->valid()); 419 EXPECT_EQ(ud->header().id, 0x5544); 420 EXPECT_EQ(ud->header().version, 0x01); 421 EXPECT_EQ(ud->header().subType, 0x01); 422 EXPECT_EQ(ud->header().componentID, 0x2000); 423 424 // Pull out the JSON data and check it. 425 const auto& d = ud->data(); 426 std::string jsonString{d.begin(), d.end()}; 427 auto json = nlohmann::json::parse(jsonString); 428 429 // Ensure the 'Process Name' entry contains the name of this test 430 // executable. 431 auto name = json["Process Name"].get<std::string>(); 432 auto found = (name.find("pel_test") != std::string::npos) || 433 (name.find("test-openpower-pels-pel") != std::string::npos); 434 EXPECT_TRUE(found); 435 // @TODO(stwcx): remove 'pel_test' when removing autotools. 436 437 auto version = json["FW Version ID"].get<std::string>(); 438 EXPECT_EQ(version, "ABCD1234"); 439 440 auto state = json["BMCState"].get<std::string>(); 441 EXPECT_EQ(state, "Ready"); 442 443 state = json["ChassisState"].get<std::string>(); 444 EXPECT_EQ(state, "On"); 445 446 state = json["HostState"].get<std::string>(); 447 EXPECT_EQ(state, "Off"); 448 449 state = json["BootState"].get<std::string>(); 450 EXPECT_EQ(state, "SystemInitComplete"); 451 452 auto keyword = json["System IM"].get<std::string>(); 453 EXPECT_EQ(keyword, "000155AA"); 454 } 455 456 // Test that the sections that override 457 // virtual std::optional<std::string> Section::getJSON() const 458 // return valid JSON. 459 TEST_F(PELTest, SectionJSONTest) 460 { 461 auto data = pelDataFactory(TestPELType::pelSimple); 462 PEL pel{data}; 463 464 // Check that all JSON returned from the sections is 465 // parseable by nlohmann::json, which will throw an 466 // exception and fail the test if there is a problem. 467 468 // The getJSON() response needs to be wrapped in a { } to make 469 // actual valid JSON (PEL::toJSON() usually handles that). 470 471 auto jsonString = pel.privateHeader().getJSON('O'); 472 473 // PrivateHeader always prints JSON 474 ASSERT_TRUE(jsonString); 475 *jsonString = '{' + *jsonString + '}'; 476 auto json = nlohmann::json::parse(*jsonString); 477 478 jsonString = pel.userHeader().getJSON('O'); 479 480 // UserHeader always prints JSON 481 ASSERT_TRUE(jsonString); 482 *jsonString = '{' + *jsonString + '}'; 483 json = nlohmann::json::parse(*jsonString); 484 485 for (const auto& section : pel.optionalSections()) 486 { 487 // The optional sections may or may not have implemented getJSON(). 488 jsonString = section->getJSON('O'); 489 if (jsonString) 490 { 491 *jsonString = '{' + *jsonString + '}'; 492 auto json = nlohmann::json::parse(*jsonString); 493 } 494 } 495 } 496 497 PelFFDCfile getJSONFFDC(const fs::path& dir) 498 { 499 PelFFDCfile ffdc; 500 ffdc.format = UserDataFormat::json; 501 ffdc.subType = 5; 502 ffdc.version = 42; 503 504 auto inputJSON = R"({ 505 "key1": "value1", 506 "key2": 42, 507 "key3" : [1, 2, 3, 4, 5], 508 "key4": {"key5": "value5"} 509 })"_json; 510 511 // Write the JSON to a file and get its descriptor. 512 auto s = inputJSON.dump(); 513 std::vector<uint8_t> data{s.begin(), s.end()}; 514 ffdc.fd = writeFileAndGetFD(dir, data); 515 516 return ffdc; 517 } 518 519 TEST_F(PELTest, MakeJSONFileUDSectionTest) 520 { 521 auto dir = makeTempDir(); 522 523 { 524 auto ffdc = getJSONFFDC(dir); 525 526 auto ud = util::makeFFDCuserDataSection(0x2002, ffdc); 527 close(ffdc.fd); 528 ASSERT_TRUE(ud); 529 ASSERT_TRUE(ud->valid()); 530 EXPECT_EQ(ud->header().id, 0x5544); 531 532 EXPECT_EQ(ud->header().version, 533 static_cast<uint8_t>(UserDataFormatVersion::json)); 534 EXPECT_EQ(ud->header().subType, 535 static_cast<uint8_t>(UserDataFormat::json)); 536 EXPECT_EQ(ud->header().componentID, 537 static_cast<uint16_t>(ComponentID::phosphorLogging)); 538 539 // Pull the JSON back out of the the UserData section 540 const auto& d = ud->data(); 541 std::string js{d.begin(), d.end()}; 542 auto json = nlohmann::json::parse(js); 543 544 EXPECT_EQ("value1", json["key1"].get<std::string>()); 545 EXPECT_EQ(42, json["key2"].get<int>()); 546 547 std::vector<int> key3Values{1, 2, 3, 4, 5}; 548 EXPECT_EQ(key3Values, json["key3"].get<std::vector<int>>()); 549 550 std::map<std::string, std::string> key4Values{{"key5", "value5"}}; 551 auto actual = json["key4"].get<std::map<std::string, std::string>>(); 552 EXPECT_EQ(key4Values, actual); 553 } 554 555 { 556 // A bad FD 557 PelFFDCfile ffdc; 558 ffdc.format = UserDataFormat::json; 559 ffdc.subType = 5; 560 ffdc.version = 42; 561 ffdc.fd = 10000; 562 563 // The section shouldn't get made 564 auto ud = util::makeFFDCuserDataSection(0x2002, ffdc); 565 ASSERT_FALSE(ud); 566 } 567 568 fs::remove_all(dir); 569 } 570 571 PelFFDCfile getCBORFFDC(const fs::path& dir) 572 { 573 PelFFDCfile ffdc; 574 ffdc.format = UserDataFormat::cbor; 575 ffdc.subType = 5; 576 ffdc.version = 42; 577 578 auto inputJSON = R"({ 579 "key1": "value1", 580 "key2": 42, 581 "key3" : [1, 2, 3, 4, 5], 582 "key4": {"key5": "value5"} 583 })"_json; 584 585 // Convert the JSON to CBOR and write it to a file 586 auto data = nlohmann::json::to_cbor(inputJSON); 587 ffdc.fd = writeFileAndGetFD(dir, data); 588 589 return ffdc; 590 } 591 592 TEST_F(PELTest, MakeCBORFileUDSectionTest) 593 { 594 auto dir = makeTempDir(); 595 596 auto ffdc = getCBORFFDC(dir); 597 auto ud = util::makeFFDCuserDataSection(0x2002, ffdc); 598 close(ffdc.fd); 599 ASSERT_TRUE(ud); 600 ASSERT_TRUE(ud->valid()); 601 EXPECT_EQ(ud->header().id, 0x5544); 602 603 EXPECT_EQ(ud->header().version, 604 static_cast<uint8_t>(UserDataFormatVersion::cbor)); 605 EXPECT_EQ(ud->header().subType, static_cast<uint8_t>(UserDataFormat::cbor)); 606 EXPECT_EQ(ud->header().componentID, 607 static_cast<uint16_t>(ComponentID::phosphorLogging)); 608 609 // Pull the CBOR back out of the PEL section 610 // The number of pad bytes to make the section be 4B aligned 611 // was added at the end, read it and then remove it and the 612 // padding before parsing it. 613 auto data = ud->data(); 614 Stream stream{data}; 615 stream.offset(data.size() - 4); 616 uint32_t pad; 617 stream >> pad; 618 619 data.resize(data.size() - 4 - pad); 620 621 auto json = nlohmann::json::from_cbor(data); 622 623 EXPECT_EQ("value1", json["key1"].get<std::string>()); 624 EXPECT_EQ(42, json["key2"].get<int>()); 625 626 std::vector<int> key3Values{1, 2, 3, 4, 5}; 627 EXPECT_EQ(key3Values, json["key3"].get<std::vector<int>>()); 628 629 std::map<std::string, std::string> key4Values{{"key5", "value5"}}; 630 auto actual = json["key4"].get<std::map<std::string, std::string>>(); 631 EXPECT_EQ(key4Values, actual); 632 633 fs::remove_all(dir); 634 } 635 636 PelFFDCfile getTextFFDC(const fs::path& dir) 637 { 638 PelFFDCfile ffdc; 639 ffdc.format = UserDataFormat::text; 640 ffdc.subType = 5; 641 ffdc.version = 42; 642 643 std::string text{"this is some text that will be used for FFDC"}; 644 std::vector<uint8_t> data{text.begin(), text.end()}; 645 646 ffdc.fd = writeFileAndGetFD(dir, data); 647 648 return ffdc; 649 } 650 651 TEST_F(PELTest, MakeTextFileUDSectionTest) 652 { 653 auto dir = makeTempDir(); 654 655 auto ffdc = getTextFFDC(dir); 656 auto ud = util::makeFFDCuserDataSection(0x2002, ffdc); 657 close(ffdc.fd); 658 ASSERT_TRUE(ud); 659 ASSERT_TRUE(ud->valid()); 660 EXPECT_EQ(ud->header().id, 0x5544); 661 662 EXPECT_EQ(ud->header().version, 663 static_cast<uint8_t>(UserDataFormatVersion::text)); 664 EXPECT_EQ(ud->header().subType, static_cast<uint8_t>(UserDataFormat::text)); 665 EXPECT_EQ(ud->header().componentID, 666 static_cast<uint16_t>(ComponentID::phosphorLogging)); 667 668 // Get the text back out 669 std::string text{ud->data().begin(), ud->data().end()}; 670 EXPECT_EQ(text, "this is some text that will be used for FFDC"); 671 672 fs::remove_all(dir); 673 } 674 675 PelFFDCfile getCustomFFDC(const fs::path& dir, const std::vector<uint8_t>& data) 676 { 677 PelFFDCfile ffdc; 678 ffdc.format = UserDataFormat::custom; 679 ffdc.subType = 5; 680 ffdc.version = 42; 681 682 ffdc.fd = writeFileAndGetFD(dir, data); 683 684 return ffdc; 685 } 686 687 TEST_F(PELTest, MakeCustomFileUDSectionTest) 688 { 689 auto dir = makeTempDir(); 690 691 { 692 std::vector<uint8_t> data{1, 2, 3, 4, 5, 6, 7, 8}; 693 694 auto ffdc = getCustomFFDC(dir, data); 695 auto ud = util::makeFFDCuserDataSection(0x2002, ffdc); 696 close(ffdc.fd); 697 ASSERT_TRUE(ud); 698 ASSERT_TRUE(ud->valid()); 699 EXPECT_EQ(ud->header().size, 8 + 8); // data size + header size 700 EXPECT_EQ(ud->header().id, 0x5544); 701 702 EXPECT_EQ(ud->header().version, 42); 703 EXPECT_EQ(ud->header().subType, 5); 704 EXPECT_EQ(ud->header().componentID, 0x2002); 705 706 // Get the data back out 707 std::vector<uint8_t> newData{ud->data().begin(), ud->data().end()}; 708 EXPECT_EQ(data, newData); 709 } 710 711 // Do the same thing again, but make it be non 4B aligned 712 // so the data gets padded. 713 { 714 std::vector<uint8_t> data{1, 2, 3, 4, 5, 6, 7, 8, 9}; 715 716 auto ffdc = getCustomFFDC(dir, data); 717 auto ud = util::makeFFDCuserDataSection(0x2002, ffdc); 718 close(ffdc.fd); 719 ASSERT_TRUE(ud); 720 ASSERT_TRUE(ud->valid()); 721 EXPECT_EQ(ud->header().size, 12 + 8); // data size + header size 722 EXPECT_EQ(ud->header().id, 0x5544); 723 724 EXPECT_EQ(ud->header().version, 42); 725 EXPECT_EQ(ud->header().subType, 5); 726 EXPECT_EQ(ud->header().componentID, 0x2002); 727 728 // Get the data back out 729 std::vector<uint8_t> newData{ud->data().begin(), ud->data().end()}; 730 731 // pad the original to 12B so we can compare 732 data.push_back(0); 733 data.push_back(0); 734 data.push_back(0); 735 736 EXPECT_EQ(data, newData); 737 } 738 739 fs::remove_all(dir); 740 } 741 742 // Test Adding FFDC from files to a PEL 743 TEST_F(PELTest, CreateWithFFDCTest) 744 { 745 auto dir = makeTempDir(); 746 message::Entry regEntry; 747 uint64_t timestamp = 5; 748 749 regEntry.name = "test"; 750 regEntry.subsystem = 5; 751 regEntry.actionFlags = 0xC000; 752 regEntry.src.type = 0xBD; 753 regEntry.src.reasonCode = 0x1234; 754 755 std::vector<std::string> additionalData{"KEY1=VALUE1"}; 756 AdditionalData ad{additionalData}; 757 NiceMock<MockDataInterface> dataIface; 758 NiceMock<MockJournal> journal; 759 PelFFDC ffdc; 760 761 std::vector<uint8_t> customData{1, 2, 3, 4, 5, 6, 7, 8}; 762 763 // This will be trimmed when added 764 std::vector<uint8_t> hugeCustomData(17000, 0x42); 765 766 ffdc.emplace_back(std::move(getJSONFFDC(dir))); 767 ffdc.emplace_back(std::move(getCBORFFDC(dir))); 768 ffdc.emplace_back(std::move(getTextFFDC(dir))); 769 ffdc.emplace_back(std::move(getCustomFFDC(dir, customData))); 770 ffdc.emplace_back(std::move(getCustomFFDC(dir, hugeCustomData))); 771 772 PEL pel{regEntry, 42, timestamp, phosphor::logging::Entry::Level::Error, 773 ad, ffdc, dataIface, journal}; 774 775 EXPECT_TRUE(pel.valid()); 776 777 // Clipped to the max 778 EXPECT_EQ(pel.size(), 16384); 779 780 // Check for the FFDC sections 781 size_t udCount = 0; 782 Section* ud = nullptr; 783 784 for (const auto& section : pel.optionalSections()) 785 { 786 if (section->header().id == static_cast<uint16_t>(SectionID::userData)) 787 { 788 udCount++; 789 ud = section.get(); 790 } 791 } 792 793 EXPECT_EQ(udCount, 7); // AD section, sysInfo, 5 ffdc sections 794 795 // Check the last section was trimmed to 796 // something a bit less that 17000. 797 EXPECT_GT(ud->header().size, 14000); 798 EXPECT_LT(ud->header().size, 16000); 799 800 fs::remove_all(dir); 801 } 802 803 // Create a PEL with device callouts 804 TEST_F(PELTest, CreateWithDevCalloutsTest) 805 { 806 message::Entry regEntry; 807 uint64_t timestamp = 5; 808 809 regEntry.name = "test"; 810 regEntry.subsystem = 5; 811 regEntry.actionFlags = 0xC000; 812 regEntry.src.type = 0xBD; 813 regEntry.src.reasonCode = 0x1234; 814 815 NiceMock<MockDataInterface> dataIface; 816 NiceMock<MockJournal> journal; 817 PelFFDC ffdc; 818 819 const auto calloutJSON = R"( 820 { 821 "I2C": 822 { 823 "14": 824 { 825 "114": 826 { 827 "Callouts":[ 828 { 829 "Name": "/chassis/motherboard/cpu0", 830 "LocationCode": "P1", 831 "Priority": "H" 832 } 833 ], 834 "Dest": "proc 0 target" 835 } 836 } 837 } 838 })"; 839 840 std::vector<std::string> names{"systemA"}; 841 EXPECT_CALL(dataIface, getSystemNames) 842 .Times(2) 843 .WillRepeatedly(Return(names)); 844 845 EXPECT_CALL(dataIface, expandLocationCode("P1", 0)) 846 .Times(1) 847 .WillOnce(Return("UXXX-P1")); 848 849 EXPECT_CALL(dataIface, getInventoryFromLocCode("P1", 0, false)) 850 .WillOnce(Return(std::vector<std::string>{ 851 "/xyz/openbmc_project/inventory/chassis/motherboard/cpu0"})); 852 853 EXPECT_CALL(dataIface, 854 getHWCalloutFields( 855 "/xyz/openbmc_project/inventory/chassis/motherboard/cpu0", 856 _, _, _)) 857 .WillOnce(DoAll(SetArgReferee<1>("1234567"), SetArgReferee<2>("CCCC"), 858 SetArgReferee<3>("123456789ABC"))); 859 860 auto dataPath = getPELReadOnlyDataPath(); 861 std::ofstream file{dataPath / "systemA_dev_callouts.json"}; 862 file << calloutJSON; 863 file.close(); 864 865 { 866 std::vector<std::string> data{ 867 "CALLOUT_ERRNO=5", 868 "CALLOUT_DEVICE_PATH=/sys/devices/platform/ahb/ahb:apb/" 869 "ahb:apb:bus@1e78a000/1e78a340.i2c-bus/i2c-14/14-0072"}; 870 871 AdditionalData ad{data}; 872 873 PEL pel{regEntry, 42, 874 timestamp, phosphor::logging::Entry::Level::Error, 875 ad, ffdc, 876 dataIface, journal}; 877 878 ASSERT_TRUE(pel.primarySRC().value()->callouts()); 879 auto& callouts = pel.primarySRC().value()->callouts()->callouts(); 880 ASSERT_EQ(callouts.size(), 1); 881 ASSERT_TRUE(pel.isHwCalloutPresent()); 882 883 EXPECT_EQ(callouts[0]->priority(), 'H'); 884 EXPECT_EQ(callouts[0]->locationCode(), "UXXX-P1"); 885 886 auto& fru = callouts[0]->fruIdentity(); 887 EXPECT_EQ(fru->getPN().value(), "1234567"); 888 EXPECT_EQ(fru->getCCIN().value(), "CCCC"); 889 EXPECT_EQ(fru->getSN().value(), "123456789ABC"); 890 891 const auto& section = pel.optionalSections().back(); 892 893 ASSERT_EQ(section->header().id, 0x5544); // UD 894 auto ud = static_cast<UserData*>(section.get()); 895 896 // Check that there was a UserData section added that 897 // contains debug details about the device. 898 const auto& d = ud->data(); 899 std::string jsonString{d.begin(), d.end()}; 900 auto actualJSON = nlohmann::json::parse(jsonString); 901 902 auto expectedJSON = R"( 903 { 904 "PEL Internal Debug Data": { 905 "SRC": [ 906 "I2C: bus: 14 address: 114 dest: proc 0 target" 907 ] 908 } 909 } 910 )"_json; 911 912 EXPECT_TRUE( 913 actualJSON.contains("/PEL Internal Debug Data/SRC"_json_pointer)); 914 EXPECT_EQ(actualJSON["PEL Internal Debug Data"]["SRC"], 915 expectedJSON["PEL Internal Debug Data"]["SRC"]); 916 } 917 918 { 919 // Device path not found (wrong i2c addr), so no callouts 920 std::vector<std::string> data{ 921 "CALLOUT_ERRNO=5", 922 "CALLOUT_DEVICE_PATH=/sys/devices/platform/ahb/ahb:apb/" 923 "ahb:apb:bus@1e78a000/1e78a340.i2c-bus/i2c-14/14-0099"}; 924 925 AdditionalData ad{data}; 926 927 PEL pel{regEntry, 42, 928 timestamp, phosphor::logging::Entry::Level::Error, 929 ad, ffdc, 930 dataIface, journal}; 931 932 // no callouts 933 EXPECT_FALSE(pel.primarySRC().value()->callouts()); 934 935 // Now check that there was a UserData section 936 // that contains the lookup error. 937 const auto& section = pel.optionalSections().back(); 938 939 ASSERT_EQ(section->header().id, 0x5544); // UD 940 auto ud = static_cast<UserData*>(section.get()); 941 942 const auto& d = ud->data(); 943 944 std::string jsonString{d.begin(), d.end()}; 945 946 auto actualJSON = nlohmann::json::parse(jsonString); 947 948 auto expectedJSON = 949 "{\"PEL Internal Debug Data\":{\"SRC\":" 950 "[\"Problem looking up I2C callouts on 14 153: " 951 "[json.exception.out_of_range.403] key '153' not found\"]}}"_json; 952 953 EXPECT_TRUE( 954 actualJSON.contains("/PEL Internal Debug Data/SRC"_json_pointer)); 955 EXPECT_EQ(actualJSON["PEL Internal Debug Data"]["SRC"], 956 expectedJSON["PEL Internal Debug Data"]["SRC"]); 957 } 958 959 fs::remove_all(dataPath); 960 } 961 962 // Test PELs when the callouts are passed in using a JSON file. 963 TEST_F(PELTest, CreateWithJSONCalloutsTest) 964 { 965 PelFFDCfile ffdcFile; 966 ffdcFile.format = UserDataFormat::json; 967 ffdcFile.subType = 0xCA; // Callout JSON 968 ffdcFile.version = 1; 969 970 // Write these callouts to a JSON file and pass it into 971 // the PEL as an FFDC file. Also has a duplicate that 972 // will be removed. 973 auto inputJSON = R"([ 974 { 975 "Priority": "H", 976 "LocationCode": "P0-C1" 977 }, 978 { 979 "Priority": "M", 980 "Procedure": "PROCEDURE" 981 }, 982 { 983 "Priority": "L", 984 "Procedure": "PROCEDURE" 985 } 986 ])"_json; 987 988 auto s = inputJSON.dump(); 989 std::vector<uint8_t> data{s.begin(), s.end()}; 990 auto dir = makeTempDir(); 991 ffdcFile.fd = writeFileAndGetFD(dir, data); 992 993 PelFFDC ffdc; 994 ffdc.push_back(std::move(ffdcFile)); 995 996 AdditionalData ad; 997 NiceMock<MockDataInterface> dataIface; 998 NiceMock<MockJournal> journal; 999 1000 EXPECT_CALL(dataIface, expandLocationCode("P0-C1", 0)) 1001 .Times(1) 1002 .WillOnce(Return("UXXX-P0-C1")); 1003 EXPECT_CALL(dataIface, getInventoryFromLocCode("P0-C1", 0, false)) 1004 .Times(1) 1005 .WillOnce(Return( 1006 std::vector<std::string>{"/inv/system/chassis/motherboard/bmc"})); 1007 EXPECT_CALL(dataIface, getHWCalloutFields( 1008 "/inv/system/chassis/motherboard/bmc", _, _, _)) 1009 .Times(1) 1010 .WillOnce(DoAll(SetArgReferee<1>("1234567"), SetArgReferee<2>("CCCC"), 1011 SetArgReferee<3>("123456789ABC"))); 1012 1013 message::Entry regEntry; 1014 regEntry.name = "test"; 1015 regEntry.subsystem = 5; 1016 regEntry.actionFlags = 0xC000; 1017 regEntry.src.type = 0xBD; 1018 regEntry.src.reasonCode = 0x1234; 1019 1020 PEL pel{regEntry, 42, 5, phosphor::logging::Entry::Level::Error, 1021 ad, ffdc, dataIface, journal}; 1022 1023 ASSERT_TRUE(pel.valid()); 1024 ASSERT_TRUE(pel.primarySRC().value()->callouts()); 1025 const auto& callouts = pel.primarySRC().value()->callouts()->callouts(); 1026 ASSERT_EQ(callouts.size(), 2); 1027 ASSERT_TRUE(pel.isHwCalloutPresent()); 1028 1029 { 1030 EXPECT_EQ(callouts[0]->priority(), 'H'); 1031 EXPECT_EQ(callouts[0]->locationCode(), "UXXX-P0-C1"); 1032 1033 auto& fru = callouts[0]->fruIdentity(); 1034 EXPECT_EQ(fru->getPN().value(), "1234567"); 1035 EXPECT_EQ(fru->getCCIN().value(), "CCCC"); 1036 EXPECT_EQ(fru->getSN().value(), "123456789ABC"); 1037 EXPECT_EQ(fru->failingComponentType(), src::FRUIdentity::hardwareFRU); 1038 } 1039 { 1040 EXPECT_EQ(callouts[1]->priority(), 'M'); 1041 EXPECT_EQ(callouts[1]->locationCode(), ""); 1042 1043 auto& fru = callouts[1]->fruIdentity(); 1044 EXPECT_EQ(fru->getMaintProc().value(), "PROCEDU"); 1045 EXPECT_EQ(fru->failingComponentType(), 1046 src::FRUIdentity::maintenanceProc); 1047 } 1048 fs::remove_all(dir); 1049 } 1050 1051 // Test PELs with symblic FRU callout. 1052 TEST_F(PELTest, CreateWithJSONSymblicCalloutTest) 1053 { 1054 PelFFDCfile ffdcFile; 1055 ffdcFile.format = UserDataFormat::json; 1056 ffdcFile.subType = 0xCA; // Callout JSON 1057 ffdcFile.version = 1; 1058 1059 // Write these callouts to a JSON file and pass it into 1060 // the PEL as an FFDC file. 1061 auto inputJSON = R"([ 1062 { 1063 "Priority": "M", 1064 "Procedure": "SVCDOCS" 1065 } 1066 ])"_json; 1067 1068 auto s = inputJSON.dump(); 1069 std::vector<uint8_t> data{s.begin(), s.end()}; 1070 auto dir = makeTempDir(); 1071 ffdcFile.fd = writeFileAndGetFD(dir, data); 1072 1073 PelFFDC ffdc; 1074 ffdc.push_back(std::move(ffdcFile)); 1075 1076 AdditionalData ad; 1077 NiceMock<MockDataInterface> dataIface; 1078 NiceMock<MockJournal> journal; 1079 1080 message::Entry regEntry; 1081 regEntry.name = "test"; 1082 regEntry.subsystem = 5; 1083 regEntry.actionFlags = 0xC000; 1084 regEntry.src.type = 0xBD; 1085 regEntry.src.reasonCode = 0x1234; 1086 1087 PEL pel{regEntry, 42, 5, phosphor::logging::Entry::Level::Error, 1088 ad, ffdc, dataIface, journal}; 1089 1090 ASSERT_TRUE(pel.valid()); 1091 ASSERT_TRUE(pel.primarySRC().value()->callouts()); 1092 const auto& callouts = pel.primarySRC().value()->callouts()->callouts(); 1093 ASSERT_EQ(callouts.size(), 1); 1094 ASSERT_FALSE(pel.isHwCalloutPresent()); 1095 1096 { 1097 EXPECT_EQ(callouts[0]->priority(), 'M'); 1098 EXPECT_EQ(callouts[0]->locationCode(), ""); 1099 1100 auto& fru = callouts[0]->fruIdentity(); 1101 EXPECT_EQ(fru->getMaintProc().value(), "SVCDOCS"); 1102 } 1103 fs::remove_all(dir); 1104 } 1105 1106 TEST_F(PELTest, FlattenLinesTest) 1107 { 1108 std::vector<std::string> msgs{"test1 test2", "test3 test4", "test5 test6"}; 1109 1110 auto buffer = util::flattenLines(msgs); 1111 1112 std::string string{"test1 test2\ntest3 test4\ntest5 test6\n"}; 1113 std::vector<uint8_t> expected(string.begin(), string.end()); 1114 1115 EXPECT_EQ(buffer, expected); 1116 } 1117 1118 void checkJournalSection(const std::unique_ptr<Section>& section, 1119 const std::string& expected) 1120 { 1121 ASSERT_EQ(SectionID::userData, 1122 static_cast<SectionID>(section->header().id)); 1123 ASSERT_EQ(UserDataFormat::text, 1124 static_cast<UserDataFormat>(section->header().subType)); 1125 ASSERT_EQ(section->header().version, 1126 static_cast<uint8_t>(UserDataFormatVersion::text)); 1127 1128 auto ud = static_cast<UserData*>(section.get()); 1129 1130 std::vector<uint8_t> expectedData(expected.begin(), expected.end()); 1131 1132 // PEL sections are 4B aligned so add padding before the compare 1133 while (expectedData.size() % 4 != 0) 1134 { 1135 expectedData.push_back('\0'); 1136 } 1137 1138 EXPECT_EQ(ud->data(), expectedData); 1139 } 1140 1141 TEST_F(PELTest, CaptureJournalTest) 1142 { 1143 message::Entry regEntry; 1144 uint64_t timestamp = 5; 1145 1146 regEntry.name = "test"; 1147 regEntry.subsystem = 5; 1148 regEntry.actionFlags = 0xC000; 1149 regEntry.src.type = 0xBD; 1150 regEntry.src.reasonCode = 0x1234; 1151 1152 std::vector<std::string> data; 1153 AdditionalData ad{data}; 1154 NiceMock<MockDataInterface> dataIface; 1155 NiceMock<MockJournal> journal; 1156 PelFFDC ffdc; 1157 1158 size_t pelSectsWithOneUD{0}; 1159 1160 { 1161 // Capture 5 lines from the journal into a single UD section 1162 message::JournalCapture jc = size_t{5}; 1163 regEntry.journalCapture = jc; 1164 1165 std::vector<std::string> msgs{"test1 test2", "test3 test4", 1166 "test5 test6", "4", "5"}; 1167 1168 EXPECT_CALL(journal, getMessages("", 5)).WillOnce(Return(msgs)); 1169 1170 PEL pel{regEntry, 42, 1171 timestamp, phosphor::logging::Entry::Level::Error, 1172 ad, ffdc, 1173 dataIface, journal}; 1174 1175 // Check the generated UserData section 1176 std::string expected{"test1 test2\ntest3 test4\ntest5 test6\n4\n5\n"}; 1177 1178 checkJournalSection(pel.optionalSections().back(), expected); 1179 1180 // Save for upcoming testcases 1181 pelSectsWithOneUD = pel.privateHeader().sectionCount(); 1182 } 1183 1184 { 1185 // Attempt to capture too many journal entries so the 1186 // section gets dropped. 1187 message::JournalCapture jc = size_t{1}; 1188 regEntry.journalCapture = jc; 1189 1190 EXPECT_CALL(journal, sync()).Times(1); 1191 1192 // A 20000 byte line won't fit in a PEL 1193 EXPECT_CALL(journal, getMessages("", 1)) 1194 .WillOnce( 1195 Return(std::vector<std::string>{std::string(20000, 'x')})); 1196 1197 PEL pel{regEntry, 42, 1198 timestamp, phosphor::logging::Entry::Level::Error, 1199 ad, ffdc, 1200 dataIface, journal}; 1201 1202 // Check for 1 fewer sections than in the previous PEL 1203 EXPECT_EQ(pel.privateHeader().sectionCount(), pelSectsWithOneUD - 1); 1204 } 1205 1206 // Capture 3 different journal sections 1207 { 1208 message::AppCaptureList captureList{ 1209 message::AppCapture{"app1", 3}, 1210 message::AppCapture{"app2", 4}, 1211 message::AppCapture{"app3", 1}, 1212 }; 1213 message::JournalCapture jc = captureList; 1214 regEntry.journalCapture = jc; 1215 1216 std::vector<std::string> app1{"A B", "C D", "E F"}; 1217 std::vector<std::string> app2{"1 2", "3 4", "5 6", "7 8"}; 1218 std::vector<std::string> app3{"a b c"}; 1219 1220 std::string expected1{"A B\nC D\nE F\n"}; 1221 std::string expected2{"1 2\n3 4\n5 6\n7 8\n"}; 1222 std::string expected3{"a b c\n"}; 1223 1224 EXPECT_CALL(journal, sync()).Times(1); 1225 EXPECT_CALL(journal, getMessages("app1", 3)).WillOnce(Return(app1)); 1226 EXPECT_CALL(journal, getMessages("app2", 4)).WillOnce(Return(app2)); 1227 EXPECT_CALL(journal, getMessages("app3", 1)).WillOnce(Return(app3)); 1228 1229 PEL pel{regEntry, 42, 1230 timestamp, phosphor::logging::Entry::Level::Error, 1231 ad, ffdc, 1232 dataIface, journal}; 1233 1234 // Two more sections than the 1 extra UD section in the first testcase 1235 ASSERT_EQ(pel.privateHeader().sectionCount(), pelSectsWithOneUD + 2); 1236 1237 const auto& optionalSections = pel.optionalSections(); 1238 auto numOptSections = optionalSections.size(); 1239 1240 checkJournalSection(optionalSections[numOptSections - 3], expected1); 1241 checkJournalSection(optionalSections[numOptSections - 2], expected2); 1242 checkJournalSection(optionalSections[numOptSections - 1], expected3); 1243 } 1244 1245 { 1246 // One section gets saved, and one is too big and gets dropped 1247 message::AppCaptureList captureList{ 1248 message::AppCapture{"app4", 2}, 1249 message::AppCapture{"app5", 1}, 1250 }; 1251 message::JournalCapture jc = captureList; 1252 regEntry.journalCapture = jc; 1253 1254 std::vector<std::string> app4{"w x", "y z"}; 1255 std::string expected4{"w x\ny z\n"}; 1256 1257 EXPECT_CALL(journal, sync()).Times(1); 1258 1259 EXPECT_CALL(journal, getMessages("app4", 2)).WillOnce(Return(app4)); 1260 1261 // A 20000 byte line won't fit in a PEL 1262 EXPECT_CALL(journal, getMessages("app5", 1)) 1263 .WillOnce( 1264 Return(std::vector<std::string>{std::string(20000, 'x')})); 1265 1266 PEL pel{regEntry, 42, 1267 timestamp, phosphor::logging::Entry::Level::Error, 1268 ad, ffdc, 1269 dataIface, journal}; 1270 1271 // The last section should have been dropped, so same as first TC 1272 ASSERT_EQ(pel.privateHeader().sectionCount(), pelSectsWithOneUD); 1273 1274 checkJournalSection(pel.optionalSections().back(), expected4); 1275 } 1276 } 1277 1278 // API to collect and parse the User Data section of the PEL. 1279 nlohmann::json getDIMMInfo(const auto& pel) 1280 { 1281 nlohmann::json dimmInfo{}; 1282 auto hasDIMMInfo = [&dimmInfo](const auto& optionalSection) { 1283 if (optionalSection->header().id != 1284 static_cast<uint16_t>(SectionID::userData)) 1285 { 1286 return false; 1287 } 1288 else 1289 { 1290 auto userData = static_cast<UserData*>(optionalSection.get()); 1291 1292 // convert the userdata section to string and then parse in to json 1293 // format 1294 std::string userDataString{userData->data().begin(), 1295 userData->data().end()}; 1296 nlohmann::json userDataJson = nlohmann::json::parse(userDataString); 1297 1298 if (userDataJson.contains("DIMMs Additional Info")) 1299 { 1300 dimmInfo = userDataJson.at("DIMMs Additional Info"); 1301 } 1302 else if ( 1303 userDataJson.contains( 1304 "/PEL Internal Debug Data/DIMMs Info Fetch Error"_json_pointer)) 1305 { 1306 dimmInfo = userDataJson.at( 1307 "/PEL Internal Debug Data/DIMMs Info Fetch Error"_json_pointer); 1308 } 1309 else 1310 { 1311 return false; 1312 } 1313 return true; 1314 } 1315 }; 1316 std::ranges::any_of(pel.optionalSections(), hasDIMMInfo); 1317 1318 return dimmInfo; 1319 } 1320 1321 // Test whether the DIMM callouts manufacturing info is getting added to the 1322 // SysInfo User Data section of the PEL 1323 TEST_F(PELTest, TestDimmsCalloutInfo) 1324 { 1325 { 1326 message::Entry entry; 1327 uint64_t timestamp = 5; 1328 AdditionalData ad; 1329 NiceMock<MockDataInterface> dataIface; 1330 NiceMock<MockJournal> journal; 1331 PelFFDC ffdc; 1332 1333 // When callouts contain DIMM callouts. 1334 entry.callouts = R"( 1335 [ 1336 { 1337 "CalloutList": [ 1338 { 1339 "Priority": "high", 1340 "LocCode": "P0-DIMM0" 1341 }, 1342 { 1343 "Priority": "low", 1344 "LocCode": "P0-DIMM1" 1345 } 1346 ] 1347 } 1348 ] 1349 )"_json; 1350 1351 EXPECT_CALL(dataIface, expandLocationCode("P0-DIMM0", 0)) 1352 .WillOnce(Return("U98D-P0-DIMM0")); 1353 EXPECT_CALL(dataIface, expandLocationCode("P0-DIMM1", 0)) 1354 .WillOnce(Return("U98D-P0-DIMM1")); 1355 1356 EXPECT_CALL(dataIface, getInventoryFromLocCode("P0-DIMM0", 0, false)) 1357 .WillOnce(Return(std::vector<std::string>{ 1358 "/xyz/openbmc_project/inventory/system/chassis/motherboard/dimm0"})); 1359 EXPECT_CALL(dataIface, getInventoryFromLocCode("P0-DIMM1", 0, false)) 1360 .WillOnce(Return(std::vector<std::string>{ 1361 "/xyz/openbmc_project/inventory/system/chassis/motherboard/dimm1"})); 1362 1363 std::vector<uint8_t> diValue{128, 74}; 1364 EXPECT_CALL(dataIface, getDIProperty("U98D-P0-DIMM0")) 1365 .WillOnce(Return(diValue)); 1366 EXPECT_CALL(dataIface, getDIProperty("U98D-P0-DIMM1")) 1367 .WillOnce(Return(diValue)); 1368 1369 // Add some location code in expanded format to DIMM cache memory 1370 dataIface.addDIMMLocCode("U98D-P0-DIMM0", true); 1371 dataIface.addDIMMLocCode("U98D-P0-DIMM1", true); 1372 1373 PEL pel{entry, 42, timestamp, phosphor::logging::Entry::Level::Error, 1374 ad, ffdc, dataIface, journal}; 1375 nlohmann::json dimmInfoJson = getDIMMInfo(pel); 1376 1377 nlohmann::json expected_data = R"( 1378 [ 1379 { 1380 "Location Code": "U98D-P0-DIMM0", 1381 "DRAM Manufacturer ID": [ 1382 "0x80", 1383 "0x4a" 1384 ] 1385 }, 1386 { 1387 "Location Code": "U98D-P0-DIMM1", 1388 "DRAM Manufacturer ID": [ 1389 "0x80", 1390 "0x4a" 1391 ] 1392 } 1393 ] 1394 )"_json; 1395 EXPECT_EQ(expected_data, dimmInfoJson); 1396 } 1397 } 1398 1399 // When PEL has FRU callouts but PHAL is not enabled. 1400 TEST_F(PELTest, TestNoDimmsCallout) 1401 { 1402 message::Entry entry; 1403 uint64_t timestamp = 5; 1404 AdditionalData ad; 1405 NiceMock<MockDataInterface> dataIface; 1406 NiceMock<MockJournal> journal; 1407 PelFFDC ffdc; 1408 1409 entry.callouts = R"( 1410 [ 1411 { 1412 "CalloutList": [ 1413 { 1414 "Priority": "high", 1415 "LocCode": "P0-PROC0" 1416 } 1417 ] 1418 } 1419 ] 1420 )"_json; 1421 1422 EXPECT_CALL(dataIface, expandLocationCode("P0-PROC0", 0)) 1423 .WillOnce(Return("U98D-P0-PROC0")); 1424 1425 EXPECT_CALL(dataIface, getInventoryFromLocCode("P0-PROC0", 0, false)) 1426 .WillOnce(Return(std::vector<std::string>{ 1427 "/xyz/openbmc_project/inventory/system/chassis/motherboard/dcm0/cpu0"})); 1428 1429 // Add some location code in expanded format to DIMM cache memory 1430 dataIface.addDIMMLocCode("U98D-P0-PROC0", false); 1431 1432 PEL pel{entry, 42, timestamp, phosphor::logging::Entry::Level::Error, 1433 ad, ffdc, dataIface, journal}; 1434 1435 nlohmann::json dimmInfoJson = getDIMMInfo(pel); 1436 1437 nlohmann::json expected_data{}; 1438 1439 EXPECT_EQ(expected_data, dimmInfoJson); 1440 } 1441 1442 // When the PEL doesn't contain any type of callouts 1443 TEST_F(PELTest, TestDimmsCalloutInfoWithNoCallouts) 1444 { 1445 message::Entry entry; 1446 uint64_t timestamp = 5; 1447 AdditionalData ad; 1448 NiceMock<MockDataInterface> dataIface; 1449 NiceMock<MockJournal> journal; 1450 PelFFDC ffdc; 1451 1452 PEL pel{entry, 42, timestamp, phosphor::logging::Entry::Level::Error, 1453 ad, ffdc, dataIface, journal}; 1454 1455 nlohmann::json dimmInfoJson = getDIMMInfo(pel); 1456 1457 nlohmann::json expected_data{}; 1458 1459 EXPECT_EQ(expected_data, dimmInfoJson); 1460 } 1461 1462 // When the PEL has DIMM callouts, but failed to fetch DI property value 1463 TEST_F(PELTest, TestDimmsCalloutInfoDIFailure) 1464 { 1465 { 1466 message::Entry entry; 1467 uint64_t timestamp = 5; 1468 AdditionalData ad; 1469 NiceMock<MockDataInterface> dataIface; 1470 NiceMock<MockJournal> journal; 1471 PelFFDC ffdc; 1472 1473 entry.callouts = R"( 1474 [ 1475 { 1476 "CalloutList": [ 1477 { 1478 "Priority": "high", 1479 "LocCode": "P0-DIMM0" 1480 } 1481 ] 1482 } 1483 ] 1484 )"_json; 1485 1486 EXPECT_CALL(dataIface, expandLocationCode("P0-DIMM0", 0)) 1487 .WillOnce(Return("U98D-P0-DIMM0")); 1488 1489 EXPECT_CALL(dataIface, getInventoryFromLocCode("P0-DIMM0", 0, false)) 1490 .WillOnce(Return(std::vector<std::string>{ 1491 "/xyz/openbmc_project/inventory/system/chassis/motherboard/dimm0"})); 1492 1493 EXPECT_CALL(dataIface, getDIProperty("U98D-P0-DIMM0")) 1494 .WillOnce(Return(std::nullopt)); 1495 1496 // Add some location code in expanded format to DIMM cache memory 1497 dataIface.addDIMMLocCode("U98D-P0-DIMM0", true); 1498 1499 PEL pel{entry, 42, timestamp, phosphor::logging::Entry::Level::Error, 1500 ad, ffdc, dataIface, journal}; 1501 1502 nlohmann::json dimmInfoJson = getDIMMInfo(pel); 1503 1504 nlohmann::json expected_data = R"( 1505 [ 1506 "Failed reading DI property from VINI Interface for the LocationCode:[U98D-P0-DIMM0]" 1507 ] 1508 )"_json; 1509 1510 EXPECT_EQ(expected_data, dimmInfoJson); 1511 } 1512 } 1513