xref: /openbmc/pldm/libpldmresponder/fru.cpp (revision d15fa099)
1 #include "fru.hpp"
2 
3 #include "common/utils.hpp"
4 
5 #include <config.h>
6 #include <libpldm/entity.h>
7 #include <libpldm/utils.h>
8 #include <systemd/sd-journal.h>
9 
10 #include <phosphor-logging/lg2.hpp>
11 #include <sdbusplus/bus.hpp>
12 
13 #include <iostream>
14 #include <set>
15 
16 PHOSPHOR_LOG2_USING;
17 
18 namespace pldm
19 {
20 namespace responder
21 {
22 void FruImpl::buildFRUTable()
23 {
24     if (isBuilt)
25     {
26         return;
27     }
28 
29     fru_parser::DBusLookupInfo dbusInfo;
30     // Read the all the inventory D-Bus objects
31     auto& bus = pldm::utils::DBusHandler::getBus();
32     dbus::ObjectValueTree objects;
33 
34     try
35     {
36         dbusInfo = parser.inventoryLookup();
37         auto method = bus.new_method_call(
38             std::get<0>(dbusInfo).c_str(), std::get<1>(dbusInfo).c_str(),
39             "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
40         auto reply = bus.call(method);
41         reply.read(objects);
42     }
43     catch (const std::exception& e)
44     {
45         error(
46             "Look up of inventory objects failed and PLDM FRU table creation failed");
47         return;
48     }
49 
50     auto itemIntfsLookup = std::get<2>(dbusInfo);
51 
52     for (const auto& object : objects)
53     {
54         const auto& interfaces = object.second;
55         bool isPresent = pldm::utils::checkForFruPresence(object.first.str);
56         // Do not create fru record if fru is not present.
57         // Pick up the next available fru.
58         if (!isPresent)
59         {
60             continue;
61         }
62         for (const auto& interface : interfaces)
63         {
64             if (itemIntfsLookup.find(interface.first) != itemIntfsLookup.end())
65             {
66                 // An exception will be thrown by getRecordInfo, if the item
67                 // D-Bus interface name specified in FRU_Master.json does
68                 // not have corresponding config jsons
69                 try
70                 {
71                     pldm_entity entity{};
72                     entity.entity_type = parser.getEntityType(interface.first);
73                     pldm_entity_node* parent = nullptr;
74                     auto parentObj = pldm::utils::findParent(object.first.str);
75                     // To add a FRU to the entity association tree, we need to
76                     // determine if the FRU has a parent (D-Bus object). For eg
77                     // /system/backplane's parent is /system. /system has no
78                     // parent. Some D-Bus pathnames might just be namespaces
79                     // (not D-Bus objects), so we need to iterate upwards until
80                     // a parent is found, or we reach the root ("/").
81                     // Parents are always added first before children in the
82                     // entity association tree. We're relying on the fact that
83                     // the std::map containing object paths from the
84                     // GetManagedObjects call will have a sorted pathname list.
85                     do
86                     {
87                         auto iter = objToEntityNode.find(parentObj);
88                         if (iter != objToEntityNode.end())
89                         {
90                             parent = iter->second;
91                             break;
92                         }
93                         parentObj = pldm::utils::findParent(parentObj);
94                     } while (parentObj != "/");
95 
96                     auto node = pldm_entity_association_tree_add(
97                         entityTree, &entity, 0xFFFF, parent,
98                         PLDM_ENTITY_ASSOCIAION_PHYSICAL);
99                     objToEntityNode[object.first.str] = node;
100 
101                     auto recordInfos = parser.getRecordInfo(interface.first);
102                     populateRecords(interfaces, recordInfos, entity);
103 
104                     associatedEntityMap.emplace(object.first, entity);
105                     break;
106                 }
107                 catch (const std::exception& e)
108                 {
109                     info(
110                         "Config JSONs missing for the item interface type, interface = {INTF}",
111                         "INTF", interface.first);
112                     break;
113                 }
114             }
115         }
116     }
117 
118     pldm_entity_association_pdr_add(entityTree, pdrRepo, false,
119                                     TERMINUS_HANDLE);
120     // save a copy of bmc's entity association tree
121     pldm_entity_association_tree_copy_root(entityTree, bmcEntityTree);
122 
123     if (table.size())
124     {
125         padBytes = pldm::utils::getNumPadBytes(table.size());
126         table.resize(table.size() + padBytes, 0);
127 
128         // Calculate the checksum
129         checksum = crc32(table.data(), table.size());
130     }
131     isBuilt = true;
132 }
133 std::string FruImpl::populatefwVersion()
134 {
135     static constexpr auto fwFunctionalObjPath =
136         "/xyz/openbmc_project/software/functional";
137     auto& bus = pldm::utils::DBusHandler::getBus();
138     std::string currentBmcVersion;
139     try
140     {
141         auto method = bus.new_method_call(pldm::utils::mapperService,
142                                           fwFunctionalObjPath,
143                                           pldm::utils::dbusProperties, "Get");
144         method.append("xyz.openbmc_project.Association", "endpoints");
145         std::variant<std::vector<std::string>> paths;
146         auto reply = bus.call(method);
147         reply.read(paths);
148         auto fwRunningVersion = std::get<std::vector<std::string>>(paths)[0];
149         constexpr auto versionIntf = "xyz.openbmc_project.Software.Version";
150         auto version = pldm::utils::DBusHandler().getDbusPropertyVariant(
151             fwRunningVersion.c_str(), "Version", versionIntf);
152         currentBmcVersion = std::get<std::string>(version);
153     }
154     catch (const std::exception& e)
155     {
156         error("failed to make a d-bus call Asociation, ERROR= {ERR_EXCEP}",
157               "ERR_EXCEP", e.what());
158         return {};
159     }
160     return currentBmcVersion;
161 }
162 void FruImpl::populateRecords(
163     const pldm::responder::dbus::InterfaceMap& interfaces,
164     const fru_parser::FruRecordInfos& recordInfos, const pldm_entity& entity)
165 {
166     // recordSetIdentifier for the FRU will be set when the first record gets
167     // added for the FRU
168     uint16_t recordSetIdentifier = 0;
169     auto numRecsCount = numRecs;
170     static uint32_t bmc_record_handle = 0;
171 
172     for (const auto& [recType, encType, fieldInfos] : recordInfos)
173     {
174         std::vector<uint8_t> tlvs;
175         uint8_t numFRUFields = 0;
176         for (const auto& [intf, prop, propType, fieldTypeNum] : fieldInfos)
177         {
178             try
179             {
180                 pldm::responder::dbus::Value propValue;
181 
182                 // Assuming that 0 container Id is assigned to the System (as
183                 // that should be the top most container as per dbus hierarchy)
184                 if (entity.entity_container_id == 0 && prop == "Version")
185                 {
186                     propValue = populatefwVersion();
187                 }
188                 else
189                 {
190                     propValue = interfaces.at(intf).at(prop);
191                 }
192                 if (propType == "bytearray")
193                 {
194                     auto byteArray = std::get<std::vector<uint8_t>>(propValue);
195                     if (!byteArray.size())
196                     {
197                         continue;
198                     }
199 
200                     numFRUFields++;
201                     tlvs.emplace_back(fieldTypeNum);
202                     tlvs.emplace_back(byteArray.size());
203                     std::move(std::begin(byteArray), std::end(byteArray),
204                               std::back_inserter(tlvs));
205                 }
206                 else if (propType == "string")
207                 {
208                     auto str = std::get<std::string>(propValue);
209                     if (!str.size())
210                     {
211                         continue;
212                     }
213 
214                     numFRUFields++;
215                     tlvs.emplace_back(fieldTypeNum);
216                     tlvs.emplace_back(str.size());
217                     std::move(std::begin(str), std::end(str),
218                               std::back_inserter(tlvs));
219                 }
220             }
221             catch (const std::out_of_range& e)
222             {
223                 continue;
224             }
225         }
226 
227         if (tlvs.size())
228         {
229             if (numRecs == numRecsCount)
230             {
231                 recordSetIdentifier = nextRSI();
232                 bmc_record_handle = nextRecordHandle();
233                 pldm_pdr_add_fru_record_set(
234                     pdrRepo, TERMINUS_HANDLE, recordSetIdentifier,
235                     entity.entity_type, entity.entity_instance_num,
236                     entity.entity_container_id, bmc_record_handle);
237             }
238             auto curSize = table.size();
239             table.resize(curSize + recHeaderSize + tlvs.size());
240             encode_fru_record(table.data(), table.size(), &curSize,
241                               recordSetIdentifier, recType, numFRUFields,
242                               encType, tlvs.data(), tlvs.size());
243             numRecs++;
244         }
245     }
246 }
247 
248 void FruImpl::getFRUTable(Response& response)
249 {
250     auto hdrSize = response.size();
251 
252     response.resize(hdrSize + table.size() + sizeof(checksum), 0);
253     std::copy(table.begin(), table.end(), response.begin() + hdrSize);
254 
255     // Copy the checksum to response data
256     auto iter = response.begin() + hdrSize + table.size();
257     std::copy_n(reinterpret_cast<const uint8_t*>(&checksum), sizeof(checksum),
258                 iter);
259 }
260 
261 int FruImpl::getFRURecordByOption(std::vector<uint8_t>& fruData,
262                                   uint16_t /* fruTableHandle */,
263                                   uint16_t recordSetIdentifer,
264                                   uint8_t recordType, uint8_t fieldType)
265 {
266     using sum = uint32_t;
267 
268     // FRU table is built lazily, build if not done.
269     buildFRUTable();
270 
271     /* 7 is sizeof(checksum,4) + padBytesMax(3)
272      * We can not know size of the record table got by options in advance, but
273      * it must be less than the source table. So it's safe to use sizeof the
274      * source table + 7 as the buffer length
275      */
276     size_t recordTableSize = table.size() - padBytes + 7;
277     fruData.resize(recordTableSize, 0);
278 
279     get_fru_record_by_option(table.data(), table.size() - padBytes,
280                              fruData.data(), &recordTableSize,
281                              recordSetIdentifer, recordType, fieldType);
282 
283     if (recordTableSize == 0)
284     {
285         return PLDM_FRU_DATA_STRUCTURE_TABLE_UNAVAILABLE;
286     }
287 
288     auto pads = pldm::utils::getNumPadBytes(recordTableSize);
289     crc32(fruData.data(), recordTableSize + pads);
290 
291     auto iter = fruData.begin() + recordTableSize + pads;
292     std::copy_n(reinterpret_cast<const uint8_t*>(&checksum), sizeof(checksum),
293                 iter);
294     fruData.resize(recordTableSize + pads + sizeof(sum));
295 
296     return PLDM_SUCCESS;
297 }
298 
299 namespace fru
300 {
301 Response Handler::getFRURecordTableMetadata(const pldm_msg* request,
302                                             size_t /*payloadLength*/)
303 {
304     // FRU table is built lazily, build if not done.
305     buildFRUTable();
306 
307     constexpr uint8_t major = 0x01;
308     constexpr uint8_t minor = 0x00;
309     constexpr uint32_t maxSize = 0xFFFFFFFF;
310 
311     Response response(sizeof(pldm_msg_hdr) +
312                           PLDM_GET_FRU_RECORD_TABLE_METADATA_RESP_BYTES,
313                       0);
314     auto responsePtr = reinterpret_cast<pldm_msg*>(response.data());
315 
316     auto rc = encode_get_fru_record_table_metadata_resp(
317         request->hdr.instance_id, PLDM_SUCCESS, major, minor, maxSize,
318         impl.size(), impl.numRSI(), impl.numRecords(), impl.checkSum(),
319         responsePtr);
320     if (rc != PLDM_SUCCESS)
321     {
322         return ccOnlyResponse(request, rc);
323     }
324 
325     return response;
326 }
327 
328 Response Handler::getFRURecordTable(const pldm_msg* request,
329                                     size_t payloadLength)
330 {
331     // FRU table is built lazily, build if not done.
332     buildFRUTable();
333 
334     if (payloadLength != PLDM_GET_FRU_RECORD_TABLE_REQ_BYTES)
335     {
336         return ccOnlyResponse(request, PLDM_ERROR_INVALID_LENGTH);
337     }
338 
339     Response response(
340         sizeof(pldm_msg_hdr) + PLDM_GET_FRU_RECORD_TABLE_MIN_RESP_BYTES, 0);
341     auto responsePtr = reinterpret_cast<pldm_msg*>(response.data());
342 
343     auto rc = encode_get_fru_record_table_resp(request->hdr.instance_id,
344                                                PLDM_SUCCESS, 0,
345                                                PLDM_START_AND_END, responsePtr);
346     if (rc != PLDM_SUCCESS)
347     {
348         return ccOnlyResponse(request, rc);
349     }
350 
351     impl.getFRUTable(response);
352 
353     return response;
354 }
355 
356 Response Handler::getFRURecordByOption(const pldm_msg* request,
357                                        size_t payloadLength)
358 {
359     if (payloadLength != sizeof(pldm_get_fru_record_by_option_req))
360     {
361         return ccOnlyResponse(request, PLDM_ERROR_INVALID_LENGTH);
362     }
363 
364     uint32_t retDataTransferHandle{};
365     uint16_t retFruTableHandle{};
366     uint16_t retRecordSetIdentifier{};
367     uint8_t retRecordType{};
368     uint8_t retFieldType{};
369     uint8_t retTransferOpFlag{};
370 
371     auto rc = decode_get_fru_record_by_option_req(
372         request, payloadLength, &retDataTransferHandle, &retFruTableHandle,
373         &retRecordSetIdentifier, &retRecordType, &retFieldType,
374         &retTransferOpFlag);
375 
376     if (rc != PLDM_SUCCESS)
377     {
378         return ccOnlyResponse(request, rc);
379     }
380 
381     std::vector<uint8_t> fruData;
382     rc = impl.getFRURecordByOption(fruData, retFruTableHandle,
383                                    retRecordSetIdentifier, retRecordType,
384                                    retFieldType);
385     if (rc != PLDM_SUCCESS)
386     {
387         return ccOnlyResponse(request, rc);
388     }
389 
390     auto respPayloadLength = PLDM_GET_FRU_RECORD_BY_OPTION_MIN_RESP_BYTES +
391                              fruData.size();
392     Response response(sizeof(pldm_msg_hdr) + respPayloadLength, 0);
393     auto responsePtr = reinterpret_cast<pldm_msg*>(response.data());
394 
395     rc = encode_get_fru_record_by_option_resp(
396         request->hdr.instance_id, PLDM_SUCCESS, 0, PLDM_START_AND_END,
397         fruData.data(), fruData.size(), responsePtr, respPayloadLength);
398 
399     if (rc != PLDM_SUCCESS)
400     {
401         return ccOnlyResponse(request, rc);
402     }
403 
404     return response;
405 }
406 
407 } // namespace fru
408 
409 } // namespace responder
410 
411 } // namespace pldm
412