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 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 std::vector<uint8_t> FruImpl::tableResize() 347 { 348 std::vector<uint8_t> tempTable; 349 350 if (table.size()) 351 { 352 std::copy(table.begin(), table.end(), std::back_inserter(tempTable)); 353 padBytes = pldm::utils::getNumPadBytes(table.size()); 354 tempTable.resize(tempTable.size() + padBytes, 0); 355 } 356 return tempTable; 357 } 358 359 void FruImpl::getFRUTable(Response& response) 360 { 361 auto hdrSize = response.size(); 362 std::vector<uint8_t> tempTable; 363 364 if (table.size()) 365 { 366 tempTable = tableResize(); 367 checksum = crc32(tempTable.data(), tempTable.size()); 368 } 369 response.resize(hdrSize + tempTable.size() + sizeof(checksum), 0); 370 std::copy(tempTable.begin(), tempTable.end(), response.begin() + hdrSize); 371 372 // Copy the checksum to response data 373 auto iter = response.begin() + hdrSize + tempTable.size(); 374 std::copy_n(reinterpret_cast<const uint8_t*>(&checksum), sizeof(checksum), 375 iter); 376 } 377 378 void FruImpl::getFRURecordTableMetadata() 379 { 380 std::vector<uint8_t> tempTable; 381 if (table.size()) 382 { 383 tempTable = tableResize(); 384 checksum = crc32(tempTable.data(), tempTable.size()); 385 } 386 } 387 388 int FruImpl::getFRURecordByOption( 389 std::vector<uint8_t>& fruData, uint16_t /* fruTableHandle */, 390 uint16_t recordSetIdentifer, uint8_t recordType, uint8_t fieldType) 391 { 392 using sum = uint32_t; 393 394 // FRU table is built lazily, build if not done. 395 buildFRUTable(); 396 397 /* 7 is sizeof(checksum,4) + padBytesMax(3) 398 * We can not know size of the record table got by options in advance, but 399 * it must be less than the source table. So it's safe to use sizeof the 400 * source table + 7 as the buffer length 401 */ 402 size_t recordTableSize = table.size() - padBytes + 7; 403 fruData.resize(recordTableSize, 0); 404 405 int rc = get_fru_record_by_option( 406 table.data(), table.size() - padBytes, fruData.data(), &recordTableSize, 407 recordSetIdentifer, recordType, fieldType); 408 409 if (rc != PLDM_SUCCESS || recordTableSize == 0) 410 { 411 return PLDM_FRU_DATA_STRUCTURE_TABLE_UNAVAILABLE; 412 } 413 414 auto pads = pldm::utils::getNumPadBytes(recordTableSize); 415 crc32(fruData.data(), recordTableSize + pads); 416 417 auto iter = fruData.begin() + recordTableSize + pads; 418 std::copy_n(reinterpret_cast<const uint8_t*>(&checksum), sizeof(checksum), 419 iter); 420 fruData.resize(recordTableSize + pads + sizeof(sum)); 421 422 return PLDM_SUCCESS; 423 } 424 425 int FruImpl::setFRUTable(const std::vector<uint8_t>& fruData) 426 { 427 auto record = 428 reinterpret_cast<const pldm_fru_record_data_format*>(fruData.data()); 429 if (record) 430 { 431 if (oemFruHandler && record->record_type == PLDM_FRU_RECORD_TYPE_OEM) 432 { 433 auto rc = oemFruHandler->processOEMFRUTable(fruData); 434 if (!rc) 435 { 436 return PLDM_SUCCESS; 437 } 438 } 439 } 440 return PLDM_ERROR_UNSUPPORTED_PLDM_CMD; 441 } 442 443 namespace fru 444 { 445 Response Handler::getFRURecordTableMetadata(const pldm_msg* request, 446 size_t /*payloadLength*/) 447 { 448 // FRU table is built lazily, build if not done. 449 buildFRUTable(); 450 451 constexpr uint8_t major = 0x01; 452 constexpr uint8_t minor = 0x00; 453 constexpr uint32_t maxSize = 0xFFFFFFFF; 454 455 Response response(sizeof(pldm_msg_hdr) + 456 PLDM_GET_FRU_RECORD_TABLE_METADATA_RESP_BYTES, 457 0); 458 auto responsePtr = reinterpret_cast<pldm_msg*>(response.data()); 459 460 impl.getFRURecordTableMetadata(); 461 462 auto rc = encode_get_fru_record_table_metadata_resp( 463 request->hdr.instance_id, PLDM_SUCCESS, major, minor, maxSize, 464 impl.size(), impl.numRSI(), impl.numRecords(), impl.checkSum(), 465 responsePtr); 466 if (rc != PLDM_SUCCESS) 467 { 468 return ccOnlyResponse(request, rc); 469 } 470 471 return response; 472 } 473 474 Response Handler::getFRURecordTable(const pldm_msg* request, 475 size_t payloadLength) 476 { 477 // FRU table is built lazily, build if not done. 478 buildFRUTable(); 479 480 if (payloadLength != PLDM_GET_FRU_RECORD_TABLE_REQ_BYTES) 481 { 482 return ccOnlyResponse(request, PLDM_ERROR_INVALID_LENGTH); 483 } 484 485 Response response( 486 sizeof(pldm_msg_hdr) + PLDM_GET_FRU_RECORD_TABLE_MIN_RESP_BYTES, 0); 487 auto responsePtr = reinterpret_cast<pldm_msg*>(response.data()); 488 489 auto rc = 490 encode_get_fru_record_table_resp(request->hdr.instance_id, PLDM_SUCCESS, 491 0, PLDM_START_AND_END, responsePtr); 492 if (rc != PLDM_SUCCESS) 493 { 494 return ccOnlyResponse(request, rc); 495 } 496 497 impl.getFRUTable(response); 498 499 return response; 500 } 501 502 Response Handler::getFRURecordByOption(const pldm_msg* request, 503 size_t payloadLength) 504 { 505 if (payloadLength != sizeof(pldm_get_fru_record_by_option_req)) 506 { 507 return ccOnlyResponse(request, PLDM_ERROR_INVALID_LENGTH); 508 } 509 510 uint32_t retDataTransferHandle{}; 511 uint16_t retFruTableHandle{}; 512 uint16_t retRecordSetIdentifier{}; 513 uint8_t retRecordType{}; 514 uint8_t retFieldType{}; 515 uint8_t retTransferOpFlag{}; 516 517 auto rc = decode_get_fru_record_by_option_req( 518 request, payloadLength, &retDataTransferHandle, &retFruTableHandle, 519 &retRecordSetIdentifier, &retRecordType, &retFieldType, 520 &retTransferOpFlag); 521 522 if (rc != PLDM_SUCCESS) 523 { 524 return ccOnlyResponse(request, rc); 525 } 526 527 std::vector<uint8_t> fruData; 528 rc = impl.getFRURecordByOption(fruData, retFruTableHandle, 529 retRecordSetIdentifier, retRecordType, 530 retFieldType); 531 if (rc != PLDM_SUCCESS) 532 { 533 return ccOnlyResponse(request, rc); 534 } 535 536 auto respPayloadLength = 537 PLDM_GET_FRU_RECORD_BY_OPTION_MIN_RESP_BYTES + fruData.size(); 538 Response response(sizeof(pldm_msg_hdr) + respPayloadLength, 0); 539 auto responsePtr = reinterpret_cast<pldm_msg*>(response.data()); 540 541 rc = encode_get_fru_record_by_option_resp( 542 request->hdr.instance_id, PLDM_SUCCESS, 0, PLDM_START_AND_END, 543 fruData.data(), fruData.size(), responsePtr, respPayloadLength); 544 545 if (rc != PLDM_SUCCESS) 546 { 547 return ccOnlyResponse(request, rc); 548 } 549 550 return response; 551 } 552 553 Response Handler::setFRURecordTable(const pldm_msg* request, 554 size_t payloadLength) 555 { 556 uint32_t transferHandle{}; 557 uint8_t transferOpFlag{}; 558 struct variable_field fruData; 559 560 auto rc = decode_set_fru_record_table_req( 561 request, payloadLength, &transferHandle, &transferOpFlag, &fruData); 562 563 if (rc != PLDM_SUCCESS) 564 { 565 return ccOnlyResponse(request, rc); 566 } 567 568 Table table(fruData.ptr, fruData.ptr + fruData.length); 569 rc = impl.setFRUTable(table); 570 if (rc != PLDM_SUCCESS) 571 { 572 return ccOnlyResponse(request, rc); 573 } 574 575 Response response( 576 sizeof(pldm_msg_hdr) + PLDM_SET_FRU_RECORD_TABLE_RESP_BYTES); 577 struct pldm_msg* responsePtr = reinterpret_cast<pldm_msg*>(response.data()); 578 579 rc = encode_set_fru_record_table_resp( 580 request->hdr.instance_id, PLDM_SUCCESS, 0 /* nextDataTransferHandle */, 581 response.size() - sizeof(pldm_msg_hdr), responsePtr); 582 583 if (rc != PLDM_SUCCESS) 584 { 585 return ccOnlyResponse(request, rc); 586 } 587 588 return response; 589 } 590 591 } // namespace fru 592 593 } // namespace responder 594 595 } // namespace pldm 596