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