xref: /openbmc/pldm/libpldmresponder/fru.cpp (revision dc36d9b62a01f78f39e16877220fa7902b0f4128)
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