xref: /openbmc/pldm/libpldmresponder/fru.cpp (revision 963c0fdf)
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 
25 std::optional<pldm_entity>
26     FruImpl::getEntityByObjectPath(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 
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 assocition 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 = node.entity_instance_num +
109                                                      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 
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("Failed building FRU table due to inventory lookup: {ERROR}",
158               "ERROR", e);
159         return;
160     }
161 
162     auto itemIntfsLookup = std::get<2>(dbusInfo);
163 
164     for (const auto& object : objects)
165     {
166         const auto& interfaces = object.second;
167         for (const auto& interface : interfaces)
168         {
169             if (itemIntfsLookup.contains(interface.first))
170             {
171                 // checking fru present property is available or not.
172                 if (!pldm::utils::checkForFruPresence(object.first.str))
173                 {
174                     continue;
175                 }
176 
177                 // An exception will be thrown by getRecordInfo, if the item
178                 // D-Bus interface name specified in FRU_Master.json does
179                 // not have corresponding config jsons
180                 try
181                 {
182                     updateAssociationTree(objects, object.first.str);
183                     pldm_entity entity{};
184                     if (objToEntityNode.contains(object.first.str))
185                     {
186                         pldm_entity_node* node =
187                             objToEntityNode.at(object.first.str);
188 
189                         entity = pldm_entity_extract(node);
190                     }
191 
192                     auto recordInfos = parser.getRecordInfo(interface.first);
193                     populateRecords(interfaces, recordInfos, entity);
194 
195                     associatedEntityMap.emplace(object.first, entity);
196                     break;
197                 }
198                 catch (const std::exception& e)
199                 {
200                     error(
201                         "Config JSONs missing for the item interface type, interface = {INTF}",
202                         "INTF", interface.first);
203                     break;
204                 }
205             }
206         }
207     }
208 
209     int rc = pldm_entity_association_pdr_add_check(entityTree, pdrRepo, false,
210                                                    TERMINUS_HANDLE);
211     if (rc < 0)
212     {
213         // pldm_entity_assocation_pdr_add() assert()ed on failure
214         error("Failed to add PLDM entity association PDR: {LIBPLDM_ERROR}",
215               "LIBPLDM_ERROR", rc);
216         throw std::runtime_error("Failed to add PLDM entity association PDR");
217     }
218 
219     // save a copy of bmc's entity association tree
220     pldm_entity_association_tree_copy_root(entityTree, bmcEntityTree);
221 
222     isBuilt = true;
223 }
224 std::string FruImpl::populatefwVersion()
225 {
226     static constexpr auto fwFunctionalObjPath =
227         "/xyz/openbmc_project/software/functional";
228     auto& bus = pldm::utils::DBusHandler::getBus();
229     std::string currentBmcVersion;
230     try
231     {
232         auto method = bus.new_method_call(pldm::utils::mapperService,
233                                           fwFunctionalObjPath,
234                                           pldm::utils::dbusProperties, "Get");
235         method.append("xyz.openbmc_project.Association", "endpoints");
236         std::variant<std::vector<std::string>> paths;
237         auto reply = bus.call(method, dbusTimeout);
238         reply.read(paths);
239         auto fwRunningVersion = std::get<std::vector<std::string>>(paths)[0];
240         constexpr auto versionIntf = "xyz.openbmc_project.Software.Version";
241         auto version = pldm::utils::DBusHandler().getDbusPropertyVariant(
242             fwRunningVersion.c_str(), "Version", versionIntf);
243         currentBmcVersion = std::get<std::string>(version);
244     }
245     catch (const std::exception& e)
246     {
247         error("failed to make a d-bus call Asociation, ERROR= {ERR_EXCEP}",
248               "ERR_EXCEP", e.what());
249         return {};
250     }
251     return currentBmcVersion;
252 }
253 void FruImpl::populateRecords(
254     const pldm::responder::dbus::InterfaceMap& interfaces,
255     const fru_parser::FruRecordInfos& recordInfos, const pldm_entity& entity)
256 {
257     // recordSetIdentifier for the FRU will be set when the first record gets
258     // added for the FRU
259     uint16_t recordSetIdentifier = 0;
260     auto numRecsCount = numRecs;
261     static uint32_t bmc_record_handle = 0;
262 
263     for (const auto& [recType, encType, fieldInfos] : recordInfos)
264     {
265         std::vector<uint8_t> tlvs;
266         uint8_t numFRUFields = 0;
267         for (const auto& [intf, prop, propType, fieldTypeNum] : fieldInfos)
268         {
269             try
270             {
271                 pldm::responder::dbus::Value propValue;
272 
273                 // Assuming that 0 container Id is assigned to the System (as
274                 // that should be the top most container as per dbus hierarchy)
275                 if (entity.entity_container_id == 0 && prop == "Version")
276                 {
277                     propValue = populatefwVersion();
278                 }
279                 else
280                 {
281                     propValue = interfaces.at(intf).at(prop);
282                 }
283                 if (propType == "bytearray")
284                 {
285                     auto byteArray = std::get<std::vector<uint8_t>>(propValue);
286                     if (!byteArray.size())
287                     {
288                         continue;
289                     }
290 
291                     numFRUFields++;
292                     tlvs.emplace_back(fieldTypeNum);
293                     tlvs.emplace_back(byteArray.size());
294                     std::move(std::begin(byteArray), std::end(byteArray),
295                               std::back_inserter(tlvs));
296                 }
297                 else if (propType == "string")
298                 {
299                     auto str = std::get<std::string>(propValue);
300                     if (!str.size())
301                     {
302                         continue;
303                     }
304 
305                     numFRUFields++;
306                     tlvs.emplace_back(fieldTypeNum);
307                     tlvs.emplace_back(str.size());
308                     std::move(std::begin(str), std::end(str),
309                               std::back_inserter(tlvs));
310                 }
311             }
312             catch (const std::out_of_range&)
313             {
314                 continue;
315             }
316         }
317 
318         if (tlvs.size())
319         {
320             if (numRecs == numRecsCount)
321             {
322                 recordSetIdentifier = nextRSI();
323                 bmc_record_handle = nextRecordHandle();
324                 int rc = pldm_pdr_add_fru_record_set_check(
325                     pdrRepo, TERMINUS_HANDLE, recordSetIdentifier,
326                     entity.entity_type, entity.entity_instance_num,
327                     entity.entity_container_id, &bmc_record_handle);
328                 if (rc)
329                 {
330                     // pldm_pdr_add_fru_record_set() assert()ed on failure
331                     throw std::runtime_error(
332                         "Failed to add PDR FRU record set");
333                 }
334             }
335             auto curSize = table.size();
336             table.resize(curSize + recHeaderSize + tlvs.size());
337             encode_fru_record(table.data(), table.size(), &curSize,
338                               recordSetIdentifier, recType, numFRUFields,
339                               encType, tlvs.data(), tlvs.size());
340             numRecs++;
341         }
342     }
343 }
344 
345 std::vector<uint8_t> FruImpl::tableResize()
346 {
347     std::vector<uint8_t> tempTable;
348 
349     if (table.size())
350     {
351         std::copy(table.begin(), table.end(), std::back_inserter(tempTable));
352         padBytes = pldm::utils::getNumPadBytes(table.size());
353         tempTable.resize(tempTable.size() + padBytes, 0);
354     }
355     return tempTable;
356 }
357 
358 void FruImpl::getFRUTable(Response& response)
359 {
360     auto hdrSize = response.size();
361     std::vector<uint8_t> tempTable;
362 
363     if (table.size())
364     {
365         tempTable = tableResize();
366         checksum = crc32(tempTable.data(), tempTable.size());
367     }
368     response.resize(hdrSize + tempTable.size() + sizeof(checksum), 0);
369     std::copy(tempTable.begin(), tempTable.end(), response.begin() + hdrSize);
370 
371     // Copy the checksum to response data
372     auto iter = response.begin() + hdrSize + tempTable.size();
373     std::copy_n(reinterpret_cast<const uint8_t*>(&checksum), sizeof(checksum),
374                 iter);
375 }
376 
377 void FruImpl::getFRURecordTableMetadata()
378 {
379     std::vector<uint8_t> tempTable;
380     if (table.size())
381     {
382         tempTable = tableResize();
383         checksum = crc32(tempTable.data(), tempTable.size());
384     }
385 }
386 
387 int FruImpl::getFRURecordByOption(std::vector<uint8_t>& fruData,
388                                   uint16_t /* fruTableHandle */,
389                                   uint16_t recordSetIdentifer,
390                                   uint8_t recordType, uint8_t fieldType)
391 {
392     using sum = uint32_t;
393 
394     // FRU table is built lazily, build if not done.
395     buildFRUTable();
396 
397     /* 7 is sizeof(checksum,4) + padBytesMax(3)
398      * We can not know size of the record table got by options in advance, but
399      * it must be less than the source table. So it's safe to use sizeof the
400      * source table + 7 as the buffer length
401      */
402     size_t recordTableSize = table.size() - padBytes + 7;
403     fruData.resize(recordTableSize, 0);
404 
405     int rc = get_fru_record_by_option_check(
406         table.data(), table.size() - padBytes, fruData.data(), &recordTableSize,
407         recordSetIdentifer, recordType, fieldType);
408 
409     if (rc != PLDM_SUCCESS || recordTableSize == 0)
410     {
411         return PLDM_FRU_DATA_STRUCTURE_TABLE_UNAVAILABLE;
412     }
413 
414     auto pads = pldm::utils::getNumPadBytes(recordTableSize);
415     crc32(fruData.data(), recordTableSize + pads);
416 
417     auto iter = fruData.begin() + recordTableSize + pads;
418     std::copy_n(reinterpret_cast<const uint8_t*>(&checksum), sizeof(checksum),
419                 iter);
420     fruData.resize(recordTableSize + pads + sizeof(sum));
421 
422     return PLDM_SUCCESS;
423 }
424 
425 int FruImpl::setFRUTable(const std::vector<uint8_t>& fruData)
426 {
427     auto record =
428         reinterpret_cast<const pldm_fru_record_data_format*>(fruData.data());
429     if (record)
430     {
431         if (oemFruHandler && record->record_type == PLDM_FRU_RECORD_TYPE_OEM)
432         {
433             auto rc = oemFruHandler->processOEMFRUTable(fruData);
434             if (!rc)
435             {
436                 return PLDM_SUCCESS;
437             }
438         }
439     }
440     return PLDM_ERROR_UNSUPPORTED_PLDM_CMD;
441 }
442 
443 namespace fru
444 {
445 Response Handler::getFRURecordTableMetadata(const pldm_msg* request,
446                                             size_t /*payloadLength*/)
447 {
448     // FRU table is built lazily, build if not done.
449     buildFRUTable();
450 
451     constexpr uint8_t major = 0x01;
452     constexpr uint8_t minor = 0x00;
453     constexpr uint32_t maxSize = 0xFFFFFFFF;
454 
455     Response response(sizeof(pldm_msg_hdr) +
456                           PLDM_GET_FRU_RECORD_TABLE_METADATA_RESP_BYTES,
457                       0);
458     auto responsePtr = reinterpret_cast<pldm_msg*>(response.data());
459 
460     impl.getFRURecordTableMetadata();
461 
462     auto rc = encode_get_fru_record_table_metadata_resp(
463         request->hdr.instance_id, PLDM_SUCCESS, major, minor, maxSize,
464         impl.size(), impl.numRSI(), impl.numRecords(), impl.checkSum(),
465         responsePtr);
466     if (rc != PLDM_SUCCESS)
467     {
468         return ccOnlyResponse(request, rc);
469     }
470 
471     return response;
472 }
473 
474 Response Handler::getFRURecordTable(const pldm_msg* request,
475                                     size_t payloadLength)
476 {
477     // FRU table is built lazily, build if not done.
478     buildFRUTable();
479 
480     if (payloadLength != PLDM_GET_FRU_RECORD_TABLE_REQ_BYTES)
481     {
482         return ccOnlyResponse(request, PLDM_ERROR_INVALID_LENGTH);
483     }
484 
485     Response response(
486         sizeof(pldm_msg_hdr) + PLDM_GET_FRU_RECORD_TABLE_MIN_RESP_BYTES, 0);
487     auto responsePtr = reinterpret_cast<pldm_msg*>(response.data());
488 
489     auto rc = encode_get_fru_record_table_resp(request->hdr.instance_id,
490                                                PLDM_SUCCESS, 0,
491                                                PLDM_START_AND_END, responsePtr);
492     if (rc != PLDM_SUCCESS)
493     {
494         return ccOnlyResponse(request, rc);
495     }
496 
497     impl.getFRUTable(response);
498 
499     return response;
500 }
501 
502 Response Handler::getFRURecordByOption(const pldm_msg* request,
503                                        size_t payloadLength)
504 {
505     if (payloadLength != sizeof(pldm_get_fru_record_by_option_req))
506     {
507         return ccOnlyResponse(request, PLDM_ERROR_INVALID_LENGTH);
508     }
509 
510     uint32_t retDataTransferHandle{};
511     uint16_t retFruTableHandle{};
512     uint16_t retRecordSetIdentifier{};
513     uint8_t retRecordType{};
514     uint8_t retFieldType{};
515     uint8_t retTransferOpFlag{};
516 
517     auto rc = decode_get_fru_record_by_option_req(
518         request, payloadLength, &retDataTransferHandle, &retFruTableHandle,
519         &retRecordSetIdentifier, &retRecordType, &retFieldType,
520         &retTransferOpFlag);
521 
522     if (rc != PLDM_SUCCESS)
523     {
524         return ccOnlyResponse(request, rc);
525     }
526 
527     std::vector<uint8_t> fruData;
528     rc = impl.getFRURecordByOption(fruData, retFruTableHandle,
529                                    retRecordSetIdentifier, retRecordType,
530                                    retFieldType);
531     if (rc != PLDM_SUCCESS)
532     {
533         return ccOnlyResponse(request, rc);
534     }
535 
536     auto respPayloadLength = PLDM_GET_FRU_RECORD_BY_OPTION_MIN_RESP_BYTES +
537                              fruData.size();
538     Response response(sizeof(pldm_msg_hdr) + respPayloadLength, 0);
539     auto responsePtr = reinterpret_cast<pldm_msg*>(response.data());
540 
541     rc = encode_get_fru_record_by_option_resp(
542         request->hdr.instance_id, PLDM_SUCCESS, 0, PLDM_START_AND_END,
543         fruData.data(), fruData.size(), responsePtr, respPayloadLength);
544 
545     if (rc != PLDM_SUCCESS)
546     {
547         return ccOnlyResponse(request, rc);
548     }
549 
550     return response;
551 }
552 
553 Response Handler::setFRURecordTable(const pldm_msg* request,
554                                     size_t payloadLength)
555 {
556     uint32_t transferHandle{};
557     uint8_t transferOpFlag{};
558     struct variable_field fruData;
559 
560     auto rc = decode_set_fru_record_table_req(
561         request, payloadLength, &transferHandle, &transferOpFlag, &fruData);
562 
563     if (rc != PLDM_SUCCESS)
564     {
565         return ccOnlyResponse(request, rc);
566     }
567 
568     Table table(fruData.ptr, fruData.ptr + fruData.length);
569     rc = impl.setFRUTable(table);
570     if (rc != PLDM_SUCCESS)
571     {
572         return ccOnlyResponse(request, rc);
573     }
574 
575     Response response(sizeof(pldm_msg_hdr) +
576                       PLDM_SET_FRU_RECORD_TABLE_RESP_BYTES);
577     struct pldm_msg* responsePtr = reinterpret_cast<pldm_msg*>(response.data());
578 
579     rc = encode_set_fru_record_table_resp(
580         request->hdr.instance_id, PLDM_SUCCESS, 0 /* nextDataTransferHandle */,
581         response.size() - sizeof(pldm_msg_hdr), responsePtr);
582 
583     if (rc != PLDM_SUCCESS)
584     {
585         return ccOnlyResponse(request, rc);
586     }
587 
588     return response;
589 }
590 
591 } // namespace fru
592 
593 } // namespace responder
594 
595 } // namespace pldm
596