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