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> 26 FruImpl::getEntityByObjectPath(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 assocition 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 (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 = node.entity_instance_num + 109 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 // Read the all the inventory D-Bus objects 149 auto& bus = pldm::utils::DBusHandler::getBus(); 150 dbus::ObjectValueTree objects; 151 152 try 153 { 154 dbusInfo = parser.inventoryLookup(); 155 auto method = bus.new_method_call( 156 std::get<0>(dbusInfo).c_str(), std::get<1>(dbusInfo).c_str(), 157 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 158 auto reply = bus.call(method, dbusTimeout); 159 reply.read(objects); 160 } 161 catch (const std::exception& e) 162 { 163 error("Failed building FRU table due to inventory lookup: {ERROR}", 164 "ERROR", e); 165 return; 166 } 167 168 auto itemIntfsLookup = std::get<2>(dbusInfo); 169 170 for (const auto& object : objects) 171 { 172 const auto& interfaces = object.second; 173 for (const auto& interface : interfaces) 174 { 175 if (itemIntfsLookup.contains(interface.first)) 176 { 177 // checking fru present property is available or not. 178 if (!pldm::utils::checkForFruPresence(object.first.str)) 179 { 180 continue; 181 } 182 183 // An exception will be thrown by getRecordInfo, if the item 184 // D-Bus interface name specified in FRU_Master.json does 185 // not have corresponding config jsons 186 try 187 { 188 updateAssociationTree(objects, object.first.str); 189 pldm_entity entity{}; 190 if (objToEntityNode.contains(object.first.str)) 191 { 192 pldm_entity_node* node = 193 objToEntityNode.at(object.first.str); 194 195 entity = pldm_entity_extract(node); 196 } 197 198 auto recordInfos = parser.getRecordInfo(interface.first); 199 populateRecords(interfaces, recordInfos, entity); 200 201 associatedEntityMap.emplace(object.first, entity); 202 break; 203 } 204 catch (const std::exception& e) 205 { 206 error( 207 "Config JSONs missing for the item interface type, interface = {INTF}", 208 "INTF", interface.first); 209 break; 210 } 211 } 212 } 213 } 214 215 int rc = pldm_entity_association_pdr_add_check(entityTree, pdrRepo, false, 216 TERMINUS_HANDLE); 217 if (rc < 0) 218 { 219 // pldm_entity_assocation_pdr_add() assert()ed on failure 220 error("Failed to add PLDM entity association PDR: {LIBPLDM_ERROR}", 221 "LIBPLDM_ERROR", rc); 222 throw std::runtime_error("Failed to add PLDM entity association PDR"); 223 } 224 225 // save a copy of bmc's entity association tree 226 pldm_entity_association_tree_copy_root(entityTree, bmcEntityTree); 227 228 isBuilt = true; 229 } 230 std::string FruImpl::populatefwVersion() 231 { 232 static constexpr auto fwFunctionalObjPath = 233 "/xyz/openbmc_project/software/functional"; 234 auto& bus = pldm::utils::DBusHandler::getBus(); 235 std::string currentBmcVersion; 236 try 237 { 238 auto method = bus.new_method_call(pldm::utils::mapperService, 239 fwFunctionalObjPath, 240 pldm::utils::dbusProperties, "Get"); 241 method.append("xyz.openbmc_project.Association", "endpoints"); 242 std::variant<std::vector<std::string>> paths; 243 auto reply = bus.call(method, dbusTimeout); 244 reply.read(paths); 245 auto fwRunningVersion = std::get<std::vector<std::string>>(paths)[0]; 246 constexpr auto versionIntf = "xyz.openbmc_project.Software.Version"; 247 auto version = pldm::utils::DBusHandler().getDbusPropertyVariant( 248 fwRunningVersion.c_str(), "Version", versionIntf); 249 currentBmcVersion = std::get<std::string>(version); 250 } 251 catch (const std::exception& e) 252 { 253 error("failed to make a d-bus call Asociation, ERROR= {ERR_EXCEP}", 254 "ERR_EXCEP", e.what()); 255 return {}; 256 } 257 return currentBmcVersion; 258 } 259 void FruImpl::populateRecords( 260 const pldm::responder::dbus::InterfaceMap& interfaces, 261 const fru_parser::FruRecordInfos& recordInfos, const pldm_entity& entity) 262 { 263 // recordSetIdentifier for the FRU will be set when the first record gets 264 // added for the FRU 265 uint16_t recordSetIdentifier = 0; 266 auto numRecsCount = numRecs; 267 static uint32_t bmc_record_handle = 0; 268 269 for (const auto& [recType, encType, fieldInfos] : recordInfos) 270 { 271 std::vector<uint8_t> tlvs; 272 uint8_t numFRUFields = 0; 273 for (const auto& [intf, prop, propType, fieldTypeNum] : fieldInfos) 274 { 275 try 276 { 277 pldm::responder::dbus::Value propValue; 278 279 // Assuming that 0 container Id is assigned to the System (as 280 // that should be the top most container as per dbus hierarchy) 281 if (entity.entity_container_id == 0 && prop == "Version") 282 { 283 propValue = populatefwVersion(); 284 } 285 else 286 { 287 propValue = interfaces.at(intf).at(prop); 288 } 289 if (propType == "bytearray") 290 { 291 auto byteArray = std::get<std::vector<uint8_t>>(propValue); 292 if (!byteArray.size()) 293 { 294 continue; 295 } 296 297 numFRUFields++; 298 tlvs.emplace_back(fieldTypeNum); 299 tlvs.emplace_back(byteArray.size()); 300 std::move(std::begin(byteArray), std::end(byteArray), 301 std::back_inserter(tlvs)); 302 } 303 else if (propType == "string") 304 { 305 auto str = std::get<std::string>(propValue); 306 if (!str.size()) 307 { 308 continue; 309 } 310 311 numFRUFields++; 312 tlvs.emplace_back(fieldTypeNum); 313 tlvs.emplace_back(str.size()); 314 std::move(std::begin(str), std::end(str), 315 std::back_inserter(tlvs)); 316 } 317 } 318 catch (const std::out_of_range&) 319 { 320 continue; 321 } 322 } 323 324 if (tlvs.size()) 325 { 326 if (numRecs == numRecsCount) 327 { 328 recordSetIdentifier = nextRSI(); 329 bmc_record_handle = nextRecordHandle(); 330 int rc = pldm_pdr_add_fru_record_set_check( 331 pdrRepo, TERMINUS_HANDLE, recordSetIdentifier, 332 entity.entity_type, entity.entity_instance_num, 333 entity.entity_container_id, &bmc_record_handle); 334 if (rc) 335 { 336 // pldm_pdr_add_fru_record_set() assert()ed on failure 337 throw std::runtime_error( 338 "Failed to add PDR FRU record set"); 339 } 340 } 341 auto curSize = table.size(); 342 table.resize(curSize + recHeaderSize + tlvs.size()); 343 encode_fru_record(table.data(), table.size(), &curSize, 344 recordSetIdentifier, recType, numFRUFields, 345 encType, tlvs.data(), tlvs.size()); 346 numRecs++; 347 } 348 } 349 } 350 351 std::vector<uint8_t> FruImpl::tableResize() 352 { 353 std::vector<uint8_t> tempTable; 354 355 if (table.size()) 356 { 357 std::copy(table.begin(), table.end(), std::back_inserter(tempTable)); 358 padBytes = pldm::utils::getNumPadBytes(table.size()); 359 tempTable.resize(tempTable.size() + padBytes, 0); 360 } 361 return tempTable; 362 } 363 364 void FruImpl::getFRUTable(Response& response) 365 { 366 auto hdrSize = response.size(); 367 std::vector<uint8_t> tempTable; 368 369 if (table.size()) 370 { 371 tempTable = tableResize(); 372 checksum = crc32(tempTable.data(), tempTable.size()); 373 } 374 response.resize(hdrSize + tempTable.size() + sizeof(checksum), 0); 375 std::copy(tempTable.begin(), tempTable.end(), response.begin() + hdrSize); 376 377 // Copy the checksum to response data 378 auto iter = response.begin() + hdrSize + tempTable.size(); 379 std::copy_n(reinterpret_cast<const uint8_t*>(&checksum), sizeof(checksum), 380 iter); 381 } 382 383 void FruImpl::getFRURecordTableMetadata() 384 { 385 std::vector<uint8_t> tempTable; 386 if (table.size()) 387 { 388 tempTable = tableResize(); 389 checksum = crc32(tempTable.data(), tempTable.size()); 390 } 391 } 392 393 int FruImpl::getFRURecordByOption(std::vector<uint8_t>& fruData, 394 uint16_t /* fruTableHandle */, 395 uint16_t recordSetIdentifer, 396 uint8_t recordType, uint8_t fieldType) 397 { 398 using sum = uint32_t; 399 400 // FRU table is built lazily, build if not done. 401 buildFRUTable(); 402 403 /* 7 is sizeof(checksum,4) + padBytesMax(3) 404 * We can not know size of the record table got by options in advance, but 405 * it must be less than the source table. So it's safe to use sizeof the 406 * source table + 7 as the buffer length 407 */ 408 size_t recordTableSize = table.size() - padBytes + 7; 409 fruData.resize(recordTableSize, 0); 410 411 int rc = get_fru_record_by_option_check( 412 table.data(), table.size() - padBytes, fruData.data(), &recordTableSize, 413 recordSetIdentifer, recordType, fieldType); 414 415 if (rc != PLDM_SUCCESS || recordTableSize == 0) 416 { 417 return PLDM_FRU_DATA_STRUCTURE_TABLE_UNAVAILABLE; 418 } 419 420 auto pads = pldm::utils::getNumPadBytes(recordTableSize); 421 crc32(fruData.data(), recordTableSize + pads); 422 423 auto iter = fruData.begin() + recordTableSize + pads; 424 std::copy_n(reinterpret_cast<const uint8_t*>(&checksum), sizeof(checksum), 425 iter); 426 fruData.resize(recordTableSize + pads + sizeof(sum)); 427 428 return PLDM_SUCCESS; 429 } 430 431 int FruImpl::setFRUTable(const std::vector<uint8_t>& fruData) 432 { 433 auto record = 434 reinterpret_cast<const pldm_fru_record_data_format*>(fruData.data()); 435 if (record) 436 { 437 if (oemFruHandler && record->record_type == PLDM_FRU_RECORD_TYPE_OEM) 438 { 439 auto rc = oemFruHandler->processOEMFRUTable(fruData); 440 if (!rc) 441 { 442 return PLDM_SUCCESS; 443 } 444 } 445 } 446 return PLDM_ERROR_UNSUPPORTED_PLDM_CMD; 447 } 448 449 namespace fru 450 { 451 Response Handler::getFRURecordTableMetadata(const pldm_msg* request, 452 size_t /*payloadLength*/) 453 { 454 // FRU table is built lazily, build if not done. 455 buildFRUTable(); 456 457 constexpr uint8_t major = 0x01; 458 constexpr uint8_t minor = 0x00; 459 constexpr uint32_t maxSize = 0xFFFFFFFF; 460 461 Response response(sizeof(pldm_msg_hdr) + 462 PLDM_GET_FRU_RECORD_TABLE_METADATA_RESP_BYTES, 463 0); 464 auto responsePtr = reinterpret_cast<pldm_msg*>(response.data()); 465 466 impl.getFRURecordTableMetadata(); 467 468 auto rc = encode_get_fru_record_table_metadata_resp( 469 request->hdr.instance_id, PLDM_SUCCESS, major, minor, maxSize, 470 impl.size(), impl.numRSI(), impl.numRecords(), impl.checkSum(), 471 responsePtr); 472 if (rc != PLDM_SUCCESS) 473 { 474 return ccOnlyResponse(request, rc); 475 } 476 477 return response; 478 } 479 480 Response Handler::getFRURecordTable(const pldm_msg* request, 481 size_t payloadLength) 482 { 483 // FRU table is built lazily, build if not done. 484 buildFRUTable(); 485 486 if (payloadLength != PLDM_GET_FRU_RECORD_TABLE_REQ_BYTES) 487 { 488 return ccOnlyResponse(request, PLDM_ERROR_INVALID_LENGTH); 489 } 490 491 Response response( 492 sizeof(pldm_msg_hdr) + PLDM_GET_FRU_RECORD_TABLE_MIN_RESP_BYTES, 0); 493 auto responsePtr = reinterpret_cast<pldm_msg*>(response.data()); 494 495 auto rc = encode_get_fru_record_table_resp(request->hdr.instance_id, 496 PLDM_SUCCESS, 0, 497 PLDM_START_AND_END, responsePtr); 498 if (rc != PLDM_SUCCESS) 499 { 500 return ccOnlyResponse(request, rc); 501 } 502 503 impl.getFRUTable(response); 504 505 return response; 506 } 507 508 Response Handler::getFRURecordByOption(const pldm_msg* request, 509 size_t payloadLength) 510 { 511 if (payloadLength != sizeof(pldm_get_fru_record_by_option_req)) 512 { 513 return ccOnlyResponse(request, PLDM_ERROR_INVALID_LENGTH); 514 } 515 516 uint32_t retDataTransferHandle{}; 517 uint16_t retFruTableHandle{}; 518 uint16_t retRecordSetIdentifier{}; 519 uint8_t retRecordType{}; 520 uint8_t retFieldType{}; 521 uint8_t retTransferOpFlag{}; 522 523 auto rc = decode_get_fru_record_by_option_req( 524 request, payloadLength, &retDataTransferHandle, &retFruTableHandle, 525 &retRecordSetIdentifier, &retRecordType, &retFieldType, 526 &retTransferOpFlag); 527 528 if (rc != PLDM_SUCCESS) 529 { 530 return ccOnlyResponse(request, rc); 531 } 532 533 std::vector<uint8_t> fruData; 534 rc = impl.getFRURecordByOption(fruData, retFruTableHandle, 535 retRecordSetIdentifier, retRecordType, 536 retFieldType); 537 if (rc != PLDM_SUCCESS) 538 { 539 return ccOnlyResponse(request, rc); 540 } 541 542 auto respPayloadLength = PLDM_GET_FRU_RECORD_BY_OPTION_MIN_RESP_BYTES + 543 fruData.size(); 544 Response response(sizeof(pldm_msg_hdr) + respPayloadLength, 0); 545 auto responsePtr = reinterpret_cast<pldm_msg*>(response.data()); 546 547 rc = encode_get_fru_record_by_option_resp( 548 request->hdr.instance_id, PLDM_SUCCESS, 0, PLDM_START_AND_END, 549 fruData.data(), fruData.size(), responsePtr, respPayloadLength); 550 551 if (rc != PLDM_SUCCESS) 552 { 553 return ccOnlyResponse(request, rc); 554 } 555 556 return response; 557 } 558 559 Response Handler::setFRURecordTable(const pldm_msg* request, 560 size_t payloadLength) 561 { 562 uint32_t transferHandle{}; 563 uint8_t transferOpFlag{}; 564 struct variable_field fruData; 565 566 auto rc = decode_set_fru_record_table_req( 567 request, payloadLength, &transferHandle, &transferOpFlag, &fruData); 568 569 if (rc != PLDM_SUCCESS) 570 { 571 return ccOnlyResponse(request, rc); 572 } 573 574 Table table(fruData.ptr, fruData.ptr + fruData.length); 575 rc = impl.setFRUTable(table); 576 if (rc != PLDM_SUCCESS) 577 { 578 return ccOnlyResponse(request, rc); 579 } 580 581 Response response(sizeof(pldm_msg_hdr) + 582 PLDM_SET_FRU_RECORD_TABLE_RESP_BYTES); 583 struct pldm_msg* responsePtr = reinterpret_cast<pldm_msg*>(response.data()); 584 585 rc = encode_set_fru_record_table_resp( 586 request->hdr.instance_id, PLDM_SUCCESS, 0 /* nextDataTransferHandle */, 587 response.size() - sizeof(pldm_msg_hdr), responsePtr); 588 589 if (rc != PLDM_SUCCESS) 590 { 591 return ccOnlyResponse(request, rc); 592 } 593 594 return response; 595 } 596 597 } // namespace fru 598 599 } // namespace responder 600 601 } // namespace pldm 602