1 #include "fru.hpp" 2 3 #include "libpldm/entity.h" 4 #include "libpldm/utils.h" 5 6 #include "common/utils.hpp" 7 8 #include <config.h> 9 #include <systemd/sd-journal.h> 10 11 #include <sdbusplus/bus.hpp> 12 13 #include <iostream> 14 #include <set> 15 16 namespace pldm 17 { 18 namespace responder 19 { 20 void FruImpl::buildFRUTable() 21 { 22 if (isBuilt) 23 { 24 return; 25 } 26 27 fru_parser::DBusLookupInfo dbusInfo; 28 // Read the all the inventory D-Bus objects 29 auto& bus = pldm::utils::DBusHandler::getBus(); 30 dbus::ObjectValueTree objects; 31 32 try 33 { 34 dbusInfo = parser.inventoryLookup(); 35 auto method = bus.new_method_call( 36 std::get<0>(dbusInfo).c_str(), std::get<1>(dbusInfo).c_str(), 37 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 38 auto reply = bus.call(method); 39 reply.read(objects); 40 } 41 catch (const std::exception& e) 42 { 43 std::cerr << "Look up of inventory objects failed and PLDM FRU table " 44 "creation failed\n"; 45 return; 46 } 47 48 auto itemIntfsLookup = std::get<2>(dbusInfo); 49 50 for (const auto& object : objects) 51 { 52 const auto& interfaces = object.second; 53 bool isPresent = pldm::utils::checkForFruPresence(object.first.str); 54 // Do not create fru record if fru is not present. 55 // Pick up the next available fru. 56 if (!isPresent) 57 { 58 continue; 59 } 60 for (const auto& interface : interfaces) 61 { 62 if (itemIntfsLookup.find(interface.first) != itemIntfsLookup.end()) 63 { 64 // An exception will be thrown by getRecordInfo, if the item 65 // D-Bus interface name specified in FRU_Master.json does 66 // not have corresponding config jsons 67 try 68 { 69 pldm_entity entity{}; 70 entity.entity_type = parser.getEntityType(interface.first); 71 pldm_entity_node* parent = nullptr; 72 auto parentObj = pldm::utils::findParent(object.first.str); 73 // To add a FRU to the entity association tree, we need to 74 // determine if the FRU has a parent (D-Bus object). For eg 75 // /system/backplane's parent is /system. /system has no 76 // parent. Some D-Bus pathnames might just be namespaces 77 // (not D-Bus objects), so we need to iterate upwards until 78 // a parent is found, or we reach the root ("/"). 79 // Parents are always added first before children in the 80 // entity association tree. We're relying on the fact that 81 // the std::map containing object paths from the 82 // GetManagedObjects call will have a sorted pathname list. 83 do 84 { 85 auto iter = objToEntityNode.find(parentObj); 86 if (iter != objToEntityNode.end()) 87 { 88 parent = iter->second; 89 break; 90 } 91 parentObj = pldm::utils::findParent(parentObj); 92 } while (parentObj != "/"); 93 94 auto node = pldm_entity_association_tree_add( 95 entityTree, &entity, 0xFFFF, parent, 96 PLDM_ENTITY_ASSOCIAION_PHYSICAL); 97 objToEntityNode[object.first.str] = node; 98 99 auto recordInfos = parser.getRecordInfo(interface.first); 100 populateRecords(interfaces, recordInfos, entity); 101 102 associatedEntityMap.emplace(object.first, entity); 103 break; 104 } 105 catch (const std::exception& e) 106 { 107 std::cout << "Config JSONs missing for the item " 108 "interface type, interface = " 109 << interface.first << "\n"; 110 break; 111 } 112 } 113 } 114 } 115 116 pldm_entity_association_pdr_add(entityTree, pdrRepo, false, 117 TERMINUS_HANDLE); 118 // save a copy of bmc's entity association tree 119 pldm_entity_association_tree_copy_root(entityTree, bmcEntityTree); 120 121 if (table.size()) 122 { 123 padBytes = pldm::utils::getNumPadBytes(table.size()); 124 table.resize(table.size() + padBytes, 0); 125 126 // Calculate the checksum 127 checksum = crc32(table.data(), table.size()); 128 } 129 isBuilt = true; 130 } 131 std::string FruImpl::populatefwVersion() 132 { 133 static constexpr auto fwFunctionalObjPath = 134 "/xyz/openbmc_project/software/functional"; 135 auto& bus = pldm::utils::DBusHandler::getBus(); 136 std::string currentBmcVersion; 137 try 138 { 139 auto method = 140 bus.new_method_call(pldm::utils::mapperService, fwFunctionalObjPath, 141 pldm::utils::dbusProperties, "Get"); 142 method.append("xyz.openbmc_project.Association", "endpoints"); 143 std::variant<std::vector<std::string>> paths; 144 auto reply = bus.call(method); 145 reply.read(paths); 146 auto fwRunningVersion = std::get<std::vector<std::string>>(paths)[0]; 147 constexpr auto versionIntf = "xyz.openbmc_project.Software.Version"; 148 auto version = pldm::utils::DBusHandler().getDbusPropertyVariant( 149 fwRunningVersion.c_str(), "Version", versionIntf); 150 currentBmcVersion = std::get<std::string>(version); 151 } 152 catch (const std::exception& e) 153 { 154 std::cerr << "failed to make a d-bus call " 155 "Asociation, ERROR= " 156 << e.what() << "\n"; 157 return {}; 158 } 159 return currentBmcVersion; 160 } 161 void FruImpl::populateRecords( 162 const pldm::responder::dbus::InterfaceMap& interfaces, 163 const fru_parser::FruRecordInfos& recordInfos, const pldm_entity& entity) 164 { 165 // recordSetIdentifier for the FRU will be set when the first record gets 166 // added for the FRU 167 uint16_t recordSetIdentifier = 0; 168 auto numRecsCount = numRecs; 169 static uint32_t bmc_record_handle = 0; 170 171 for (auto const& [recType, encType, fieldInfos] : recordInfos) 172 { 173 std::vector<uint8_t> tlvs; 174 uint8_t numFRUFields = 0; 175 for (auto const& [intf, prop, propType, fieldTypeNum] : fieldInfos) 176 { 177 try 178 { 179 pldm::responder::dbus::Value propValue; 180 181 // Assuming that 0 container Id is assigned to the System (as 182 // that should be the top most container as per dbus hierarchy) 183 if (entity.entity_container_id == 0 && prop == "Version") 184 { 185 propValue = populatefwVersion(); 186 } 187 else 188 { 189 propValue = interfaces.at(intf).at(prop); 190 } 191 if (propType == "bytearray") 192 { 193 auto byteArray = std::get<std::vector<uint8_t>>(propValue); 194 if (!byteArray.size()) 195 { 196 continue; 197 } 198 199 numFRUFields++; 200 tlvs.emplace_back(fieldTypeNum); 201 tlvs.emplace_back(byteArray.size()); 202 std::move(std::begin(byteArray), std::end(byteArray), 203 std::back_inserter(tlvs)); 204 } 205 else if (propType == "string") 206 { 207 auto str = std::get<std::string>(propValue); 208 if (!str.size()) 209 { 210 continue; 211 } 212 213 numFRUFields++; 214 tlvs.emplace_back(fieldTypeNum); 215 tlvs.emplace_back(str.size()); 216 std::move(std::begin(str), std::end(str), 217 std::back_inserter(tlvs)); 218 } 219 } 220 catch (const std::out_of_range& e) 221 { 222 continue; 223 } 224 } 225 226 if (tlvs.size()) 227 { 228 if (numRecs == numRecsCount) 229 { 230 recordSetIdentifier = nextRSI(); 231 bmc_record_handle = nextRecordHandle(); 232 pldm_pdr_add_fru_record_set( 233 pdrRepo, TERMINUS_HANDLE, recordSetIdentifier, 234 entity.entity_type, entity.entity_instance_num, 235 entity.entity_container_id, bmc_record_handle); 236 } 237 auto curSize = table.size(); 238 table.resize(curSize + recHeaderSize + tlvs.size()); 239 encode_fru_record(table.data(), table.size(), &curSize, 240 recordSetIdentifier, recType, numFRUFields, 241 encType, tlvs.data(), tlvs.size()); 242 numRecs++; 243 } 244 } 245 } 246 247 void FruImpl::getFRUTable(Response& response) 248 { 249 auto hdrSize = response.size(); 250 251 response.resize(hdrSize + table.size() + sizeof(checksum), 0); 252 std::copy(table.begin(), table.end(), response.begin() + hdrSize); 253 254 // Copy the checksum to response data 255 auto iter = response.begin() + hdrSize + table.size(); 256 std::copy_n(reinterpret_cast<const uint8_t*>(&checksum), sizeof(checksum), 257 iter); 258 } 259 260 int FruImpl::getFRURecordByOption(std::vector<uint8_t>& fruData, 261 uint16_t /* fruTableHandle */, 262 uint16_t recordSetIdentifer, 263 uint8_t recordType, uint8_t fieldType) 264 { 265 using sum = uint32_t; 266 267 // FRU table is built lazily, build if not done. 268 buildFRUTable(); 269 270 /* 7 is sizeof(checksum,4) + padBytesMax(3) 271 * We can not know size of the record table got by options in advance, but 272 * it must be less than the source table. So it's safe to use sizeof the 273 * source table + 7 as the buffer length 274 */ 275 size_t recordTableSize = table.size() - padBytes + 7; 276 fruData.resize(recordTableSize, 0); 277 278 get_fru_record_by_option(table.data(), table.size() - padBytes, 279 fruData.data(), &recordTableSize, 280 recordSetIdentifer, recordType, fieldType); 281 282 if (recordTableSize == 0) 283 { 284 return PLDM_FRU_DATA_STRUCTURE_TABLE_UNAVAILABLE; 285 } 286 287 auto pads = pldm::utils::getNumPadBytes(recordTableSize); 288 crc32(fruData.data(), recordTableSize + pads); 289 290 auto iter = fruData.begin() + recordTableSize + pads; 291 std::copy_n(reinterpret_cast<const uint8_t*>(&checksum), sizeof(checksum), 292 iter); 293 fruData.resize(recordTableSize + pads + sizeof(sum)); 294 295 return PLDM_SUCCESS; 296 } 297 298 namespace fru 299 { 300 Response Handler::getFRURecordTableMetadata(const pldm_msg* request, 301 size_t /*payloadLength*/) 302 { 303 // FRU table is built lazily, build if not done. 304 buildFRUTable(); 305 306 constexpr uint8_t major = 0x01; 307 constexpr uint8_t minor = 0x00; 308 constexpr uint32_t maxSize = 0xFFFFFFFF; 309 310 Response response(sizeof(pldm_msg_hdr) + 311 PLDM_GET_FRU_RECORD_TABLE_METADATA_RESP_BYTES, 312 0); 313 auto responsePtr = reinterpret_cast<pldm_msg*>(response.data()); 314 315 auto rc = encode_get_fru_record_table_metadata_resp( 316 request->hdr.instance_id, PLDM_SUCCESS, major, minor, maxSize, 317 impl.size(), impl.numRSI(), impl.numRecords(), impl.checkSum(), 318 responsePtr); 319 if (rc != PLDM_SUCCESS) 320 { 321 return ccOnlyResponse(request, rc); 322 } 323 324 return response; 325 } 326 327 Response Handler::getFRURecordTable(const pldm_msg* request, 328 size_t payloadLength) 329 { 330 // FRU table is built lazily, build if not done. 331 buildFRUTable(); 332 333 if (payloadLength != PLDM_GET_FRU_RECORD_TABLE_REQ_BYTES) 334 { 335 return ccOnlyResponse(request, PLDM_ERROR_INVALID_LENGTH); 336 } 337 338 Response response( 339 sizeof(pldm_msg_hdr) + PLDM_GET_FRU_RECORD_TABLE_MIN_RESP_BYTES, 0); 340 auto responsePtr = reinterpret_cast<pldm_msg*>(response.data()); 341 342 auto rc = 343 encode_get_fru_record_table_resp(request->hdr.instance_id, PLDM_SUCCESS, 344 0, PLDM_START_AND_END, responsePtr); 345 if (rc != PLDM_SUCCESS) 346 { 347 return ccOnlyResponse(request, rc); 348 } 349 350 impl.getFRUTable(response); 351 352 return response; 353 } 354 355 Response Handler::getFRURecordByOption(const pldm_msg* request, 356 size_t payloadLength) 357 { 358 if (payloadLength != sizeof(pldm_get_fru_record_by_option_req)) 359 { 360 return ccOnlyResponse(request, PLDM_ERROR_INVALID_LENGTH); 361 } 362 363 uint32_t retDataTransferHandle{}; 364 uint16_t retFruTableHandle{}; 365 uint16_t retRecordSetIdentifier{}; 366 uint8_t retRecordType{}; 367 uint8_t retFieldType{}; 368 uint8_t retTransferOpFlag{}; 369 370 auto rc = decode_get_fru_record_by_option_req( 371 request, payloadLength, &retDataTransferHandle, &retFruTableHandle, 372 &retRecordSetIdentifier, &retRecordType, &retFieldType, 373 &retTransferOpFlag); 374 375 if (rc != PLDM_SUCCESS) 376 { 377 return ccOnlyResponse(request, rc); 378 } 379 380 std::vector<uint8_t> fruData; 381 rc = impl.getFRURecordByOption(fruData, retFruTableHandle, 382 retRecordSetIdentifier, retRecordType, 383 retFieldType); 384 if (rc != PLDM_SUCCESS) 385 { 386 return ccOnlyResponse(request, rc); 387 } 388 389 auto respPayloadLength = 390 PLDM_GET_FRU_RECORD_BY_OPTION_MIN_RESP_BYTES + fruData.size(); 391 Response response(sizeof(pldm_msg_hdr) + respPayloadLength, 0); 392 auto responsePtr = reinterpret_cast<pldm_msg*>(response.data()); 393 394 rc = encode_get_fru_record_by_option_resp( 395 request->hdr.instance_id, PLDM_SUCCESS, 0, PLDM_START_AND_END, 396 fruData.data(), fruData.size(), responsePtr, respPayloadLength); 397 398 if (rc != PLDM_SUCCESS) 399 { 400 return ccOnlyResponse(request, rc); 401 } 402 403 return response; 404 } 405 406 } // namespace fru 407 408 } // namespace responder 409 410 } // namespace pldm 411