1 #include "fru.hpp" 2 3 #include "common/utils.hpp" 4 5 #include <libpldm/entity.h> 6 #include <libpldm/utils.h> 7 #include <systemd/sd-journal.h> 8 9 #include <phosphor-logging/lg2.hpp> 10 #include <sdbusplus/bus.hpp> 11 12 #include <optional> 13 #include <set> 14 #include <stack> 15 16 PHOSPHOR_LOG2_USING; 17 18 namespace pldm 19 { 20 namespace responder 21 { 22 23 constexpr auto root = "/xyz/openbmc_project/inventory/"; 24 25 std::optional<pldm_entity> FruImpl::getEntityByObjectPath( 26 const dbus::InterfaceMap& intfMaps) 27 { 28 for (const auto& intfMap : intfMaps) 29 { 30 try 31 { 32 pldm_entity entity{}; 33 entity.entity_type = parser.getEntityType(intfMap.first); 34 return entity; 35 } 36 catch (const std::exception&) 37 { 38 continue; 39 } 40 } 41 42 return std::nullopt; 43 } 44 45 void FruImpl::updateAssociationTree(const dbus::ObjectValueTree& objects, 46 const std::string& path) 47 { 48 if (path.find(root) == std::string::npos) 49 { 50 return; 51 } 52 53 std::stack<std::string> tmpObjPaths{}; 54 tmpObjPaths.emplace(path); 55 56 auto obj = pldm::utils::findParent(path); 57 while ((obj + '/') != root) 58 { 59 tmpObjPaths.emplace(obj); 60 obj = pldm::utils::findParent(obj); 61 } 62 63 std::stack<std::string> tmpObj = tmpObjPaths; 64 while (!tmpObj.empty()) 65 { 66 std::string s = tmpObj.top(); 67 tmpObj.pop(); 68 } 69 // Update pldm entity to association tree 70 std::string prePath = tmpObjPaths.top(); 71 while (!tmpObjPaths.empty()) 72 { 73 std::string currPath = tmpObjPaths.top(); 74 tmpObjPaths.pop(); 75 76 do 77 { 78 if (objToEntityNode.contains(currPath)) 79 { 80 pldm_entity node = 81 pldm_entity_extract(objToEntityNode.at(currPath)); 82 if (pldm_entity_association_tree_find_with_locality( 83 entityTree, &node, false)) 84 { 85 break; 86 } 87 } 88 else 89 { 90 if (!objects.contains(currPath)) 91 { 92 break; 93 } 94 95 auto entityPtr = getEntityByObjectPath(objects.at(currPath)); 96 if (!entityPtr) 97 { 98 break; 99 } 100 101 pldm_entity entity = *entityPtr; 102 103 for (const auto& it : objToEntityNode) 104 { 105 pldm_entity node = pldm_entity_extract(it.second); 106 if (node.entity_type == entity.entity_type) 107 { 108 entity.entity_instance_num = 109 node.entity_instance_num + 1; 110 break; 111 } 112 } 113 114 if (currPath == prePath) 115 { 116 auto node = pldm_entity_association_tree_add_entity( 117 entityTree, &entity, 0xFFFF, nullptr, 118 PLDM_ENTITY_ASSOCIAION_PHYSICAL, false, true, 0xFFFF); 119 objToEntityNode[currPath] = node; 120 } 121 else 122 { 123 if (objToEntityNode.contains(prePath)) 124 { 125 auto node = pldm_entity_association_tree_add_entity( 126 entityTree, &entity, 0xFFFF, 127 objToEntityNode[prePath], 128 PLDM_ENTITY_ASSOCIAION_PHYSICAL, false, true, 129 0xFFFF); 130 objToEntityNode[currPath] = node; 131 } 132 } 133 } 134 } while (0); 135 136 prePath = currPath; 137 } 138 } 139 140 void FruImpl::buildFRUTable() 141 { 142 if (isBuilt) 143 { 144 return; 145 } 146 147 fru_parser::DBusLookupInfo dbusInfo; 148 149 try 150 { 151 dbusInfo = parser.inventoryLookup(); 152 objects = pldm::utils::DBusHandler::getInventoryObjects< 153 pldm::utils::DBusHandler>(); 154 } 155 catch (const std::exception& e) 156 { 157 error( 158 "Failed to build FRU table due to inventory lookup, error - {ERROR}", 159 "ERROR", e); 160 return; 161 } 162 163 auto itemIntfsLookup = std::get<2>(dbusInfo); 164 165 for (const auto& object : objects) 166 { 167 const auto& interfaces = object.second; 168 for (const auto& interface : interfaces) 169 { 170 if (itemIntfsLookup.contains(interface.first)) 171 { 172 // checking fru present property is available or not. 173 if (!pldm::utils::checkForFruPresence(object.first.str)) 174 { 175 continue; 176 } 177 178 // An exception will be thrown by getRecordInfo, if the item 179 // D-Bus interface name specified in FRU_Master.json does 180 // not have corresponding config jsons 181 try 182 { 183 updateAssociationTree(objects, object.first.str); 184 pldm_entity entity{}; 185 if (objToEntityNode.contains(object.first.str)) 186 { 187 pldm_entity_node* node = 188 objToEntityNode.at(object.first.str); 189 190 entity = pldm_entity_extract(node); 191 } 192 193 auto recordInfos = parser.getRecordInfo(interface.first); 194 populateRecords(interfaces, recordInfos, entity); 195 196 associatedEntityMap.emplace(object.first, entity); 197 break; 198 } 199 catch (const std::exception& e) 200 { 201 error( 202 "Config JSONs missing for the item '{INTERFACE}', error - {ERROR}", 203 "INTERFACE", interface.first, "ERROR", e); 204 break; 205 } 206 } 207 } 208 } 209 210 int rc = pldm_entity_association_pdr_add(entityTree, pdrRepo, false, 211 TERMINUS_HANDLE); 212 if (rc < 0) 213 { 214 // pldm_entity_assocation_pdr_add() assert()ed on failure 215 error("Failed to add PLDM entity association PDR, response code '{RC}'", 216 "RC", rc); 217 throw std::runtime_error("Failed to add PLDM entity association PDR"); 218 } 219 220 // save a copy of bmc's entity association tree 221 pldm_entity_association_tree_copy_root(entityTree, bmcEntityTree); 222 223 isBuilt = true; 224 } 225 std::string FruImpl::populatefwVersion() 226 { 227 static constexpr auto fwFunctionalObjPath = 228 "/xyz/openbmc_project/software/functional"; 229 auto& bus = pldm::utils::DBusHandler::getBus(); 230 std::string currentBmcVersion; 231 try 232 { 233 auto method = 234 bus.new_method_call(pldm::utils::mapperService, fwFunctionalObjPath, 235 pldm::utils::dbusProperties, "Get"); 236 method.append("xyz.openbmc_project.Association", "endpoints"); 237 std::variant<std::vector<std::string>> paths; 238 auto reply = bus.call(method, dbusTimeout); 239 reply.read(paths); 240 auto fwRunningVersion = std::get<std::vector<std::string>>(paths)[0]; 241 constexpr auto versionIntf = "xyz.openbmc_project.Software.Version"; 242 auto version = pldm::utils::DBusHandler().getDbusPropertyVariant( 243 fwRunningVersion.c_str(), "Version", versionIntf); 244 currentBmcVersion = std::get<std::string>(version); 245 } 246 catch (const std::exception& e) 247 { 248 error("Failed to make a d-bus call Association, error - {ERROR}", 249 "ERROR", e); 250 return {}; 251 } 252 return currentBmcVersion; 253 } 254 void FruImpl::populateRecords( 255 const pldm::responder::dbus::InterfaceMap& interfaces, 256 const fru_parser::FruRecordInfos& recordInfos, const pldm_entity& entity) 257 { 258 // recordSetIdentifier for the FRU will be set when the first record gets 259 // added for the FRU 260 uint16_t recordSetIdentifier = 0; 261 auto numRecsCount = numRecs; 262 static uint32_t bmc_record_handle = 0; 263 264 for (const auto& [recType, encType, fieldInfos] : recordInfos) 265 { 266 std::vector<uint8_t> tlvs; 267 uint8_t numFRUFields = 0; 268 for (const auto& [intf, prop, propType, fieldTypeNum] : fieldInfos) 269 { 270 try 271 { 272 pldm::responder::dbus::Value propValue; 273 274 // Assuming that 0 container Id is assigned to the System (as 275 // that should be the top most container as per dbus hierarchy) 276 if (entity.entity_container_id == 0 && prop == "Version") 277 { 278 propValue = populatefwVersion(); 279 } 280 else 281 { 282 propValue = interfaces.at(intf).at(prop); 283 } 284 if (propType == "bytearray") 285 { 286 auto byteArray = std::get<std::vector<uint8_t>>(propValue); 287 if (!byteArray.size()) 288 { 289 continue; 290 } 291 292 numFRUFields++; 293 tlvs.emplace_back(fieldTypeNum); 294 tlvs.emplace_back(byteArray.size()); 295 std::move(std::begin(byteArray), std::end(byteArray), 296 std::back_inserter(tlvs)); 297 } 298 else if (propType == "string") 299 { 300 auto str = std::get<std::string>(propValue); 301 if (!str.size()) 302 { 303 continue; 304 } 305 306 numFRUFields++; 307 tlvs.emplace_back(fieldTypeNum); 308 tlvs.emplace_back(str.size()); 309 std::move(std::begin(str), std::end(str), 310 std::back_inserter(tlvs)); 311 } 312 } 313 catch (const std::out_of_range&) 314 { 315 continue; 316 } 317 } 318 319 if (tlvs.size()) 320 { 321 if (numRecs == numRecsCount) 322 { 323 recordSetIdentifier = nextRSI(); 324 bmc_record_handle = nextRecordHandle(); 325 int rc = pldm_pdr_add_fru_record_set( 326 pdrRepo, TERMINUS_HANDLE, recordSetIdentifier, 327 entity.entity_type, entity.entity_instance_num, 328 entity.entity_container_id, &bmc_record_handle); 329 if (rc) 330 { 331 // pldm_pdr_add_fru_record_set() assert()ed on failure 332 throw std::runtime_error( 333 "Failed to add PDR FRU record set"); 334 } 335 } 336 auto curSize = table.size(); 337 table.resize(curSize + recHeaderSize + tlvs.size()); 338 encode_fru_record(table.data(), table.size(), &curSize, 339 recordSetIdentifier, recType, numFRUFields, 340 encType, tlvs.data(), tlvs.size()); 341 numRecs++; 342 } 343 } 344 } 345 346 void FruImpl::deleteFRURecord(uint16_t rsi) 347 { 348 std::vector<uint8_t> updatedFruTbl; 349 size_t pos = 0; 350 351 while (pos < table.size()) 352 { 353 // Ensure enough space for the record header 354 if ((table.size() - pos) < sizeof(struct pldm_fru_record_data_format)) 355 { 356 // Log or handle corrupt/truncated record 357 error("Error: Incomplete FRU record header"); 358 return; 359 } 360 361 auto recordSetSrc = 362 reinterpret_cast<const struct pldm_fru_record_data_format*>( 363 &table[pos]); 364 365 size_t recordLen = sizeof(struct pldm_fru_record_data_format) - 366 sizeof(struct pldm_fru_record_tlv); 367 368 const struct pldm_fru_record_tlv* tlv = recordSetSrc->tlvs; 369 370 for (uint8_t i = 0; i < recordSetSrc->num_fru_fields; ++i) 371 { 372 if ((table.size() - pos) < (recordLen + sizeof(*tlv))) 373 { 374 error("Error: Incomplete TLV header"); 375 return; 376 } 377 378 size_t len = sizeof(*tlv) - 1 + tlv->length; 379 380 if ((table.size() - pos) < (recordLen + len)) 381 { 382 error("Error: Incomplete TLV data"); 383 return; 384 } 385 386 recordLen += len; 387 388 // Advance to next tlv 389 tlv = reinterpret_cast<const struct pldm_fru_record_tlv*>( 390 reinterpret_cast<const uint8_t*>(tlv) + len); 391 } 392 393 if ((le16toh(recordSetSrc->record_set_id) != rsi && rsi != 0)) 394 { 395 std::copy(table.begin() + pos, table.begin() + pos + recordLen, 396 std::back_inserter(updatedFruTbl)); 397 } 398 else 399 { 400 // Deleted record 401 numRecs--; 402 } 403 404 pos += recordLen; 405 } 406 // Replace the old table with the updated one 407 table = std::move(updatedFruTbl); 408 } 409 410 std::vector<uint8_t> FruImpl::tableResize() 411 { 412 std::vector<uint8_t> tempTable; 413 414 if (table.size()) 415 { 416 std::copy(table.begin(), table.end(), std::back_inserter(tempTable)); 417 padBytes = pldm::utils::getNumPadBytes(table.size()); 418 tempTable.resize(tempTable.size() + padBytes, 0); 419 } 420 return tempTable; 421 } 422 423 void FruImpl::getFRUTable(Response& response) 424 { 425 auto hdrSize = response.size(); 426 std::vector<uint8_t> tempTable; 427 428 if (table.size()) 429 { 430 tempTable = tableResize(); 431 checksum = pldm_edac_crc32(tempTable.data(), tempTable.size()); 432 } 433 response.resize(hdrSize + tempTable.size() + sizeof(checksum), 0); 434 std::copy(tempTable.begin(), tempTable.end(), response.begin() + hdrSize); 435 436 // Copy the checksum to response data 437 auto iter = response.begin() + hdrSize + tempTable.size(); 438 std::copy_n(reinterpret_cast<const uint8_t*>(&checksum), sizeof(checksum), 439 iter); 440 } 441 442 void FruImpl::getFRURecordTableMetadata() 443 { 444 std::vector<uint8_t> tempTable; 445 if (table.size()) 446 { 447 tempTable = tableResize(); 448 checksum = pldm_edac_crc32(tempTable.data(), tempTable.size()); 449 } 450 } 451 452 int FruImpl::getFRURecordByOption( 453 std::vector<uint8_t>& fruData, uint16_t /* fruTableHandle */, 454 uint16_t recordSetIdentifer, uint8_t recordType, uint8_t fieldType) 455 { 456 using sum = uint32_t; 457 458 // FRU table is built lazily, build if not done. 459 buildFRUTable(); 460 461 /* 7 is sizeof(checksum,4) + padBytesMax(3) 462 * We can not know size of the record table got by options in advance, but 463 * it must be less than the source table. So it's safe to use sizeof the 464 * source table + 7 as the buffer length 465 */ 466 size_t recordTableSize = table.size() - padBytes + 7; 467 fruData.resize(recordTableSize, 0); 468 469 int rc = get_fru_record_by_option( 470 table.data(), table.size() - padBytes, fruData.data(), &recordTableSize, 471 recordSetIdentifer, recordType, fieldType); 472 473 if (rc != PLDM_SUCCESS || recordTableSize == 0) 474 { 475 return PLDM_FRU_DATA_STRUCTURE_TABLE_UNAVAILABLE; 476 } 477 478 auto pads = pldm::utils::getNumPadBytes(recordTableSize); 479 pldm_edac_crc32(fruData.data(), recordTableSize + pads); 480 481 auto iter = fruData.begin() + recordTableSize + pads; 482 std::copy_n(reinterpret_cast<const uint8_t*>(&checksum), sizeof(checksum), 483 iter); 484 fruData.resize(recordTableSize + pads + sizeof(sum)); 485 486 return PLDM_SUCCESS; 487 } 488 489 int FruImpl::setFRUTable(const std::vector<uint8_t>& fruData) 490 { 491 auto record = 492 reinterpret_cast<const pldm_fru_record_data_format*>(fruData.data()); 493 if (record) 494 { 495 if (oemFruHandler && record->record_type == PLDM_FRU_RECORD_TYPE_OEM) 496 { 497 auto rc = oemFruHandler->processOEMFRUTable(fruData); 498 if (!rc) 499 { 500 return PLDM_SUCCESS; 501 } 502 } 503 } 504 return PLDM_ERROR_UNSUPPORTED_PLDM_CMD; 505 } 506 507 namespace fru 508 { 509 Response Handler::getFRURecordTableMetadata(const pldm_msg* request, 510 size_t /*payloadLength*/) 511 { 512 // FRU table is built lazily, build if not done. 513 buildFRUTable(); 514 515 constexpr uint8_t major = 0x01; 516 constexpr uint8_t minor = 0x00; 517 constexpr uint32_t maxSize = 0xFFFFFFFF; 518 519 Response response(sizeof(pldm_msg_hdr) + 520 PLDM_GET_FRU_RECORD_TABLE_METADATA_RESP_BYTES, 521 0); 522 auto responsePtr = new (response.data()) pldm_msg; 523 524 impl.getFRURecordTableMetadata(); 525 526 auto rc = encode_get_fru_record_table_metadata_resp( 527 request->hdr.instance_id, PLDM_SUCCESS, major, minor, maxSize, 528 impl.size(), impl.numRSI(), impl.numRecords(), impl.checkSum(), 529 responsePtr); 530 if (rc != PLDM_SUCCESS) 531 { 532 return ccOnlyResponse(request, rc); 533 } 534 535 return response; 536 } 537 538 Response Handler::getFRURecordTable(const pldm_msg* request, 539 size_t payloadLength) 540 { 541 // FRU table is built lazily, build if not done. 542 buildFRUTable(); 543 544 if (payloadLength != PLDM_GET_FRU_RECORD_TABLE_REQ_BYTES) 545 { 546 return ccOnlyResponse(request, PLDM_ERROR_INVALID_LENGTH); 547 } 548 549 Response response( 550 sizeof(pldm_msg_hdr) + PLDM_GET_FRU_RECORD_TABLE_MIN_RESP_BYTES, 0); 551 auto responsePtr = new (response.data()) pldm_msg; 552 553 auto rc = 554 encode_get_fru_record_table_resp(request->hdr.instance_id, PLDM_SUCCESS, 555 0, PLDM_START_AND_END, responsePtr); 556 if (rc != PLDM_SUCCESS) 557 { 558 return ccOnlyResponse(request, rc); 559 } 560 561 impl.getFRUTable(response); 562 563 return response; 564 } 565 566 Response Handler::getFRURecordByOption(const pldm_msg* request, 567 size_t payloadLength) 568 { 569 if (payloadLength != sizeof(pldm_get_fru_record_by_option_req)) 570 { 571 return ccOnlyResponse(request, PLDM_ERROR_INVALID_LENGTH); 572 } 573 574 uint32_t retDataTransferHandle{}; 575 uint16_t retFruTableHandle{}; 576 uint16_t retRecordSetIdentifier{}; 577 uint8_t retRecordType{}; 578 uint8_t retFieldType{}; 579 uint8_t retTransferOpFlag{}; 580 581 auto rc = decode_get_fru_record_by_option_req( 582 request, payloadLength, &retDataTransferHandle, &retFruTableHandle, 583 &retRecordSetIdentifier, &retRecordType, &retFieldType, 584 &retTransferOpFlag); 585 586 if (rc != PLDM_SUCCESS) 587 { 588 return ccOnlyResponse(request, rc); 589 } 590 591 std::vector<uint8_t> fruData; 592 rc = impl.getFRURecordByOption(fruData, retFruTableHandle, 593 retRecordSetIdentifier, retRecordType, 594 retFieldType); 595 if (rc != PLDM_SUCCESS) 596 { 597 return ccOnlyResponse(request, rc); 598 } 599 600 auto respPayloadLength = 601 PLDM_GET_FRU_RECORD_BY_OPTION_MIN_RESP_BYTES + fruData.size(); 602 Response response(sizeof(pldm_msg_hdr) + respPayloadLength, 0); 603 auto responsePtr = new (response.data()) pldm_msg; 604 605 rc = encode_get_fru_record_by_option_resp( 606 request->hdr.instance_id, PLDM_SUCCESS, 0, PLDM_START_AND_END, 607 fruData.data(), fruData.size(), responsePtr, respPayloadLength); 608 609 if (rc != PLDM_SUCCESS) 610 { 611 return ccOnlyResponse(request, rc); 612 } 613 614 return response; 615 } 616 617 Response Handler::setFRURecordTable(const pldm_msg* request, 618 size_t payloadLength) 619 { 620 uint32_t transferHandle{}; 621 uint8_t transferOpFlag{}; 622 struct variable_field fruData; 623 624 auto rc = decode_set_fru_record_table_req( 625 request, payloadLength, &transferHandle, &transferOpFlag, &fruData); 626 627 if (rc != PLDM_SUCCESS) 628 { 629 return ccOnlyResponse(request, rc); 630 } 631 632 Table table(fruData.ptr, fruData.ptr + fruData.length); 633 rc = impl.setFRUTable(table); 634 if (rc != PLDM_SUCCESS) 635 { 636 return ccOnlyResponse(request, rc); 637 } 638 639 Response response( 640 sizeof(pldm_msg_hdr) + PLDM_SET_FRU_RECORD_TABLE_RESP_BYTES); 641 struct pldm_msg* responsePtr = new (response.data()) pldm_msg; 642 643 rc = encode_set_fru_record_table_resp( 644 request->hdr.instance_id, PLDM_SUCCESS, 0 /* nextDataTransferHandle */, 645 response.size() - sizeof(pldm_msg_hdr), responsePtr); 646 647 if (rc != PLDM_SUCCESS) 648 { 649 return ccOnlyResponse(request, rc); 650 } 651 652 return response; 653 } 654 655 } // namespace fru 656 657 } // namespace responder 658 659 } // namespace pldm 660