xref: /openbmc/pldm/libpldmresponder/fru.cpp (revision fcda4d03)
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 <iostream>
13 #include <optional>
14 #include <set>
15 #include <stack>
16 
17 PHOSPHOR_LOG2_USING;
18 
19 namespace pldm
20 {
21 namespace responder
22 {
23 
24 constexpr auto root = "/xyz/openbmc_project/inventory/";
25 
26 std::optional<pldm_entity>
27     FruImpl::getEntityByObjectPath(const dbus::InterfaceMap& intfMaps)
28 {
29     for (const auto& intfMap : intfMaps)
30     {
31         try
32         {
33             pldm_entity entity{};
34             entity.entity_type = parser.getEntityType(intfMap.first);
35             return entity;
36         }
37         catch (const std::exception& e)
38         {
39             continue;
40         }
41     }
42 
43     return std::nullopt;
44 }
45 
46 void FruImpl::updateAssociationTree(const dbus::ObjectValueTree& objects,
47                                     const std::string& path)
48 {
49     if (path.find(root) == std::string::npos)
50     {
51         return;
52     }
53 
54     std::stack<std::string> tmpObjPaths{};
55     tmpObjPaths.emplace(path);
56 
57     auto obj = pldm::utils::findParent(path);
58     while ((obj + '/') != root)
59     {
60         tmpObjPaths.emplace(obj);
61         obj = pldm::utils::findParent(obj);
62     }
63 
64     std::stack<std::string> tmpObj = tmpObjPaths;
65     while (!tmpObj.empty())
66     {
67         std::string s = tmpObj.top();
68         std::cout << s << std::endl;
69         tmpObj.pop();
70     }
71     // Update pldm entity to assocition tree
72     std::string prePath = tmpObjPaths.top();
73     while (!tmpObjPaths.empty())
74     {
75         std::string currPath = tmpObjPaths.top();
76         tmpObjPaths.pop();
77 
78         do
79         {
80             if (objToEntityNode.contains(currPath))
81             {
82                 pldm_entity node =
83                     pldm_entity_extract(objToEntityNode.at(currPath));
84                 if (pldm_entity_association_tree_find_with_locality(
85                         entityTree, &node, false))
86                 {
87                     break;
88                 }
89             }
90             else
91             {
92                 if (!objects.contains(currPath))
93                 {
94                     break;
95                 }
96 
97                 auto entityPtr = getEntityByObjectPath(objects.at(currPath));
98                 if (!entityPtr)
99                 {
100                     break;
101                 }
102 
103                 pldm_entity entity = *entityPtr;
104 
105                 for (auto& it : objToEntityNode)
106                 {
107                     pldm_entity node = pldm_entity_extract(it.second);
108                     if (node.entity_type == entity.entity_type)
109                     {
110                         entity.entity_instance_num = node.entity_instance_num +
111                                                      1;
112                         break;
113                     }
114                 }
115 
116                 if (currPath == prePath)
117                 {
118                     auto node = pldm_entity_association_tree_add_entity(
119                         entityTree, &entity, 0xFFFF, nullptr,
120                         PLDM_ENTITY_ASSOCIAION_PHYSICAL, false, true, 0xFFFF);
121                     objToEntityNode[currPath] = node;
122                 }
123                 else
124                 {
125                     if (objToEntityNode.contains(prePath))
126                     {
127                         auto node = pldm_entity_association_tree_add_entity(
128                             entityTree, &entity, 0xFFFF,
129                             objToEntityNode[prePath],
130                             PLDM_ENTITY_ASSOCIAION_PHYSICAL, false, true,
131                             0xFFFF);
132                         objToEntityNode[currPath] = node;
133                     }
134                 }
135             }
136         } while (0);
137 
138         prePath = currPath;
139     }
140 }
141 
142 void FruImpl::buildFRUTable()
143 {
144     if (isBuilt)
145     {
146         return;
147     }
148 
149     fru_parser::DBusLookupInfo dbusInfo;
150     // Read the all the inventory D-Bus objects
151     auto& bus = pldm::utils::DBusHandler::getBus();
152     dbus::ObjectValueTree objects;
153 
154     try
155     {
156         dbusInfo = parser.inventoryLookup();
157         auto method = bus.new_method_call(
158             std::get<0>(dbusInfo).c_str(), std::get<1>(dbusInfo).c_str(),
159             "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
160         auto reply = bus.call(
161             method,
162             std::chrono::duration_cast<microsec>(sec(DBUS_TIMEOUT)).count());
163         reply.read(objects);
164     }
165     catch (const std::exception& e)
166     {
167         error(
168             "Look up of inventory objects failed and PLDM FRU table creation failed");
169         return;
170     }
171 
172     auto itemIntfsLookup = std::get<2>(dbusInfo);
173 
174     for (const auto& object : objects)
175     {
176         const auto& interfaces = object.second;
177         bool isPresent = pldm::utils::checkForFruPresence(object.first.str);
178         // Do not create fru record if fru is not present.
179         // Pick up the next available fru.
180         if (!isPresent)
181         {
182             continue;
183         }
184         for (const auto& interface : interfaces)
185         {
186             if (itemIntfsLookup.find(interface.first) != itemIntfsLookup.end())
187             {
188                 // An exception will be thrown by getRecordInfo, if the item
189                 // D-Bus interface name specified in FRU_Master.json does
190                 // not have corresponding config jsons
191                 try
192                 {
193                     updateAssociationTree(objects, object.first.str);
194                     pldm_entity entity{};
195                     if (objToEntityNode.contains(object.first.str))
196                     {
197                         pldm_entity_node* node =
198                             objToEntityNode.at(object.first.str);
199 
200                         entity = pldm_entity_extract(node);
201                     }
202 
203                     auto recordInfos = parser.getRecordInfo(interface.first);
204                     populateRecords(interfaces, recordInfos, entity);
205 
206                     associatedEntityMap.emplace(object.first, entity);
207                     break;
208                 }
209                 catch (const std::exception& e)
210                 {
211                     info(
212                         "Config JSONs missing for the item interface type, interface = {INTF}",
213                         "INTF", interface.first);
214                     break;
215                 }
216             }
217         }
218     }
219 
220     int rc = pldm_entity_association_pdr_add_check(entityTree, pdrRepo, false,
221                                                    TERMINUS_HANDLE);
222     if (rc < 0)
223     {
224         // pldm_entity_assocation_pdr_add() assert()ed on failure
225         error("Failed to add PLDM entity association PDR: {LIBPLDM_ERROR}",
226               "LIBPLDM_ERROR", rc);
227         throw std::runtime_error("Failed to add PLDM entity association PDR");
228     }
229 
230     // save a copy of bmc's entity association tree
231     pldm_entity_association_tree_copy_root(entityTree, bmcEntityTree);
232 
233     if (table.size())
234     {
235         padBytes = pldm::utils::getNumPadBytes(table.size());
236         table.resize(table.size() + padBytes, 0);
237 
238         // Calculate the checksum
239         checksum = crc32(table.data(), table.size());
240     }
241     isBuilt = true;
242 }
243 std::string FruImpl::populatefwVersion()
244 {
245     static constexpr auto fwFunctionalObjPath =
246         "/xyz/openbmc_project/software/functional";
247     auto& bus = pldm::utils::DBusHandler::getBus();
248     std::string currentBmcVersion;
249     try
250     {
251         auto method = bus.new_method_call(pldm::utils::mapperService,
252                                           fwFunctionalObjPath,
253                                           pldm::utils::dbusProperties, "Get");
254         method.append("xyz.openbmc_project.Association", "endpoints");
255         std::variant<std::vector<std::string>> paths;
256         auto reply = bus.call(
257             method,
258             std::chrono::duration_cast<microsec>(sec(DBUS_TIMEOUT)).count());
259         reply.read(paths);
260         auto fwRunningVersion = std::get<std::vector<std::string>>(paths)[0];
261         constexpr auto versionIntf = "xyz.openbmc_project.Software.Version";
262         auto version = pldm::utils::DBusHandler().getDbusPropertyVariant(
263             fwRunningVersion.c_str(), "Version", versionIntf);
264         currentBmcVersion = std::get<std::string>(version);
265     }
266     catch (const std::exception& e)
267     {
268         error("failed to make a d-bus call Asociation, ERROR= {ERR_EXCEP}",
269               "ERR_EXCEP", e.what());
270         return {};
271     }
272     return currentBmcVersion;
273 }
274 void FruImpl::populateRecords(
275     const pldm::responder::dbus::InterfaceMap& interfaces,
276     const fru_parser::FruRecordInfos& recordInfos, const pldm_entity& entity)
277 {
278     // recordSetIdentifier for the FRU will be set when the first record gets
279     // added for the FRU
280     uint16_t recordSetIdentifier = 0;
281     auto numRecsCount = numRecs;
282     static uint32_t bmc_record_handle = 0;
283 
284     for (const auto& [recType, encType, fieldInfos] : recordInfos)
285     {
286         std::vector<uint8_t> tlvs;
287         uint8_t numFRUFields = 0;
288         for (const auto& [intf, prop, propType, fieldTypeNum] : fieldInfos)
289         {
290             try
291             {
292                 pldm::responder::dbus::Value propValue;
293 
294                 // Assuming that 0 container Id is assigned to the System (as
295                 // that should be the top most container as per dbus hierarchy)
296                 if (entity.entity_container_id == 0 && prop == "Version")
297                 {
298                     propValue = populatefwVersion();
299                 }
300                 else
301                 {
302                     propValue = interfaces.at(intf).at(prop);
303                 }
304                 if (propType == "bytearray")
305                 {
306                     auto byteArray = std::get<std::vector<uint8_t>>(propValue);
307                     if (!byteArray.size())
308                     {
309                         continue;
310                     }
311 
312                     numFRUFields++;
313                     tlvs.emplace_back(fieldTypeNum);
314                     tlvs.emplace_back(byteArray.size());
315                     std::move(std::begin(byteArray), std::end(byteArray),
316                               std::back_inserter(tlvs));
317                 }
318                 else if (propType == "string")
319                 {
320                     auto str = std::get<std::string>(propValue);
321                     if (!str.size())
322                     {
323                         continue;
324                     }
325 
326                     numFRUFields++;
327                     tlvs.emplace_back(fieldTypeNum);
328                     tlvs.emplace_back(str.size());
329                     std::move(std::begin(str), std::end(str),
330                               std::back_inserter(tlvs));
331                 }
332             }
333             catch (const std::out_of_range& e)
334             {
335                 continue;
336             }
337         }
338 
339         if (tlvs.size())
340         {
341             if (numRecs == numRecsCount)
342             {
343                 recordSetIdentifier = nextRSI();
344                 bmc_record_handle = nextRecordHandle();
345                 int rc = pldm_pdr_add_fru_record_set_check(
346                     pdrRepo, TERMINUS_HANDLE, recordSetIdentifier,
347                     entity.entity_type, entity.entity_instance_num,
348                     entity.entity_container_id, &bmc_record_handle);
349                 if (rc)
350                 {
351                     // pldm_pdr_add_fru_record_set() assert()ed on failure
352                     throw std::runtime_error(
353                         "Failed to add PDR FRU record set");
354                 }
355             }
356             auto curSize = table.size();
357             table.resize(curSize + recHeaderSize + tlvs.size());
358             encode_fru_record(table.data(), table.size(), &curSize,
359                               recordSetIdentifier, recType, numFRUFields,
360                               encType, tlvs.data(), tlvs.size());
361             numRecs++;
362         }
363     }
364 }
365 
366 void FruImpl::getFRUTable(Response& response)
367 {
368     auto hdrSize = response.size();
369 
370     response.resize(hdrSize + table.size() + sizeof(checksum), 0);
371     std::copy(table.begin(), table.end(), response.begin() + hdrSize);
372 
373     // Copy the checksum to response data
374     auto iter = response.begin() + hdrSize + table.size();
375     std::copy_n(reinterpret_cast<const uint8_t*>(&checksum), sizeof(checksum),
376                 iter);
377 }
378 
379 int FruImpl::getFRURecordByOption(std::vector<uint8_t>& fruData,
380                                   uint16_t /* fruTableHandle */,
381                                   uint16_t recordSetIdentifer,
382                                   uint8_t recordType, uint8_t fieldType)
383 {
384     using sum = uint32_t;
385 
386     // FRU table is built lazily, build if not done.
387     buildFRUTable();
388 
389     /* 7 is sizeof(checksum,4) + padBytesMax(3)
390      * We can not know size of the record table got by options in advance, but
391      * it must be less than the source table. So it's safe to use sizeof the
392      * source table + 7 as the buffer length
393      */
394     size_t recordTableSize = table.size() - padBytes + 7;
395     fruData.resize(recordTableSize, 0);
396 
397     int rc = get_fru_record_by_option_check(
398         table.data(), table.size() - padBytes, fruData.data(), &recordTableSize,
399         recordSetIdentifer, recordType, fieldType);
400 
401     if (rc != PLDM_SUCCESS || recordTableSize == 0)
402     {
403         return PLDM_FRU_DATA_STRUCTURE_TABLE_UNAVAILABLE;
404     }
405 
406     auto pads = pldm::utils::getNumPadBytes(recordTableSize);
407     crc32(fruData.data(), recordTableSize + pads);
408 
409     auto iter = fruData.begin() + recordTableSize + pads;
410     std::copy_n(reinterpret_cast<const uint8_t*>(&checksum), sizeof(checksum),
411                 iter);
412     fruData.resize(recordTableSize + pads + sizeof(sum));
413 
414     return PLDM_SUCCESS;
415 }
416 
417 namespace fru
418 {
419 Response Handler::getFRURecordTableMetadata(const pldm_msg* request,
420                                             size_t /*payloadLength*/)
421 {
422     // FRU table is built lazily, build if not done.
423     buildFRUTable();
424 
425     constexpr uint8_t major = 0x01;
426     constexpr uint8_t minor = 0x00;
427     constexpr uint32_t maxSize = 0xFFFFFFFF;
428 
429     Response response(sizeof(pldm_msg_hdr) +
430                           PLDM_GET_FRU_RECORD_TABLE_METADATA_RESP_BYTES,
431                       0);
432     auto responsePtr = reinterpret_cast<pldm_msg*>(response.data());
433 
434     auto rc = encode_get_fru_record_table_metadata_resp(
435         request->hdr.instance_id, PLDM_SUCCESS, major, minor, maxSize,
436         impl.size(), impl.numRSI(), impl.numRecords(), impl.checkSum(),
437         responsePtr);
438     if (rc != PLDM_SUCCESS)
439     {
440         return ccOnlyResponse(request, rc);
441     }
442 
443     return response;
444 }
445 
446 Response Handler::getFRURecordTable(const pldm_msg* request,
447                                     size_t payloadLength)
448 {
449     // FRU table is built lazily, build if not done.
450     buildFRUTable();
451 
452     if (payloadLength != PLDM_GET_FRU_RECORD_TABLE_REQ_BYTES)
453     {
454         return ccOnlyResponse(request, PLDM_ERROR_INVALID_LENGTH);
455     }
456 
457     Response response(
458         sizeof(pldm_msg_hdr) + PLDM_GET_FRU_RECORD_TABLE_MIN_RESP_BYTES, 0);
459     auto responsePtr = reinterpret_cast<pldm_msg*>(response.data());
460 
461     auto rc = encode_get_fru_record_table_resp(request->hdr.instance_id,
462                                                PLDM_SUCCESS, 0,
463                                                PLDM_START_AND_END, responsePtr);
464     if (rc != PLDM_SUCCESS)
465     {
466         return ccOnlyResponse(request, rc);
467     }
468 
469     impl.getFRUTable(response);
470 
471     return response;
472 }
473 
474 Response Handler::getFRURecordByOption(const pldm_msg* request,
475                                        size_t payloadLength)
476 {
477     if (payloadLength != sizeof(pldm_get_fru_record_by_option_req))
478     {
479         return ccOnlyResponse(request, PLDM_ERROR_INVALID_LENGTH);
480     }
481 
482     uint32_t retDataTransferHandle{};
483     uint16_t retFruTableHandle{};
484     uint16_t retRecordSetIdentifier{};
485     uint8_t retRecordType{};
486     uint8_t retFieldType{};
487     uint8_t retTransferOpFlag{};
488 
489     auto rc = decode_get_fru_record_by_option_req(
490         request, payloadLength, &retDataTransferHandle, &retFruTableHandle,
491         &retRecordSetIdentifier, &retRecordType, &retFieldType,
492         &retTransferOpFlag);
493 
494     if (rc != PLDM_SUCCESS)
495     {
496         return ccOnlyResponse(request, rc);
497     }
498 
499     std::vector<uint8_t> fruData;
500     rc = impl.getFRURecordByOption(fruData, retFruTableHandle,
501                                    retRecordSetIdentifier, retRecordType,
502                                    retFieldType);
503     if (rc != PLDM_SUCCESS)
504     {
505         return ccOnlyResponse(request, rc);
506     }
507 
508     auto respPayloadLength = PLDM_GET_FRU_RECORD_BY_OPTION_MIN_RESP_BYTES +
509                              fruData.size();
510     Response response(sizeof(pldm_msg_hdr) + respPayloadLength, 0);
511     auto responsePtr = reinterpret_cast<pldm_msg*>(response.data());
512 
513     rc = encode_get_fru_record_by_option_resp(
514         request->hdr.instance_id, PLDM_SUCCESS, 0, PLDM_START_AND_END,
515         fruData.data(), fruData.size(), responsePtr, respPayloadLength);
516 
517     if (rc != PLDM_SUCCESS)
518     {
519         return ccOnlyResponse(request, rc);
520     }
521 
522     return response;
523 }
524 
525 } // namespace fru
526 
527 } // namespace responder
528 
529 } // namespace pldm
530