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