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