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