1 #include "fru-area.hpp" 2 #include "frup.hpp" 3 #include "types.hpp" 4 5 #include <dlfcn.h> 6 #include <host-ipmid/ipmid-api.h> 7 #include <mapper.h> 8 #include <systemd/sd-bus.h> 9 #include <unistd.h> 10 11 #include <algorithm> 12 #include <cstdio> 13 #include <cstring> 14 #include <exception> 15 #include <fstream> 16 #include <iostream> 17 #include <memory> 18 #include <phosphor-logging/log.hpp> 19 #include <sdbusplus/server.hpp> 20 #include <sstream> 21 #include <vector> 22 23 using namespace ipmi::vpd; 24 using namespace phosphor::logging; 25 26 extern const FruMap frus; 27 extern const std::map<Path, InterfaceMap> extras; 28 29 namespace 30 { 31 32 //------------------------------------------------------------ 33 // Cleanup routine 34 // Must always be called as last reference to fru_fp. 35 //------------------------------------------------------------ 36 int cleanupError(FILE* fru_fp, fru_area_vec_t& fru_area_vec) 37 { 38 if (fru_fp != NULL) 39 { 40 std::fclose(fru_fp); 41 } 42 43 if (!(fru_area_vec.empty())) 44 { 45 fru_area_vec.clear(); 46 } 47 48 return -1; 49 } 50 51 //------------------------------------------------------------------------ 52 // Gets the value of the key from the fru dictionary of the given section. 53 // FRU dictionary is parsed fru data for all the sections. 54 //------------------------------------------------------------------------ 55 std::string getFRUValue(const std::string& section, const std::string& key, 56 const std::string& delimiter, IPMIFruInfo& fruData) 57 { 58 59 auto minIndexValue = 0; 60 auto maxIndexValue = 0; 61 std::string fruValue = ""; 62 63 if (section == "Board") 64 { 65 minIndexValue = OPENBMC_VPD_KEY_BOARD_MFG_DATE; 66 maxIndexValue = OPENBMC_VPD_KEY_BOARD_MAX; 67 } 68 else if (section == "Product") 69 { 70 minIndexValue = OPENBMC_VPD_KEY_PRODUCT_MFR; 71 maxIndexValue = OPENBMC_VPD_KEY_PRODUCT_MAX; 72 } 73 else if (section == "Chassis") 74 { 75 minIndexValue = OPENBMC_VPD_KEY_CHASSIS_TYPE; 76 maxIndexValue = OPENBMC_VPD_KEY_CHASSIS_MAX; 77 } 78 79 auto first = fruData.cbegin() + minIndexValue; 80 auto last = first + (maxIndexValue - minIndexValue) + 1; 81 82 auto itr = 83 std::find_if(first, last, [&key](auto& e) { return key == e.first; }); 84 85 if (itr != last) 86 { 87 fruValue = itr->second; 88 } 89 90 // if the key is custom property then the value could be in two formats. 91 // 1) custom field 2 = "value". 92 // 2) custom field 2 = "key:value". 93 // if delimiter length = 0 i.e custom field 2 = "value" 94 95 constexpr auto customProp = "Custom Field"; 96 if (key.find(customProp) != std::string::npos) 97 { 98 if (delimiter.length() > 0) 99 { 100 size_t delimiterpos = fruValue.find(delimiter); 101 if (delimiterpos != std::string::npos) 102 fruValue = fruValue.substr(delimiterpos + 1); 103 } 104 } 105 return fruValue; 106 } 107 108 // Get the inventory service from the mapper. 109 auto getService(sdbusplus::bus::bus& bus, const std::string& intf, 110 const std::string& path) 111 { 112 auto mapperCall = 113 bus.new_method_call("xyz.openbmc_project.ObjectMapper", 114 "/xyz/openbmc_project/object_mapper", 115 "xyz.openbmc_project.ObjectMapper", "GetObject"); 116 117 mapperCall.append(path); 118 mapperCall.append(std::vector<std::string>({intf})); 119 std::map<std::string, std::vector<std::string>> mapperResponse; 120 121 try 122 { 123 auto mapperResponseMsg = bus.call(mapperCall); 124 mapperResponseMsg.read(mapperResponse); 125 } 126 catch (const sdbusplus::exception::SdBusError& ex) 127 { 128 log<level::ERR>("Exception from sdbus call", 129 entry("WHAT=%s", ex.what())); 130 throw; 131 } 132 133 if (mapperResponse.begin() == mapperResponse.end()) 134 { 135 throw std::runtime_error("ERROR in reading the mapper response"); 136 } 137 138 return mapperResponse.begin()->first; 139 } 140 141 // Takes FRU data, invokes Parser for each fru record area and updates 142 // Inventory 143 //------------------------------------------------------------------------ 144 int updateInventory(fru_area_vec_t& area_vec, sd_bus* bus_sd) 145 { 146 // Generic error reporter 147 int rc = 0; 148 uint8_t fruid = 0; 149 IPMIFruInfo fruData; 150 151 // For each FRU area, extract the needed data , get it parsed and update 152 // the Inventory. 153 for (const auto& fruArea : area_vec) 154 { 155 fruid = fruArea->get_fruid(); 156 // Fill the container with information 157 rc = parse_fru_area((fruArea)->get_type(), (void*)(fruArea)->get_data(), 158 (fruArea)->get_len(), fruData); 159 if (rc < 0) 160 { 161 log<level::ERR>("Error parsing FRU records"); 162 return rc; 163 } 164 } // END walking the vector of areas and updating 165 166 // For each Fru we have the list of instances which needs to be updated. 167 // Each instance object implements certain interfaces. 168 // Each Interface is having Dbus properties. 169 // Each Dbus Property would be having metaData(eg section,VpdPropertyName). 170 171 // Here we are just printing the object,interface and the properties. 172 // which needs to be called with the new inventory manager implementation. 173 sdbusplus::bus::bus bus{bus_sd}; 174 using namespace std::string_literals; 175 static const auto intf = "xyz.openbmc_project.Inventory.Manager"s; 176 static const auto path = "/xyz/openbmc_project/inventory"s; 177 std::string service; 178 try 179 { 180 service = getService(bus, intf, path); 181 } 182 catch (const std::exception& e) 183 { 184 std::cerr << e.what() << "\n"; 185 return -1; 186 } 187 188 auto iter = frus.find(fruid); 189 if (iter == frus.end()) 190 { 191 log<level::ERR>("Unable to get the fru info", 192 entry("FRU=%d", static_cast<int>(fruid))); 193 return -1; 194 } 195 196 auto& instanceList = iter->second; 197 if (instanceList.size() <= 0) 198 { 199 log<level::DEBUG>("Object list empty for this FRU", 200 entry("FRU=%d", static_cast<int>(fruid))); 201 } 202 203 ObjectMap objects; 204 for (auto& instance : instanceList) 205 { 206 InterfaceMap interfaces; 207 const auto& extrasIter = extras.find(instance.path); 208 209 for (auto& interfaceList : instance.interfaces) 210 { 211 PropertyMap props; // store all the properties 212 for (auto& properties : interfaceList.second) 213 { 214 std::string value; 215 decltype(auto) pdata = properties.second; 216 217 if (!pdata.section.empty() && !pdata.property.empty()) 218 { 219 value = getFRUValue(pdata.section, pdata.property, 220 pdata.delimiter, fruData); 221 } 222 props.emplace(std::move(properties.first), std::move(value)); 223 } 224 // Check and update extra properties 225 if (extras.end() != extrasIter) 226 { 227 const auto& propsIter = 228 (extrasIter->second).find(interfaceList.first); 229 if ((extrasIter->second).end() != propsIter) 230 { 231 for (const auto& map : propsIter->second) 232 { 233 props.emplace(map.first, map.second); 234 } 235 } 236 } 237 interfaces.emplace(std::move(interfaceList.first), 238 std::move(props)); 239 } 240 241 // Call the inventory manager 242 sdbusplus::message::object_path objectPath = instance.path; 243 // Check and update extra properties 244 if (extras.end() != extrasIter) 245 { 246 for (const auto& entry : extrasIter->second) 247 { 248 if (interfaces.end() == interfaces.find(entry.first)) 249 { 250 interfaces.emplace(entry.first, entry.second); 251 } 252 } 253 } 254 objects.emplace(objectPath, interfaces); 255 } 256 257 auto pimMsg = bus.new_method_call(service.c_str(), path.c_str(), 258 intf.c_str(), "Notify"); 259 pimMsg.append(std::move(objects)); 260 261 try 262 { 263 auto inventoryMgrResponseMsg = bus.call(pimMsg); 264 } 265 catch (const sdbusplus::exception::SdBusError& ex) 266 { 267 log<level::ERR>("Error in notify call", entry("WHAT=%s", ex.what())); 268 return -1; 269 } 270 271 return rc; 272 } 273 274 } // namespace 275 276 //---------------------------------------------------------------- 277 // Constructor 278 //---------------------------------------------------------------- 279 ipmi_fru::ipmi_fru(const uint8_t fruid, const ipmi_fru_area_type type, 280 sd_bus* bus_type, bool bmc_fru) 281 { 282 iv_fruid = fruid; 283 iv_type = type; 284 iv_bmc_fru = bmc_fru; 285 iv_bus_type = bus_type; 286 iv_valid = false; 287 iv_data = NULL; 288 iv_present = false; 289 290 if (iv_type == IPMI_FRU_AREA_INTERNAL_USE) 291 { 292 iv_name = "INTERNAL_"; 293 } 294 else if (iv_type == IPMI_FRU_AREA_CHASSIS_INFO) 295 { 296 iv_name = "CHASSIS_"; 297 } 298 else if (iv_type == IPMI_FRU_AREA_BOARD_INFO) 299 { 300 iv_name = "BOARD_"; 301 } 302 else if (iv_type == IPMI_FRU_AREA_PRODUCT_INFO) 303 { 304 iv_name = "PRODUCT_"; 305 } 306 else if (iv_type == IPMI_FRU_AREA_MULTI_RECORD) 307 { 308 iv_name = "MULTI_"; 309 } 310 else 311 { 312 iv_name = IPMI_FRU_AREA_TYPE_MAX; 313 log<level::ERR>("Invalid Area", entry("TYPE=%d", iv_type)); 314 } 315 } 316 317 //----------------------------------------------------- 318 // For a FRU area type, accepts the data and updates 319 // area specific data. 320 //----------------------------------------------------- 321 void ipmi_fru::set_data(const uint8_t* data, const size_t len) 322 { 323 iv_len = len; 324 iv_data = new uint8_t[len]; 325 std::memcpy(iv_data, data, len); 326 } 327 328 //----------------------------------------------------- 329 // Sets the dbus parameters 330 //----------------------------------------------------- 331 void ipmi_fru::update_dbus_paths(const char* bus_name, const char* obj_path, 332 const char* intf_name) 333 { 334 iv_bus_name = bus_name; 335 iv_obj_path = obj_path; 336 iv_intf_name = intf_name; 337 } 338 339 //------------------- 340 // Destructor 341 //------------------- 342 ipmi_fru::~ipmi_fru() 343 { 344 if (iv_data != NULL) 345 { 346 delete[] iv_data; 347 iv_data = NULL; 348 } 349 } 350 351 //------------------------------------------------ 352 // Takes the pointer to stream of bytes and length 353 // and returns the 8 bit checksum 354 // This algo is per IPMI V2.0 spec 355 //------------------------------------------------- 356 unsigned char calculate_crc(const unsigned char* data, size_t len) 357 { 358 char crc = 0; 359 size_t byte = 0; 360 361 for (byte = 0; byte < len; byte++) 362 { 363 crc += *data++; 364 } 365 366 return (-crc); 367 } 368 369 //--------------------------------------------------------------------- 370 // Accepts a fru area offset in commom hdr and tells which area it is. 371 //--------------------------------------------------------------------- 372 ipmi_fru_area_type get_fru_area_type(uint8_t area_offset) 373 { 374 ipmi_fru_area_type type = IPMI_FRU_AREA_TYPE_MAX; 375 376 switch (area_offset) 377 { 378 case IPMI_FRU_INTERNAL_OFFSET: 379 type = IPMI_FRU_AREA_INTERNAL_USE; 380 break; 381 382 case IPMI_FRU_CHASSIS_OFFSET: 383 type = IPMI_FRU_AREA_CHASSIS_INFO; 384 break; 385 386 case IPMI_FRU_BOARD_OFFSET: 387 type = IPMI_FRU_AREA_BOARD_INFO; 388 break; 389 390 case IPMI_FRU_PRODUCT_OFFSET: 391 type = IPMI_FRU_AREA_PRODUCT_INFO; 392 break; 393 394 case IPMI_FRU_MULTI_OFFSET: 395 type = IPMI_FRU_AREA_MULTI_RECORD; 396 break; 397 398 default: 399 type = IPMI_FRU_AREA_TYPE_MAX; 400 } 401 402 return type; 403 } 404 405 ///----------------------------------------------- 406 // Validates the data for crc and mandatory fields 407 ///----------------------------------------------- 408 int verify_fru_data(const uint8_t* data, const size_t len) 409 { 410 uint8_t checksum = 0; 411 int rc = -1; 412 413 // Validate for first byte to always have a value of [1] 414 if (data[0] != IPMI_FRU_HDR_BYTE_ZERO) 415 { 416 log<level::ERR>("Invalid entry in byte-0", 417 entry("ENTRY=0x%X", static_cast<uint32_t>(data[0]))); 418 return rc; 419 } 420 #ifdef __IPMI_DEBUG__ 421 else 422 { 423 log<level::DEBUG>("Validated in entry_1 of fru_data", 424 entry("ENTRY=0x%X", static_cast<uint32_t>(data[0]))); 425 } 426 #endif 427 428 // See if the calculated CRC matches with the embedded one. 429 // CRC to be calculated on all except the last one that is CRC itself. 430 checksum = calculate_crc(data, len - 1); 431 if (checksum != data[len - 1]) 432 { 433 #ifdef __IPMI_DEBUG__ 434 log<level::ERR>( 435 "Checksum mismatch", 436 entry("Calculated=0x%X", static_cast<uint32_t>(checksum)), 437 entry("Embedded=0x%X", static_cast<uint32_t>(data[len]))); 438 #endif 439 return rc; 440 } 441 #ifdef __IPMI_DEBUG__ 442 else 443 { 444 log<level::DEBUG>("Checksum matches"); 445 } 446 #endif 447 448 return EXIT_SUCCESS; 449 } 450 451 ///---------------------------------------------------- 452 // Checks if a particular fru area is populated or not 453 ///---------------------------------------------------- 454 bool remove_invalid_area(const std::unique_ptr<ipmi_fru>& fru_area) 455 { 456 // Filter the ones that are empty 457 if (!(fru_area->get_len())) 458 { 459 return true; 460 } 461 return false; 462 } 463 464 ///---------------------------------------------------------------------------------- 465 // Populates various FRU areas 466 // @prereq : This must be called only after validating common header. 467 ///---------------------------------------------------------------------------------- 468 int ipmi_populate_fru_areas(uint8_t* fru_data, const size_t data_len, 469 fru_area_vec_t& fru_area_vec) 470 { 471 int rc = -1; 472 473 // Now walk the common header and see if the file size has atleast the last 474 // offset mentioned by the common_hdr. If the file size is less than the 475 // offset of any if the fru areas mentioned in the common header, then we do 476 // not have a complete file. 477 for (uint8_t fru_entry = IPMI_FRU_INTERNAL_OFFSET; 478 fru_entry < (sizeof(struct common_header) - 2); fru_entry++) 479 { 480 rc = -1; 481 // Actual offset in the payload is the offset mentioned in common header 482 // multiplied by 8. Common header is always the first 8 bytes. 483 size_t area_offset = fru_data[fru_entry] * IPMI_EIGHT_BYTES; 484 if (area_offset && (data_len < (area_offset + 2))) 485 { 486 // Our file size is less than what it needs to be. +2 because we are 487 // using area len that is at 2 byte off area_offset 488 log<level::ERR>("fru file is incomplete", 489 entry("SIZE=%d", data_len)); 490 return rc; 491 } 492 else if (area_offset) 493 { 494 // Read 2 bytes to know the actual size of area. 495 uint8_t area_hdr[2] = {0}; 496 std::memcpy(area_hdr, &((uint8_t*)fru_data)[area_offset], 497 sizeof(area_hdr)); 498 499 // Size of this area will be the 2nd byte in the fru area header. 500 size_t area_len = area_hdr[1] * IPMI_EIGHT_BYTES; 501 uint8_t area_data[area_len] = {0}; 502 503 log<level::DEBUG>("Fru Data", entry("SIZE=%d", data_len), 504 entry("AREA OFFSET=%d", area_offset), 505 entry("AREA_SIZE=%d", area_len)); 506 507 // See if we really have that much buffer. We have area offset amd 508 // from there, the actual len. 509 if (data_len < (area_len + area_offset)) 510 { 511 log<level::ERR>("Incomplete Fru file", 512 entry("SIZE=%d", data_len)); 513 return rc; 514 } 515 516 // Save off the data. 517 std::memcpy(area_data, &((uint8_t*)fru_data)[area_offset], 518 area_len); 519 520 // Validate the crc 521 rc = verify_fru_data(area_data, area_len); 522 if (rc < 0) 523 { 524 log<level::ERR>("Err validating fru area", 525 entry("OFFSET=%d", area_offset)); 526 return rc; 527 } 528 else 529 { 530 log<level::DEBUG>("Successfully verified area checksum.", 531 entry("OFFSET=%d", area_offset)); 532 } 533 534 // We already have a vector that is passed to us containing all 535 // of the fields populated. Update the data portion now. 536 for (auto& iter : fru_area_vec) 537 { 538 if ((iter)->get_type() == get_fru_area_type(fru_entry)) 539 { 540 (iter)->set_data(area_data, area_len); 541 } 542 } 543 } // If we have fru data present 544 } // Walk common_hdr 545 546 // Not all the fields will be populated in a fru data. Mostly all cases will 547 // not have more than 2 or 3. 548 fru_area_vec.erase(std::remove_if(fru_area_vec.begin(), fru_area_vec.end(), 549 remove_invalid_area), 550 fru_area_vec.end()); 551 552 return EXIT_SUCCESS; 553 } 554 555 ///--------------------------------------------------------- 556 // Validates the fru data per ipmi common header constructs. 557 // Returns with updated common_hdr and also file_size 558 //---------------------------------------------------------- 559 int ipmi_validate_common_hdr(const uint8_t* fru_data, const size_t data_len) 560 { 561 int rc = -1; 562 563 uint8_t common_hdr[sizeof(struct common_header)] = {0}; 564 if (data_len >= sizeof(common_hdr)) 565 { 566 std::memcpy(common_hdr, fru_data, sizeof(common_hdr)); 567 } 568 else 569 { 570 log<level::ERR>("Incomplete fru data file", entry("SIZE=%d", data_len)); 571 return rc; 572 } 573 574 // Verify the crc and size 575 rc = verify_fru_data(common_hdr, sizeof(common_hdr)); 576 if (rc < 0) 577 { 578 log<level::ERR>("Failed to validate common header"); 579 return rc; 580 } 581 582 return EXIT_SUCCESS; 583 } 584 585 ///----------------------------------------------------- 586 // Accepts the filename and validates per IPMI FRU spec 587 //---------------------------------------------------- 588 int validateFRUArea(const uint8_t fruid, const char* fru_file_name, 589 sd_bus* bus_type, const bool bmc_fru) 590 { 591 size_t data_len = 0; 592 size_t bytes_read = 0; 593 int rc = -1; 594 595 // Vector that holds individual IPMI FRU AREAs. Although MULTI and INTERNAL 596 // are not used, keeping it here for completeness. 597 fru_area_vec_t fru_area_vec; 598 599 for (uint8_t fru_entry = IPMI_FRU_INTERNAL_OFFSET; 600 fru_entry < (sizeof(struct common_header) - 2); fru_entry++) 601 { 602 // Create an object and push onto a vector. 603 std::unique_ptr<ipmi_fru> fru_area = std::make_unique<ipmi_fru>( 604 fruid, get_fru_area_type(fru_entry), bus_type, bmc_fru); 605 606 // Physically being present 607 bool present = access(fru_file_name, F_OK) == 0; 608 fru_area->set_present(present); 609 610 fru_area_vec.emplace_back(std::move(fru_area)); 611 } 612 613 FILE* fru_fp = std::fopen(fru_file_name, "rb"); 614 if (fru_fp == NULL) 615 { 616 log<level::ERR>("Unable to open fru file", 617 entry("FILE=%s", fru_file_name), 618 entry("ERRNO=%s", std::strerror(errno))); 619 return cleanupError(fru_fp, fru_area_vec); 620 } 621 622 // Get the size of the file to see if it meets minimum requirement 623 if (std::fseek(fru_fp, 0, SEEK_END)) 624 { 625 log<level::ERR>("Unable to seek fru file", 626 entry("FILE=%s", fru_file_name), 627 entry("ERRNO=%s", std::strerror(errno))); 628 return cleanupError(fru_fp, fru_area_vec); 629 } 630 631 // Allocate a buffer to hold entire file content 632 data_len = std::ftell(fru_fp); 633 uint8_t fru_data[data_len] = {0}; 634 635 std::rewind(fru_fp); 636 bytes_read = std::fread(fru_data, data_len, 1, fru_fp); 637 if (bytes_read != 1) 638 { 639 log<level::ERR>("Failed reading fru data.", 640 entry("BYTESREAD=%d", bytes_read), 641 entry("ERRNO=%s", std::strerror(errno))); 642 return cleanupError(fru_fp, fru_area_vec); 643 } 644 645 // We are done reading. 646 std::fclose(fru_fp); 647 fru_fp = NULL; 648 649 rc = ipmi_validate_common_hdr(fru_data, data_len); 650 if (rc < 0) 651 { 652 return cleanupError(fru_fp, fru_area_vec); 653 } 654 655 // Now that we validated the common header, populate various fru sections if 656 // we have them here. 657 rc = ipmi_populate_fru_areas(fru_data, data_len, fru_area_vec); 658 if (rc < 0) 659 { 660 log<level::ERR>("Populating FRU areas failed", entry("FRU=%d", fruid)); 661 return cleanupError(fru_fp, fru_area_vec); 662 } 663 else 664 { 665 log<level::DEBUG>("Populated FRU areas", 666 entry("FILE=%s", fru_file_name)); 667 } 668 669 #ifdef __IPMI_DEBUG__ 670 for (auto& iter : fru_area_vec) 671 { 672 std::printf("FRU ID : [%d]\n", (iter)->get_fruid()); 673 std::printf("AREA NAME : [%s]\n", (iter)->get_name()); 674 std::printf("TYPE : [%d]\n", (iter)->get_type()); 675 std::printf("LEN : [%d]\n", (iter)->get_len()); 676 std::printf("BUS NAME : [%s]\n", (iter)->get_bus_name()); 677 std::printf("OBJ PATH : [%s]\n", (iter)->get_obj_path()); 678 std::printf("INTF NAME :[%s]\n", (iter)->get_intf_name()); 679 } 680 #endif 681 682 // If the vector is populated with everything, then go ahead and update the 683 // inventory. 684 if (!(fru_area_vec.empty())) 685 { 686 687 #ifdef __IPMI_DEBUG__ 688 std::printf("\n SIZE of vector is : [%d] \n", fru_area_vec.size()); 689 #endif 690 rc = updateInventory(fru_area_vec, bus_type); 691 if (rc < 0) 692 { 693 log<level::ERR>("Error updating inventory."); 694 } 695 } 696 697 // we are done with all that we wanted to do. This will do the job of 698 // calling any destructors too. 699 fru_area_vec.clear(); 700 701 return rc; 702 } 703