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 <iostream> 13 #include <optional> 14 #include <set> 15 #include <stack> 16 17 PHOSPHOR_LOG2_USING; 18 19 namespace pldm 20 { 21 namespace responder 22 { 23 24 constexpr auto root = "/xyz/openbmc_project/inventory/"; 25 26 std::optional<pldm_entity> 27 FruImpl::getEntityByObjectPath(const dbus::InterfaceMap& intfMaps) 28 { 29 for (const auto& intfMap : intfMaps) 30 { 31 try 32 { 33 pldm_entity entity{}; 34 entity.entity_type = parser.getEntityType(intfMap.first); 35 return entity; 36 } 37 catch (const std::exception& e) 38 { 39 continue; 40 } 41 } 42 43 return std::nullopt; 44 } 45 46 void FruImpl::updateAssociationTree(const dbus::ObjectValueTree& objects, 47 const std::string& path) 48 { 49 if (path.find(root) == std::string::npos) 50 { 51 return; 52 } 53 54 std::stack<std::string> tmpObjPaths{}; 55 tmpObjPaths.emplace(path); 56 57 auto obj = pldm::utils::findParent(path); 58 while ((obj + '/') != root) 59 { 60 tmpObjPaths.emplace(obj); 61 obj = pldm::utils::findParent(obj); 62 } 63 64 std::stack<std::string> tmpObj = tmpObjPaths; 65 while (!tmpObj.empty()) 66 { 67 std::string s = tmpObj.top(); 68 info("{TMP_OBJ_STR}", "TMP_OBJ_PATH", s); 69 tmpObj.pop(); 70 } 71 // Update pldm entity to assocition tree 72 std::string prePath = tmpObjPaths.top(); 73 while (!tmpObjPaths.empty()) 74 { 75 std::string currPath = tmpObjPaths.top(); 76 tmpObjPaths.pop(); 77 78 do 79 { 80 if (objToEntityNode.contains(currPath)) 81 { 82 pldm_entity node = 83 pldm_entity_extract(objToEntityNode.at(currPath)); 84 if (pldm_entity_association_tree_find_with_locality( 85 entityTree, &node, false)) 86 { 87 break; 88 } 89 } 90 else 91 { 92 if (!objects.contains(currPath)) 93 { 94 break; 95 } 96 97 auto entityPtr = getEntityByObjectPath(objects.at(currPath)); 98 if (!entityPtr) 99 { 100 break; 101 } 102 103 pldm_entity entity = *entityPtr; 104 105 for (auto& it : objToEntityNode) 106 { 107 pldm_entity node = pldm_entity_extract(it.second); 108 if (node.entity_type == entity.entity_type) 109 { 110 entity.entity_instance_num = node.entity_instance_num + 111 1; 112 break; 113 } 114 } 115 116 if (currPath == prePath) 117 { 118 auto node = pldm_entity_association_tree_add_entity( 119 entityTree, &entity, 0xFFFF, nullptr, 120 PLDM_ENTITY_ASSOCIAION_PHYSICAL, false, true, 0xFFFF); 121 objToEntityNode[currPath] = node; 122 } 123 else 124 { 125 if (objToEntityNode.contains(prePath)) 126 { 127 auto node = pldm_entity_association_tree_add_entity( 128 entityTree, &entity, 0xFFFF, 129 objToEntityNode[prePath], 130 PLDM_ENTITY_ASSOCIAION_PHYSICAL, false, true, 131 0xFFFF); 132 objToEntityNode[currPath] = node; 133 } 134 } 135 } 136 } while (0); 137 138 prePath = currPath; 139 } 140 } 141 142 void FruImpl::buildFRUTable() 143 { 144 if (isBuilt) 145 { 146 return; 147 } 148 149 fru_parser::DBusLookupInfo dbusInfo; 150 // Read the all the inventory D-Bus objects 151 auto& bus = pldm::utils::DBusHandler::getBus(); 152 dbus::ObjectValueTree objects; 153 154 try 155 { 156 dbusInfo = parser.inventoryLookup(); 157 auto method = bus.new_method_call( 158 std::get<0>(dbusInfo).c_str(), std::get<1>(dbusInfo).c_str(), 159 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 160 auto reply = bus.call(method, dbusTimeout); 161 reply.read(objects); 162 } 163 catch (const std::exception& e) 164 { 165 error( 166 "Look up of inventory objects failed and PLDM FRU table creation failed"); 167 return; 168 } 169 170 auto itemIntfsLookup = std::get<2>(dbusInfo); 171 172 for (const auto& object : objects) 173 { 174 const auto& interfaces = object.second; 175 bool isPresent = pldm::utils::checkForFruPresence(object.first.str); 176 // Do not create fru record if fru is not present. 177 // Pick up the next available fru. 178 if (!isPresent) 179 { 180 continue; 181 } 182 for (const auto& interface : interfaces) 183 { 184 if (itemIntfsLookup.find(interface.first) != itemIntfsLookup.end()) 185 { 186 // An exception will be thrown by getRecordInfo, if the item 187 // D-Bus interface name specified in FRU_Master.json does 188 // not have corresponding config jsons 189 try 190 { 191 updateAssociationTree(objects, object.first.str); 192 pldm_entity entity{}; 193 if (objToEntityNode.contains(object.first.str)) 194 { 195 pldm_entity_node* node = 196 objToEntityNode.at(object.first.str); 197 198 entity = pldm_entity_extract(node); 199 } 200 201 auto recordInfos = parser.getRecordInfo(interface.first); 202 populateRecords(interfaces, recordInfos, entity); 203 204 associatedEntityMap.emplace(object.first, entity); 205 break; 206 } 207 catch (const std::exception& e) 208 { 209 error( 210 "Config JSONs missing for the item interface type, interface = {INTF}", 211 "INTF", interface.first); 212 break; 213 } 214 } 215 } 216 } 217 218 int rc = pldm_entity_association_pdr_add_check(entityTree, pdrRepo, false, 219 TERMINUS_HANDLE); 220 if (rc < 0) 221 { 222 // pldm_entity_assocation_pdr_add() assert()ed on failure 223 error("Failed to add PLDM entity association PDR: {LIBPLDM_ERROR}", 224 "LIBPLDM_ERROR", rc); 225 throw std::runtime_error("Failed to add PLDM entity association PDR"); 226 } 227 228 // save a copy of bmc's entity association tree 229 pldm_entity_association_tree_copy_root(entityTree, bmcEntityTree); 230 231 if (table.size()) 232 { 233 padBytes = pldm::utils::getNumPadBytes(table.size()); 234 table.resize(table.size() + padBytes, 0); 235 236 // Calculate the checksum 237 checksum = crc32(table.data(), table.size()); 238 } 239 isBuilt = true; 240 } 241 std::string FruImpl::populatefwVersion() 242 { 243 static constexpr auto fwFunctionalObjPath = 244 "/xyz/openbmc_project/software/functional"; 245 auto& bus = pldm::utils::DBusHandler::getBus(); 246 std::string currentBmcVersion; 247 try 248 { 249 auto method = bus.new_method_call(pldm::utils::mapperService, 250 fwFunctionalObjPath, 251 pldm::utils::dbusProperties, "Get"); 252 method.append("xyz.openbmc_project.Association", "endpoints"); 253 std::variant<std::vector<std::string>> paths; 254 auto reply = bus.call(method, dbusTimeout); 255 reply.read(paths); 256 auto fwRunningVersion = std::get<std::vector<std::string>>(paths)[0]; 257 constexpr auto versionIntf = "xyz.openbmc_project.Software.Version"; 258 auto version = pldm::utils::DBusHandler().getDbusPropertyVariant( 259 fwRunningVersion.c_str(), "Version", versionIntf); 260 currentBmcVersion = std::get<std::string>(version); 261 } 262 catch (const std::exception& e) 263 { 264 error("failed to make a d-bus call Asociation, ERROR= {ERR_EXCEP}", 265 "ERR_EXCEP", e.what()); 266 return {}; 267 } 268 return currentBmcVersion; 269 } 270 void FruImpl::populateRecords( 271 const pldm::responder::dbus::InterfaceMap& interfaces, 272 const fru_parser::FruRecordInfos& recordInfos, const pldm_entity& entity) 273 { 274 // recordSetIdentifier for the FRU will be set when the first record gets 275 // added for the FRU 276 uint16_t recordSetIdentifier = 0; 277 auto numRecsCount = numRecs; 278 static uint32_t bmc_record_handle = 0; 279 280 for (const auto& [recType, encType, fieldInfos] : recordInfos) 281 { 282 std::vector<uint8_t> tlvs; 283 uint8_t numFRUFields = 0; 284 for (const auto& [intf, prop, propType, fieldTypeNum] : fieldInfos) 285 { 286 try 287 { 288 pldm::responder::dbus::Value propValue; 289 290 // Assuming that 0 container Id is assigned to the System (as 291 // that should be the top most container as per dbus hierarchy) 292 if (entity.entity_container_id == 0 && prop == "Version") 293 { 294 propValue = populatefwVersion(); 295 } 296 else 297 { 298 propValue = interfaces.at(intf).at(prop); 299 } 300 if (propType == "bytearray") 301 { 302 auto byteArray = std::get<std::vector<uint8_t>>(propValue); 303 if (!byteArray.size()) 304 { 305 continue; 306 } 307 308 numFRUFields++; 309 tlvs.emplace_back(fieldTypeNum); 310 tlvs.emplace_back(byteArray.size()); 311 std::move(std::begin(byteArray), std::end(byteArray), 312 std::back_inserter(tlvs)); 313 } 314 else if (propType == "string") 315 { 316 auto str = std::get<std::string>(propValue); 317 if (!str.size()) 318 { 319 continue; 320 } 321 322 numFRUFields++; 323 tlvs.emplace_back(fieldTypeNum); 324 tlvs.emplace_back(str.size()); 325 std::move(std::begin(str), std::end(str), 326 std::back_inserter(tlvs)); 327 } 328 } 329 catch (const std::out_of_range& e) 330 { 331 continue; 332 } 333 } 334 335 if (tlvs.size()) 336 { 337 if (numRecs == numRecsCount) 338 { 339 recordSetIdentifier = nextRSI(); 340 bmc_record_handle = nextRecordHandle(); 341 int rc = pldm_pdr_add_fru_record_set_check( 342 pdrRepo, TERMINUS_HANDLE, recordSetIdentifier, 343 entity.entity_type, entity.entity_instance_num, 344 entity.entity_container_id, &bmc_record_handle); 345 if (rc) 346 { 347 // pldm_pdr_add_fru_record_set() assert()ed on failure 348 throw std::runtime_error( 349 "Failed to add PDR FRU record set"); 350 } 351 } 352 auto curSize = table.size(); 353 table.resize(curSize + recHeaderSize + tlvs.size()); 354 encode_fru_record(table.data(), table.size(), &curSize, 355 recordSetIdentifier, recType, numFRUFields, 356 encType, tlvs.data(), tlvs.size()); 357 numRecs++; 358 } 359 } 360 } 361 362 void FruImpl::getFRUTable(Response& response) 363 { 364 auto hdrSize = response.size(); 365 366 response.resize(hdrSize + table.size() + sizeof(checksum), 0); 367 std::copy(table.begin(), table.end(), response.begin() + hdrSize); 368 369 // Copy the checksum to response data 370 auto iter = response.begin() + hdrSize + table.size(); 371 std::copy_n(reinterpret_cast<const uint8_t*>(&checksum), sizeof(checksum), 372 iter); 373 } 374 375 int FruImpl::getFRURecordByOption(std::vector<uint8_t>& fruData, 376 uint16_t /* fruTableHandle */, 377 uint16_t recordSetIdentifer, 378 uint8_t recordType, uint8_t fieldType) 379 { 380 using sum = uint32_t; 381 382 // FRU table is built lazily, build if not done. 383 buildFRUTable(); 384 385 /* 7 is sizeof(checksum,4) + padBytesMax(3) 386 * We can not know size of the record table got by options in advance, but 387 * it must be less than the source table. So it's safe to use sizeof the 388 * source table + 7 as the buffer length 389 */ 390 size_t recordTableSize = table.size() - padBytes + 7; 391 fruData.resize(recordTableSize, 0); 392 393 int rc = get_fru_record_by_option_check( 394 table.data(), table.size() - padBytes, fruData.data(), &recordTableSize, 395 recordSetIdentifer, recordType, fieldType); 396 397 if (rc != PLDM_SUCCESS || recordTableSize == 0) 398 { 399 return PLDM_FRU_DATA_STRUCTURE_TABLE_UNAVAILABLE; 400 } 401 402 auto pads = pldm::utils::getNumPadBytes(recordTableSize); 403 crc32(fruData.data(), recordTableSize + pads); 404 405 auto iter = fruData.begin() + recordTableSize + pads; 406 std::copy_n(reinterpret_cast<const uint8_t*>(&checksum), sizeof(checksum), 407 iter); 408 fruData.resize(recordTableSize + pads + sizeof(sum)); 409 410 return PLDM_SUCCESS; 411 } 412 413 namespace fru 414 { 415 Response Handler::getFRURecordTableMetadata(const pldm_msg* request, 416 size_t /*payloadLength*/) 417 { 418 // FRU table is built lazily, build if not done. 419 buildFRUTable(); 420 421 constexpr uint8_t major = 0x01; 422 constexpr uint8_t minor = 0x00; 423 constexpr uint32_t maxSize = 0xFFFFFFFF; 424 425 Response response(sizeof(pldm_msg_hdr) + 426 PLDM_GET_FRU_RECORD_TABLE_METADATA_RESP_BYTES, 427 0); 428 auto responsePtr = reinterpret_cast<pldm_msg*>(response.data()); 429 430 auto rc = encode_get_fru_record_table_metadata_resp( 431 request->hdr.instance_id, PLDM_SUCCESS, major, minor, maxSize, 432 impl.size(), impl.numRSI(), impl.numRecords(), impl.checkSum(), 433 responsePtr); 434 if (rc != PLDM_SUCCESS) 435 { 436 return ccOnlyResponse(request, rc); 437 } 438 439 return response; 440 } 441 442 Response Handler::getFRURecordTable(const pldm_msg* request, 443 size_t payloadLength) 444 { 445 // FRU table is built lazily, build if not done. 446 buildFRUTable(); 447 448 if (payloadLength != PLDM_GET_FRU_RECORD_TABLE_REQ_BYTES) 449 { 450 return ccOnlyResponse(request, PLDM_ERROR_INVALID_LENGTH); 451 } 452 453 Response response( 454 sizeof(pldm_msg_hdr) + PLDM_GET_FRU_RECORD_TABLE_MIN_RESP_BYTES, 0); 455 auto responsePtr = reinterpret_cast<pldm_msg*>(response.data()); 456 457 auto rc = encode_get_fru_record_table_resp(request->hdr.instance_id, 458 PLDM_SUCCESS, 0, 459 PLDM_START_AND_END, responsePtr); 460 if (rc != PLDM_SUCCESS) 461 { 462 return ccOnlyResponse(request, rc); 463 } 464 465 impl.getFRUTable(response); 466 467 return response; 468 } 469 470 Response Handler::getFRURecordByOption(const pldm_msg* request, 471 size_t payloadLength) 472 { 473 if (payloadLength != sizeof(pldm_get_fru_record_by_option_req)) 474 { 475 return ccOnlyResponse(request, PLDM_ERROR_INVALID_LENGTH); 476 } 477 478 uint32_t retDataTransferHandle{}; 479 uint16_t retFruTableHandle{}; 480 uint16_t retRecordSetIdentifier{}; 481 uint8_t retRecordType{}; 482 uint8_t retFieldType{}; 483 uint8_t retTransferOpFlag{}; 484 485 auto rc = decode_get_fru_record_by_option_req( 486 request, payloadLength, &retDataTransferHandle, &retFruTableHandle, 487 &retRecordSetIdentifier, &retRecordType, &retFieldType, 488 &retTransferOpFlag); 489 490 if (rc != PLDM_SUCCESS) 491 { 492 return ccOnlyResponse(request, rc); 493 } 494 495 std::vector<uint8_t> fruData; 496 rc = impl.getFRURecordByOption(fruData, retFruTableHandle, 497 retRecordSetIdentifier, retRecordType, 498 retFieldType); 499 if (rc != PLDM_SUCCESS) 500 { 501 return ccOnlyResponse(request, rc); 502 } 503 504 auto respPayloadLength = PLDM_GET_FRU_RECORD_BY_OPTION_MIN_RESP_BYTES + 505 fruData.size(); 506 Response response(sizeof(pldm_msg_hdr) + respPayloadLength, 0); 507 auto responsePtr = reinterpret_cast<pldm_msg*>(response.data()); 508 509 rc = encode_get_fru_record_by_option_resp( 510 request->hdr.instance_id, PLDM_SUCCESS, 0, PLDM_START_AND_END, 511 fruData.data(), fruData.size(), responsePtr, respPayloadLength); 512 513 if (rc != PLDM_SUCCESS) 514 { 515 return ccOnlyResponse(request, rc); 516 } 517 518 return response; 519 } 520 521 } // namespace fru 522 523 } // namespace responder 524 525 } // namespace pldm 526