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