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