xref: /openbmc/pldm/libpldmresponder/fru.cpp (revision 129dab1c4a6f5c268afdec8d6b4d812c1e8a0be7)
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 
getEntityByObjectPath(const dbus::InterfaceMap & intfMaps)25 std::optional<pldm_entity> FruImpl::getEntityByObjectPath(
26     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 
updateAssociationTree(const dbus::ObjectValueTree & objects,const std::string & path)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 
buildFRUTable()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 }
populatefwVersion()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 }
populateRecords(const pldm::responder::dbus::InterfaceMap & interfaces,const fru_parser::FruRecordInfos & recordInfos,const pldm_entity & entity)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 
deleteFRURecord(uint16_t rsi)346 void FruImpl::deleteFRURecord(uint16_t rsi)
347 {
348     std::vector<uint8_t> updatedFruTbl;
349     size_t pos = 0;
350 
351     while (pos < table.size())
352     {
353         // Ensure enough space for the record header
354         if ((table.size() - pos) < sizeof(struct pldm_fru_record_data_format))
355         {
356             // Log or handle corrupt/truncated record
357             error("Error: Incomplete FRU record header");
358             return;
359         }
360 
361         auto recordSetSrc =
362             reinterpret_cast<const struct pldm_fru_record_data_format*>(
363                 &table[pos]);
364 
365         size_t recordLen = sizeof(struct pldm_fru_record_data_format) -
366                            sizeof(struct pldm_fru_record_tlv);
367 
368         const struct pldm_fru_record_tlv* tlv = recordSetSrc->tlvs;
369 
370         for (uint8_t i = 0; i < recordSetSrc->num_fru_fields; ++i)
371         {
372             if ((table.size() - pos) < (recordLen + sizeof(*tlv)))
373             {
374                 error("Error: Incomplete TLV header");
375                 return;
376             }
377 
378             size_t len = sizeof(*tlv) - 1 + tlv->length;
379 
380             if ((table.size() - pos) < (recordLen + len))
381             {
382                 error("Error: Incomplete TLV data");
383                 return;
384             }
385 
386             recordLen += len;
387 
388             // Advance to next tlv
389             tlv = reinterpret_cast<const struct pldm_fru_record_tlv*>(
390                 reinterpret_cast<const uint8_t*>(tlv) + len);
391         }
392 
393         if ((le16toh(recordSetSrc->record_set_id) != rsi && rsi != 0))
394         {
395             std::copy(table.begin() + pos, table.begin() + pos + recordLen,
396                       std::back_inserter(updatedFruTbl));
397         }
398         else
399         {
400             // Deleted record
401             numRecs--;
402         }
403 
404         pos += recordLen;
405     }
406     // Replace the old table with the updated one
407     table = std::move(updatedFruTbl);
408 }
409 
tableResize()410 std::vector<uint8_t> FruImpl::tableResize()
411 {
412     std::vector<uint8_t> tempTable;
413 
414     if (table.size())
415     {
416         std::copy(table.begin(), table.end(), std::back_inserter(tempTable));
417         padBytes = pldm::utils::getNumPadBytes(table.size());
418         tempTable.resize(tempTable.size() + padBytes, 0);
419     }
420     return tempTable;
421 }
422 
getFRUTable(Response & response)423 void FruImpl::getFRUTable(Response& response)
424 {
425     auto hdrSize = response.size();
426     std::vector<uint8_t> tempTable;
427 
428     if (table.size())
429     {
430         tempTable = tableResize();
431         checksum = pldm_edac_crc32(tempTable.data(), tempTable.size());
432     }
433     response.resize(hdrSize + tempTable.size() + sizeof(checksum), 0);
434     std::copy(tempTable.begin(), tempTable.end(), response.begin() + hdrSize);
435 
436     // Copy the checksum to response data
437     auto iter = response.begin() + hdrSize + tempTable.size();
438     std::copy_n(reinterpret_cast<const uint8_t*>(&checksum), sizeof(checksum),
439                 iter);
440 }
441 
getFRURecordTableMetadata()442 void FruImpl::getFRURecordTableMetadata()
443 {
444     std::vector<uint8_t> tempTable;
445     if (table.size())
446     {
447         tempTable = tableResize();
448         checksum = pldm_edac_crc32(tempTable.data(), tempTable.size());
449     }
450 }
451 
getFRURecordByOption(std::vector<uint8_t> & fruData,uint16_t,uint16_t recordSetIdentifer,uint8_t recordType,uint8_t fieldType)452 int FruImpl::getFRURecordByOption(
453     std::vector<uint8_t>& fruData, uint16_t /* fruTableHandle */,
454     uint16_t recordSetIdentifer, uint8_t recordType, uint8_t fieldType)
455 {
456     using sum = uint32_t;
457 
458     // FRU table is built lazily, build if not done.
459     buildFRUTable();
460 
461     /* 7 is sizeof(checksum,4) + padBytesMax(3)
462      * We can not know size of the record table got by options in advance, but
463      * it must be less than the source table. So it's safe to use sizeof the
464      * source table + 7 as the buffer length
465      */
466     size_t recordTableSize = table.size() - padBytes + 7;
467     fruData.resize(recordTableSize, 0);
468 
469     int rc = get_fru_record_by_option(
470         table.data(), table.size() - padBytes, fruData.data(), &recordTableSize,
471         recordSetIdentifer, recordType, fieldType);
472 
473     if (rc != PLDM_SUCCESS || recordTableSize == 0)
474     {
475         return PLDM_FRU_DATA_STRUCTURE_TABLE_UNAVAILABLE;
476     }
477 
478     auto pads = pldm::utils::getNumPadBytes(recordTableSize);
479     pldm_edac_crc32(fruData.data(), recordTableSize + pads);
480 
481     auto iter = fruData.begin() + recordTableSize + pads;
482     std::copy_n(reinterpret_cast<const uint8_t*>(&checksum), sizeof(checksum),
483                 iter);
484     fruData.resize(recordTableSize + pads + sizeof(sum));
485 
486     return PLDM_SUCCESS;
487 }
488 
setFRUTable(const std::vector<uint8_t> & fruData)489 int FruImpl::setFRUTable(const std::vector<uint8_t>& fruData)
490 {
491     auto record =
492         reinterpret_cast<const pldm_fru_record_data_format*>(fruData.data());
493     if (record)
494     {
495         if (oemFruHandler && record->record_type == PLDM_FRU_RECORD_TYPE_OEM)
496         {
497             auto rc = oemFruHandler->processOEMFRUTable(fruData);
498             if (!rc)
499             {
500                 return PLDM_SUCCESS;
501             }
502         }
503     }
504     return PLDM_ERROR_UNSUPPORTED_PLDM_CMD;
505 }
506 
507 namespace fru
508 {
getFRURecordTableMetadata(const pldm_msg * request,size_t)509 Response Handler::getFRURecordTableMetadata(const pldm_msg* request,
510                                             size_t /*payloadLength*/)
511 {
512     // FRU table is built lazily, build if not done.
513     buildFRUTable();
514 
515     constexpr uint8_t major = 0x01;
516     constexpr uint8_t minor = 0x00;
517     constexpr uint32_t maxSize = 0xFFFFFFFF;
518 
519     Response response(sizeof(pldm_msg_hdr) +
520                           PLDM_GET_FRU_RECORD_TABLE_METADATA_RESP_BYTES,
521                       0);
522     auto responsePtr = new (response.data()) pldm_msg;
523 
524     impl.getFRURecordTableMetadata();
525 
526     auto rc = encode_get_fru_record_table_metadata_resp(
527         request->hdr.instance_id, PLDM_SUCCESS, major, minor, maxSize,
528         impl.size(), impl.numRSI(), impl.numRecords(), impl.checkSum(),
529         responsePtr);
530     if (rc != PLDM_SUCCESS)
531     {
532         return ccOnlyResponse(request, rc);
533     }
534 
535     return response;
536 }
537 
getFRURecordTable(const pldm_msg * request,size_t payloadLength)538 Response Handler::getFRURecordTable(const pldm_msg* request,
539                                     size_t payloadLength)
540 {
541     // FRU table is built lazily, build if not done.
542     buildFRUTable();
543 
544     if (payloadLength != PLDM_GET_FRU_RECORD_TABLE_REQ_BYTES)
545     {
546         return ccOnlyResponse(request, PLDM_ERROR_INVALID_LENGTH);
547     }
548 
549     Response response(
550         sizeof(pldm_msg_hdr) + PLDM_GET_FRU_RECORD_TABLE_MIN_RESP_BYTES, 0);
551     auto responsePtr = new (response.data()) pldm_msg;
552 
553     auto rc =
554         encode_get_fru_record_table_resp(request->hdr.instance_id, PLDM_SUCCESS,
555                                          0, PLDM_START_AND_END, responsePtr);
556     if (rc != PLDM_SUCCESS)
557     {
558         return ccOnlyResponse(request, rc);
559     }
560 
561     impl.getFRUTable(response);
562 
563     return response;
564 }
565 
getFRURecordByOption(const pldm_msg * request,size_t payloadLength)566 Response Handler::getFRURecordByOption(const pldm_msg* request,
567                                        size_t payloadLength)
568 {
569     if (payloadLength != sizeof(pldm_get_fru_record_by_option_req))
570     {
571         return ccOnlyResponse(request, PLDM_ERROR_INVALID_LENGTH);
572     }
573 
574     uint32_t retDataTransferHandle{};
575     uint16_t retFruTableHandle{};
576     uint16_t retRecordSetIdentifier{};
577     uint8_t retRecordType{};
578     uint8_t retFieldType{};
579     uint8_t retTransferOpFlag{};
580 
581     auto rc = decode_get_fru_record_by_option_req(
582         request, payloadLength, &retDataTransferHandle, &retFruTableHandle,
583         &retRecordSetIdentifier, &retRecordType, &retFieldType,
584         &retTransferOpFlag);
585 
586     if (rc != PLDM_SUCCESS)
587     {
588         return ccOnlyResponse(request, rc);
589     }
590 
591     std::vector<uint8_t> fruData;
592     rc = impl.getFRURecordByOption(fruData, retFruTableHandle,
593                                    retRecordSetIdentifier, retRecordType,
594                                    retFieldType);
595     if (rc != PLDM_SUCCESS)
596     {
597         return ccOnlyResponse(request, rc);
598     }
599 
600     auto respPayloadLength =
601         PLDM_GET_FRU_RECORD_BY_OPTION_MIN_RESP_BYTES + fruData.size();
602     Response response(sizeof(pldm_msg_hdr) + respPayloadLength, 0);
603     auto responsePtr = new (response.data()) pldm_msg;
604 
605     rc = encode_get_fru_record_by_option_resp(
606         request->hdr.instance_id, PLDM_SUCCESS, 0, PLDM_START_AND_END,
607         fruData.data(), fruData.size(), responsePtr, respPayloadLength);
608 
609     if (rc != PLDM_SUCCESS)
610     {
611         return ccOnlyResponse(request, rc);
612     }
613 
614     return response;
615 }
616 
setFRURecordTable(const pldm_msg * request,size_t payloadLength)617 Response Handler::setFRURecordTable(const pldm_msg* request,
618                                     size_t payloadLength)
619 {
620     uint32_t transferHandle{};
621     uint8_t transferOpFlag{};
622     struct variable_field fruData;
623 
624     auto rc = decode_set_fru_record_table_req(
625         request, payloadLength, &transferHandle, &transferOpFlag, &fruData);
626 
627     if (rc != PLDM_SUCCESS)
628     {
629         return ccOnlyResponse(request, rc);
630     }
631 
632     Table table(fruData.ptr, fruData.ptr + fruData.length);
633     rc = impl.setFRUTable(table);
634     if (rc != PLDM_SUCCESS)
635     {
636         return ccOnlyResponse(request, rc);
637     }
638 
639     Response response(
640         sizeof(pldm_msg_hdr) + PLDM_SET_FRU_RECORD_TABLE_RESP_BYTES);
641     struct pldm_msg* responsePtr = new (response.data()) pldm_msg;
642 
643     rc = encode_set_fru_record_table_resp(
644         request->hdr.instance_id, PLDM_SUCCESS, 0 /* nextDataTransferHandle */,
645         response.size() - sizeof(pldm_msg_hdr), responsePtr);
646 
647     if (rc != PLDM_SUCCESS)
648     {
649         return ccOnlyResponse(request, rc);
650     }
651 
652     return response;
653 }
654 
655 } // namespace fru
656 
657 } // namespace responder
658 
659 } // namespace pldm
660