fru-device: Add support to dynamically add FRU fields
This patch adds the ability to dynamically add or update FRU fields
at runtime, enhancing system flexibility in managing FRU data.
Previously, field modification was limited to the preassigned space
available in the FRU area. Any attempt to exceed this boundary would
fail:
Example:
```busctl set-property xyz.openbmc_project.FruDevice /xyz/openbmc_project/FruDevice/MDA_WCU_AI xyz.openbmc_project.FruDevice CHASSIS_SERIAL_NUMBER s "1234567890123"
busctl set-property xyz.openbmc_project.FruDevice /xyz/openbmc_project/FruDevice/MDA_WCU_AI xyz.openbmc_project.FruDevice CHASSIS_SERIAL_NUMBER s "12345678901234"
Failed to set property CHASSIS_SERIAL_NUMBER on interface xyz.openbmc_project.FruDevice: Invalid argument
hexdump -C /sys/bus/i2c/drivers/at24/8-0051/eeprom
```
With this patch, the FRU area can extend dynamically. All FRU areas are
repacked, and the FRU common header is updated without errors:
Example:
```
busctl set-property xyz.openbmc_project.FruDevice /xyz/openbmc_project/FruDevice/MDA_WCU_AI xyz.openbmc_project.FruDevice CHASSIS_SERIAL_NUMBER s "1234567890123"
busctl set-property xyz.openbmc_project.FruDevice /xyz/openbmc_project/FruDevice/MDA_WCU_AI xyz.openbmc_project.FruDevice CHASSIS_SERIAL_NUMBER s "12345678901234567890123456789"
```
Also check eeprom so that FRU areas get aligned properly.
Key changes:
- Introduced support through the `UpdateFruField` API.
- Utilizes `disassembleFruData` to parse existing FRU data into editable
fields.
- Applies field changes using `setField`, which handles updates or
additions.
- Reconstructs updated FRU binary data with `assembleFruData` before
committing it back.
TESTED=Build for Tiagopass & test on QEMU using below commands:
busctl call xyz.openbmc_project.FruDevice \
/xyz/openbmc_project/FruDevice/BMC_Storage_Module \
xyz.openbmc_project.FruDevice UpdateFruField ss \
"CHASSIS_INFO_AM10" "1234567890"
Readback:
busctl introspect xyz.openbmc_project.FruDevice \
/xyz/openbmc_project/FruDevice/BMC_Storage_Module
Change-Id: I5df2776211cb5cfd23570e479568da4717df3097
Signed-off-by: Naresh Solanki <naresh.solanki@9elements.com>
diff --git a/src/fru_device/fru_device.cpp b/src/fru_device/fru_device.cpp
index 8a24b6c..cbd2af0 100644
--- a/src/fru_device/fru_device.cpp
+++ b/src/fru_device/fru_device.cpp
@@ -75,8 +75,8 @@
boost::asio::io_context io;
// NOLINTEND(cppcoreguidelines-avoid-non-const-global-variables)
-bool updateFRUProperty(
- const std::string& updatePropertyReq, uint32_t bus, uint32_t address,
+bool updateFruProperty(
+ const std::string& propertyValue, uint32_t bus, uint32_t address,
const std::string& propertyName,
boost::container::flat_map<
std::pair<size_t, size_t>,
@@ -818,6 +818,28 @@
objServer.add_interface(productName, "xyz.openbmc_project.FruDevice");
dbusInterfaceMap[std::pair<size_t, size_t>(bus, address)] = iface;
+ if (ENABLE_FRU_UPDATE_PROPERTY)
+ {
+ iface->register_method(
+ "UpdateFruField",
+ [bus, address, &dbusInterfaceMap, &unknownBusObjectCount,
+ &powerIsOn, &objServer](const std::string& fieldName,
+ const std::string& fieldValue) {
+ // Update the property
+ if (!updateFruProperty(fieldValue, bus, address, fieldName,
+ dbusInterfaceMap, unknownBusObjectCount,
+ powerIsOn, objServer))
+ {
+ lg2::debug(
+ "Failed to Add Field: Name = {NAME}, Value = {VALUE}",
+ "NAME", fieldName, "VALUE", fieldValue);
+ return false;
+ }
+
+ return true;
+ });
+ }
+
for (auto& property : formattedFRU)
{
std::regex_replace(property.second.begin(), property.second.begin(),
@@ -841,7 +863,7 @@
if (strcmp(req.c_str(), resp.c_str()) != 0)
{
// call the method which will update
- if (updateFRUProperty(req, bus, address, propertyName,
+ if (updateFruProperty(req, bus, address, propertyName,
dbusInterfaceMap,
unknownBusObjectCount, powerIsOn,
objServer))
@@ -1177,19 +1199,8 @@
});
}
-// Details with example of Asset Tag Update
-// To find location of Product Info Area asset tag as per FRU specification
-// 1. Find product Info area starting offset (*8 - as header will be in
-// multiple of 8 bytes).
-// 2. Skip 3 bytes of product info area (like format version, area length,
-// and language code).
-// 3. Traverse manufacturer name, product name, product version, & product
-// serial number, by reading type/length code to reach the Asset Tag.
-// 4. Update the Asset Tag, reposition the product Info area in multiple of
-// 8 bytes. Update the Product area length and checksum.
-
-bool updateFRUProperty(
- const std::string& updatePropertyReq, uint32_t bus, uint32_t address,
+bool updateFruProperty(
+ const std::string& propertyValue, uint32_t bus, uint32_t address,
const std::string& propertyName,
boost::container::flat_map<
std::pair<size_t, size_t>,
@@ -1197,149 +1208,79 @@
size_t& unknownBusObjectCount, const bool& powerIsOn,
sdbusplus::asio::object_server& objServer)
{
- size_t updatePropertyReqLen = updatePropertyReq.length();
- if (updatePropertyReqLen == 1 || updatePropertyReqLen > 63)
+ lg2::debug(
+ "updateFruProperty called: FieldName = {NAME}, FieldValue = {VALUE}",
+ "NAME", propertyName, "VALUE", propertyValue);
+
+ // Validate field length: must be 2–63 characters
+ const size_t len = propertyValue.length();
+ if (len <= 1 || len > 63)
{
- std::cerr
- << "FRU field data cannot be of 1 char or more than 63 chars. "
- "Invalid Length "
- << updatePropertyReqLen << "\n";
+ lg2::debug(
+ "FRU field data must be between 2 and 63 characters. Invalid Length: {LEN}",
+ "LEN", len);
return false;
}
std::vector<uint8_t> fruData;
-
if (!getFruData(fruData, bus, address))
{
- std::cerr << "Failure getting FRU Data \n";
+ std::cerr << "Failure getting FRU Data from bus " << bus << ", address "
+ << address << "\n";
return false;
}
- struct FruArea fruAreaParams{};
-
- if (!findFruAreaLocationAndField(fruData, propertyName, fruAreaParams))
- {
- std::cerr << "findFruAreaLocationAndField failed \n";
- return false;
- }
-
- std::vector<uint8_t> restFRUAreaFieldsData;
- if (!copyRestFRUArea(fruData, propertyName, fruAreaParams,
- restFRUAreaFieldsData))
- {
- std::cerr << "copyRestFRUArea failed \n";
- return false;
- }
-
- // Push post update fru areas if any
- unsigned int nextFRUAreaLoc = 0;
- for (fruAreas nextFRUArea = fruAreas::fruAreaInternal;
- nextFRUArea <= fruAreas::fruAreaMultirecord; ++nextFRUArea)
- {
- unsigned int fruAreaLoc =
- fruData[getHeaderAreaFieldOffset(nextFRUArea)] * fruBlockSize;
- if ((fruAreaLoc > fruAreaParams.restFieldsEnd) &&
- ((nextFRUAreaLoc == 0) || (fruAreaLoc < nextFRUAreaLoc)))
- {
- nextFRUAreaLoc = fruAreaLoc;
- }
- }
- std::vector<uint8_t> restFRUAreasData;
- if (nextFRUAreaLoc != 0U)
- {
- std::copy_n(fruData.begin() + nextFRUAreaLoc,
- fruData.size() - nextFRUAreaLoc,
- std::back_inserter(restFRUAreasData));
- }
-
- // check FRU area size
- size_t fruAreaDataSize =
- ((fruAreaParams.updateFieldLoc - fruAreaParams.start + 1) +
- restFRUAreaFieldsData.size());
- size_t fruAreaAvailableSize = fruAreaParams.size - fruAreaDataSize;
- if ((updatePropertyReqLen + 1) > fruAreaAvailableSize)
- {
-#ifdef ENABLE_FRU_AREA_RESIZE
- size_t newFRUAreaSize = fruAreaDataSize + updatePropertyReqLen + 1;
- // round size to 8-byte blocks
- newFRUAreaSize =
- ((newFRUAreaSize - 1) / fruBlockSize + 1) * fruBlockSize;
- size_t newFRUDataSize =
- fruData.size() + newFRUAreaSize - fruAreaParams.size;
- fruData.resize(newFRUDataSize);
- fruAreaParams.size = newFRUAreaSize;
- fruAreaParams.end = fruAreaParams.start + fruAreaParams.size;
-#else
- std::cerr << "FRU field length: " << updatePropertyReqLen + 1
- << " should not be greater than available FRU area size: "
- << fruAreaAvailableSize << "\n";
- return false;
-#endif // ENABLE_FRU_AREA_RESIZE
- }
-
- // write new requested property field length and data
- constexpr uint8_t newTypeLenMask = 0xC0;
- fruData[fruAreaParams.updateFieldLoc] =
- static_cast<uint8_t>(updatePropertyReqLen | newTypeLenMask);
- fruAreaParams.updateFieldLoc++;
- std::copy(updatePropertyReq.begin(), updatePropertyReq.end(),
- fruData.begin() + fruAreaParams.updateFieldLoc);
-
- // Copy remaining data to main fru area - post updated fru field vector
- fruAreaParams.restFieldsLoc =
- fruAreaParams.updateFieldLoc + updatePropertyReqLen;
- size_t fruAreaDataEnd =
- fruAreaParams.restFieldsLoc + restFRUAreaFieldsData.size();
-
- std::copy(restFRUAreaFieldsData.begin(), restFRUAreaFieldsData.end(),
- fruData.begin() + fruAreaParams.restFieldsLoc);
-
- // Update final fru with new fru area length and checksum
- unsigned int nextFRUAreaNewLoc = updateFRUAreaLenAndChecksum(
- fruData, fruAreaParams.start, fruAreaDataEnd, fruAreaParams.end);
-
-#ifdef ENABLE_FRU_AREA_RESIZE
- ++nextFRUAreaNewLoc;
- ssize_t nextFRUAreaOffsetDiff =
- (nextFRUAreaNewLoc - nextFRUAreaLoc) / fruBlockSize;
- // Append rest FRU Areas if size changed and there were other sections after
- // updated one
- if ((nextFRUAreaOffsetDiff != 0) && (nextFRUAreaLoc != 0U))
- {
- std::copy(restFRUAreasData.begin(), restFRUAreasData.end(),
- fruData.begin() + nextFRUAreaNewLoc);
- // Update Common Header
- for (fruAreas nextFRUArea = fruAreas::fruAreaInternal;
- nextFRUArea <= fruAreas::fruAreaMultirecord; ++nextFRUArea)
- {
- unsigned int fruAreaOffsetField =
- getHeaderAreaFieldOffset(nextFRUArea);
- size_t curFRUAreaOffset = fruData[fruAreaOffsetField];
- if (curFRUAreaOffset > fruAreaParams.end)
- {
- fruData[fruAreaOffsetField] = static_cast<int8_t>(
- curFRUAreaOffset + nextFRUAreaOffsetDiff);
- }
- }
- // Calculate new checksum
- std::vector<uint8_t> headerFRUData;
- std::copy_n(fruData.begin(), 7, std::back_inserter(headerFRUData));
- size_t checksumVal = calculateChecksum(headerFRUData);
- fruData[7] = static_cast<uint8_t>(checksumVal);
- // fill zeros if FRU Area size decreased
- if (nextFRUAreaOffsetDiff < 0)
- {
- std::fill(fruData.begin() + nextFRUAreaNewLoc +
- restFRUAreasData.size(),
- fruData.end(), 0);
- }
- }
-#else
- // this is to avoid "unused variable" warning
- (void)nextFRUAreaNewLoc;
-#endif // ENABLE_FRU_AREA_RESIZE
if (fruData.empty())
{
+ std::cerr << "Empty FRU data\n";
+ return false;
+ }
+
+ // Extract area name (prefix before underscore)
+ fruAreas fruAreaToUpdate{};
+ std::string areaName = propertyName.substr(0, propertyName.find('_'));
+ if (!getAreaIdx(areaName, fruAreaToUpdate))
+ {
+ lg2::debug("Failed to get FRU area for property: {AREA}", "AREA",
+ areaName);
+ return false;
+ }
+
+ std::vector<std::vector<uint8_t>> areasData;
+ disassembleFruData(fruData, areasData);
+
+ std::vector<uint8_t>& areaData =
+ areasData[static_cast<size_t>(fruAreaToUpdate)];
+ if (areaData.empty())
+ {
+ // If ENABLE_FRU_AREA_RESIZE is not defined then return with failure
+#ifndef ENABLE_FRU_AREA_RESIZE
+ lg2::debug(
+ "FRU area {AREA} not present and ENABLE_FRU_AREA_RESIZE is not set. "
+ "Returning failure.",
+ "AREA", areaName);
+ return false;
+#endif
+ if (!createDummyArea(fruAreaToUpdate, areaData))
+ {
+ lg2::debug("Failed to create dummy area for {AREA}", "AREA",
+ areaName);
+ return false;
+ }
+ }
+
+ if (!setField(fruAreaToUpdate, areaData, propertyName, propertyValue))
+ {
+ lg2::debug("Failed to set field value for property: {PROPERTY}",
+ "PROPERTY", propertyName);
+ return false;
+ }
+
+ assembleFruData(fruData, areasData);
+
+ if (fruData.empty())
+ {
+ std::cerr << "FRU data is empty after assembly\n";
return false;
}
@@ -1349,7 +1290,6 @@
return false;
}
- // Rescan the bus so that GetRawFru dbus-call fetches updated values
rescanBusses(busMap, dbusInterfaceMap, unknownBusObjectCount, powerIsOn,
objServer);
return true;
diff --git a/src/fru_device/fru_utils.cpp b/src/fru_device/fru_utils.cpp
index 14ec8c1..67ea9ed 100644
--- a/src/fru_device/fru_utils.cpp
+++ b/src/fru_device/fru_utils.cpp
@@ -921,6 +921,420 @@
return ret;
}
+static bool updateHeadercksum(std::vector<uint8_t>& fruData)
+{
+ if (fruData.size() < 8)
+ {
+ lg2::debug("FRU data is too small to contain a valid header.");
+ return false;
+ }
+ uint8_t oldcksum = fruData[7];
+ std::span<const uint8_t> fruSpan{fruData};
+ uint8_t checksum = calculateChecksum(fruSpan.begin(), fruSpan.begin() + 7);
+ fruData[7] = checksum;
+
+ if (oldcksum != checksum)
+ {
+ lg2::debug(
+ "FRU header checksum updated from {OLD_CHECKSUM} to {NEW_CHECKSUM}",
+ "OLD_CHECKSUM", static_cast<int>(oldcksum), "NEW_CHECKSUM",
+ static_cast<int>(checksum));
+ }
+ return true;
+}
+
+bool updateAreacksum(std::vector<uint8_t>& fruArea)
+{
+ if (fruArea.size() < fruBlockSize)
+ {
+ lg2::debug("FRU area is too small to contain a valid header.");
+ return false;
+ }
+ if (fruArea.size() % fruBlockSize != 0)
+ {
+ lg2::debug("FRU area size is not a multiple of {SIZE} bytes.", "SIZE",
+ fruBlockSize);
+ return false;
+ }
+
+ uint8_t oldcksum = fruArea[fruArea.size() - 1];
+
+ fruArea[fruArea.size() - 1] =
+ 0; // Reset checksum byte to 0 before recalculating
+ fruArea[fruArea.size() - 1] = calculateChecksum(fruArea);
+
+ if (oldcksum != fruArea[fruArea.size() - 1])
+ {
+ lg2::debug(
+ "FRU area checksum updated from {OLD_CHECKSUM} to {NEW_CHECKSUM}",
+ "OLD_CHECKSUM", static_cast<int>(oldcksum), "NEW_CHECKSUM",
+ static_cast<int>(fruArea[fruArea.size() - 1]));
+ }
+ return true;
+}
+
+bool disassembleFruData(std::vector<uint8_t>& fruData,
+ std::vector<std::vector<uint8_t>>& areasData)
+{
+ if (fruData.size() < 8)
+ {
+ lg2::debug("FRU data is too small to contain a valid header.");
+ return false;
+ }
+
+ // Clear areasData before disassembling
+ areasData.clear();
+
+ // Iterate through all areas & store each area data in a vector.
+ for (fruAreas area = fruAreas::fruAreaInternal;
+ area <= fruAreas::fruAreaMultirecord; ++area)
+ {
+ size_t areaOffset = fruData[getHeaderAreaFieldOffset(area)];
+
+ if (areaOffset == 0)
+ {
+ // Store empty area data for areas that are not present
+ areasData.emplace_back();
+ continue; // Skip areas that are not present
+ }
+ areaOffset *= fruBlockSize; // Convert to byte offset
+ size_t areaSize = 0;
+ switch (area)
+ {
+ case fruAreas::fruAreaChassis:
+ case fruAreas::fruAreaBoard:
+ case fruAreas::fruAreaProduct:
+ areaSize = fruData[areaOffset + 1] *
+ fruBlockSize; // Area size in bytes
+ break;
+ case fruAreas::fruAreaInternal:
+ {
+ // Internal area size: It is difference between the next area
+ // offset and current area offset
+ size_t nextAreaOffset = 0;
+ for (uint8_t i = 1; i <= 4; i++)
+ {
+ nextAreaOffset = fruData[getHeaderAreaFieldOffset(
+ static_cast<fruAreas>(i))];
+ if (nextAreaOffset != 0)
+ {
+ break; // Found the next area offset
+ }
+ }
+ if (nextAreaOffset == 0)
+ {
+ return false; // No next area found, invalid FRU data
+ }
+ areaSize = (nextAreaOffset * fruBlockSize) -
+ areaOffset; // Area size in bytes
+ }
+ break;
+ case fruAreas::fruAreaMultirecord:
+ // Multirecord area size.
+ areaSize = fruData.size() - areaOffset; // Area size in bytes
+ break;
+ default:
+ lg2::error("Invalid FRU area: {AREA}", "AREA",
+ static_cast<int>(area));
+ return false;
+ }
+
+ if ((areaOffset + areaSize) > fruData.size())
+ {
+ lg2::error("Area offset + size exceeds FRU data size.");
+ return false;
+ }
+ std::vector<uint8_t> areaData(fruData.begin() + areaOffset,
+ fruData.begin() + areaOffset + areaSize);
+ // Update areaData checksum as well
+ switch (area)
+ {
+ case fruAreas::fruAreaChassis:
+ case fruAreas::fruAreaBoard:
+ case fruAreas::fruAreaProduct:
+ updateAreacksum(areaData);
+ break;
+ default:
+ // No checksum update for other areas
+ break;
+ }
+ areasData.push_back(areaData);
+ }
+ return true;
+}
+
+bool setField(const fruAreas& fruAreaToUpdate, std::vector<uint8_t>& areaData,
+ const std::string& propertyName, const std::string& value)
+{
+ const std::vector<std::string>* fruAreaFieldNames = nullptr;
+ int fieldLength = 0;
+ size_t fieldIndex = 0;
+ size_t endOfFieldMarker = 0;
+ std::string areaName = propertyName.substr(0, propertyName.find('_'));
+ std::string propertyNamePrefix = areaName + "_";
+
+ switch (fruAreaToUpdate)
+ {
+ case fruAreas::fruAreaChassis:
+ fruAreaFieldNames = &chassisFruAreas;
+ fieldIndex = 3;
+ break;
+ case fruAreas::fruAreaBoard:
+ fruAreaFieldNames = &boardFruAreas;
+ fieldIndex = 6;
+ break;
+ case fruAreas::fruAreaProduct:
+ fruAreaFieldNames = &productFruAreas;
+ fieldIndex = 3;
+ break;
+ default:
+ lg2::error("Invalid FRU area: {AREA}", "AREA",
+ static_cast<int>(fruAreaToUpdate));
+ return false;
+ }
+ bool found = false;
+ for (const auto& field : *fruAreaFieldNames)
+ {
+ fieldLength = getFieldLength(areaData[fieldIndex]);
+ if (fieldLength < 0)
+ {
+ // This should never happen. Insert dummy field.
+ areaData.insert(areaData.begin() + fieldIndex, 0xc0);
+ fieldLength = 0;
+ }
+
+ if (propertyNamePrefix + field == propertyName)
+ {
+ found = true;
+ break;
+ }
+ fieldIndex += 1 + fieldLength;
+ }
+ if (!found)
+ {
+ size_t pos = propertyName.find(fruCustomFieldName);
+ if (pos != std::string::npos)
+ {
+ // Get field after pos
+ std::string customFieldIdx =
+ propertyName.substr(pos + fruCustomFieldName.size());
+
+ // Check if customFieldIdx is a number
+ if (std::all_of(customFieldIdx.begin(), customFieldIdx.end(),
+ ::isdigit))
+ {
+ // Convert to integer
+ size_t customFieldIndex = std::stoi(customFieldIdx);
+ // Loop through the custom fields to find the correct index
+ for (size_t i = 0; i < customFieldIndex; i++)
+ {
+ fieldLength = getFieldLength(areaData[fieldIndex]);
+ if (fieldLength < 0)
+ {
+ // This should never happen. Insert dummy field.
+ areaData.insert(areaData.begin() + fieldIndex, 0xc0);
+ fieldLength = 0;
+ }
+ fieldIndex += 1 + fieldLength;
+ }
+ fieldIndex -= (fieldLength + 1);
+ fieldLength = getFieldLength(areaData[fieldIndex]);
+ found = true;
+ }
+ }
+ }
+
+ if (!found)
+ {
+ lg2::debug("Field {FIELD} not found in area {AREA}", "FIELD",
+ propertyName, "AREA", getFruAreaName(fruAreaToUpdate));
+ return false;
+ }
+ // Reset checksum byte to 0 before recalculating
+ areaData[areaData.size() - 1] = 0;
+
+ // Erase the existing field content.
+ areaData.erase(areaData.begin() + fieldIndex,
+ areaData.begin() + fieldIndex + fieldLength + 1);
+ // Insert the new field value
+ areaData.insert(areaData.begin() + fieldIndex, 0xc0 | value.size());
+ areaData.insert(areaData.begin() + fieldIndex + 1, value.begin(),
+ value.end());
+
+ // Locate the end of fields marker
+ endOfFieldMarker = fieldIndex + 1 + value.size();
+
+ for (; endOfFieldMarker < areaData.size();)
+ {
+ fieldLength = getFieldLength(areaData[endOfFieldMarker]);
+ if (fieldLength < 0)
+ {
+ break;
+ }
+ endOfFieldMarker += 1 + fieldLength;
+ }
+ if (endOfFieldMarker >= areaData.size())
+ {
+ lg2::debug("End of fields marker not found in area {AREA}.", "AREA",
+ getFruAreaName(fruAreaToUpdate));
+ return false;
+ }
+
+ // Resize areaData to endOfFieldMarker
+ areaData.resize(endOfFieldMarker + 1, 0); // Fill with zeros
+
+ // Calculate number of blocks needed
+ uint8_t numOfBlocks = (areaData.size() + fruBlockSize - 1) / fruBlockSize;
+ if (areaData.size() % fruBlockSize == 0)
+ {
+ numOfBlocks++;
+ }
+
+ // If ENABLE_FRU_AREA_RESIZE is not defined, we do not change the size
+ // of the areaData vector & return false.
+#ifndef ENABLE_FRU_AREA_RESIZE
+ uint8_t prevNumOfBlocks = areaData[1];
+ if (numOfBlocks != prevNumOfBlocks)
+ {
+ lg2::debug(
+ "FRU area {AREA} resize is disabled, cannot increase size from {OLD_SIZE} to {NEW_SIZE}",
+ "AREA", getFruAreaName(fruAreaToUpdate), "OLD_SIZE",
+ static_cast<int>(prevNumOfBlocks), "NEW_SIZE",
+ static_cast<int>(numOfBlocks));
+ return false;
+ }
+#endif // ENABLE_FRU_AREA_RESIZE
+
+ // Resize areaData as per numOfBlocks & fill with zeros
+ areaData.resize(numOfBlocks * fruBlockSize, 0);
+ // Update the length field
+ areaData[1] = numOfBlocks;
+ updateAreacksum(areaData);
+
+ return true;
+}
+
+bool assembleFruData(std::vector<uint8_t>& fruData,
+ const std::vector<std::vector<uint8_t>>& areasData)
+{
+ // Clear the existing FRU data
+ fruData.clear();
+ fruData.resize(8); // Start with the header size
+
+ // Write the header
+ fruData[0] = fruVersion; // Version
+ fruData[1] = 0; // Internal area offset
+ fruData[2] = 0; // Chassis area offset
+ fruData[3] = 0; // Board area offset
+ fruData[4] = 0; // Product area offset
+ fruData[5] = 0; // Multirecord area offset
+ fruData[6] = 0; // Pad
+ fruData[7] = 0; // Checksum (to be updated later)
+
+ size_t writeOffset = 8; // Start writing after the header
+
+ for (fruAreas area = fruAreas::fruAreaInternal;
+ area <= fruAreas::fruAreaMultirecord; ++area)
+ {
+ size_t areaBlockSize =
+ (areasData[static_cast<size_t>(area)].size() + fruBlockSize - 1) /
+ fruBlockSize; // Calculate block size
+
+ if (areasData[static_cast<size_t>(area)].empty())
+ {
+ lg2::debug("Skipping empty area: {AREA}", "AREA",
+ getFruAreaName(area));
+ continue; // Skip areas that are not present
+ }
+
+ // Set the area offset in the header
+ fruData[getHeaderAreaFieldOffset(area)] =
+ (writeOffset + fruBlockSize - 1) / fruBlockSize;
+
+ // Copy area data back to the main fruData vector
+ std::copy(areasData[static_cast<size_t>(area)].begin(),
+ areasData[static_cast<size_t>(area)].end(),
+ std::back_inserter(fruData));
+
+ fruData.resize(writeOffset + areaBlockSize * fruBlockSize,
+ 0); // Resize to accommodate the area data
+ writeOffset += areaBlockSize * fruBlockSize; // Update write offset
+ }
+
+ // Update the header checksum
+ if (!updateHeadercksum(fruData))
+ {
+ lg2::debug("Failed to update FRU header checksum.");
+ return false;
+ }
+ return true;
+}
+
+// Create a dummy area in areData variable based on specified fruArea
+bool createDummyArea(fruAreas fruArea, std::vector<uint8_t>& areaData)
+{
+ uint8_t numOfFields = 0;
+ uint8_t numOfBlocks = 0;
+ // Clear the areaData vector
+ areaData.clear();
+
+ // Set the version, length, and other fields
+ areaData.push_back(1); // Version 1
+ areaData.push_back(0); // Length (to be updated later)
+
+ switch (fruArea)
+ {
+ case fruAreas::fruAreaChassis:
+ areaData.push_back(0x00); // Chassis type
+ numOfFields = chassisFruAreas.size();
+ break;
+ case fruAreas::fruAreaBoard:
+ areaData.push_back(0x00); // Board language code (default)
+ areaData.insert(areaData.end(),
+ {0x00, 0x00,
+ 0x00}); // Board manufacturer date (default)
+ numOfFields = boardFruAreas.size();
+ break;
+ case fruAreas::fruAreaProduct:
+ areaData.push_back(0x00); // Product language code (default)
+ numOfFields = productFruAreas.size();
+ break;
+ default:
+ lg2::debug("Invalid FRU area to create: {AREA}", "AREA",
+ static_cast<int>(fruArea));
+ return false;
+ }
+
+ for (size_t i = 0; i < numOfFields; ++i)
+ {
+ areaData.push_back(0xc0); // Empty field type
+ }
+
+ // Add EndOfFields marker
+ areaData.push_back(0xC1);
+ numOfBlocks = (areaData.size() + fruBlockSize - 1) /
+ fruBlockSize; // Calculate number of blocks needed
+ areaData.resize(numOfBlocks * fruBlockSize, 0); // Fill with zeros
+ areaData[1] = numOfBlocks; // Update length field
+ updateAreacksum(areaData);
+
+ return true;
+}
+
+bool getAreaIdx(const std::string& areaName, fruAreas& fruAreaToUpdate)
+{
+ auto it = std::find(fruAreaNames.begin(), fruAreaNames.end(), areaName);
+
+ if (it == fruAreaNames.end())
+ {
+ lg2::debug("Can't parse index for area name: {AREA}", "AREA", areaName);
+ return false;
+ }
+ fruAreaToUpdate = static_cast<fruAreas>(it - fruAreaNames.begin());
+
+ return true;
+}
+
// Iterate FruArea Names and find start and size of the fru area that contains
// the propertyName and the field start location for the property. fruAreaParams
// struct values fruAreaStart, fruAreaSize, fruAreaEnd, fieldLoc values gets
diff --git a/src/fru_device/fru_utils.hpp b/src/fru_device/fru_utils.hpp
index ba9ba72..0f8b032 100644
--- a/src/fru_device/fru_utils.hpp
+++ b/src/fru_device/fru_utils.hpp
@@ -213,3 +213,18 @@
bool getFruData(std::vector<uint8_t>& fruData, uint32_t bus, uint32_t address);
bool isFieldEditable(std::string_view fieldName);
+
+bool getAreaIdx(const std::string& areaName, fruAreas& fruAreaToUpdate);
+
+bool updateAreacksum(std::vector<uint8_t>& fruArea);
+
+bool disassembleFruData(std::vector<uint8_t>& fruData,
+ std::vector<std::vector<uint8_t>>& areasData);
+
+bool createDummyArea(fruAreas fruArea, std::vector<uint8_t>& areaData);
+
+bool assembleFruData(std::vector<uint8_t>& fruData,
+ const std::vector<std::vector<uint8_t>>& areasData);
+
+bool setField(const fruAreas& fruAreaToUpdate, std::vector<uint8_t>& areaData,
+ const std::string& propertyName, const std::string& value);
diff --git a/test/test_fru-utils.cpp b/test/test_fru-utils.cpp
index fbb85b2..a4c698c 100644
--- a/test/test_fru-utils.cpp
+++ b/test/test_fru-utils.cpp
@@ -483,3 +483,129 @@
EXPECT_FALSE(isFieldEditable("ABCD_PRODUCT"));
EXPECT_FALSE(isFieldEditable("ABCD_BOARD"));
}
+
+TEST(GetAreaIdxTest, InvalidAreaReturnsInvalid)
+{
+ // Validates that false is returned for an invalid area.
+ const std::string invalidArea = "INVALID_AREA";
+ fruAreas areaIdx = fruAreas::fruAreaInternal;
+ EXPECT_FALSE(getAreaIdx(invalidArea, areaIdx));
+}
+
+TEST(GetAreaIdxTest, ValidAreaReturnsValid)
+{
+ const std::vector<std::string> validAreas = {"INTERNAL", "CHASSIS", "BOARD",
+ "PRODUCT", "MULTIRECORD"};
+
+ const std::array<fruAreas, 5> expectedAreas = {
+ fruAreas::fruAreaInternal, fruAreas::fruAreaChassis,
+ fruAreas::fruAreaBoard, fruAreas::fruAreaProduct,
+ fruAreas::fruAreaMultirecord};
+
+ for (size_t i = 0; i < validAreas.size(); ++i)
+ {
+ fruAreas testArea = fruAreas::fruAreaInternal; // default init
+ EXPECT_TRUE(getAreaIdx(validAreas[i], testArea));
+ EXPECT_EQ(testArea, expectedAreas[i]);
+ }
+}
+
+TEST(UpdateAreaChecksumTest, EmptyArea)
+{
+ // Validates that an empty area does not cause any issues.
+ std::vector<uint8_t> fruArea = {};
+ EXPECT_FALSE(updateAreacksum(fruArea));
+}
+
+TEST(UpdateAreaChecksumTest, ValidArea)
+{
+ // Validates that a valid area updates the checksum correctly.
+ std::vector<uint8_t> fruArea = {0x01, 0x00, 0x01, 0x02,
+ 0x03, 0x04, 0x00, 0x00};
+ EXPECT_TRUE(updateAreacksum(fruArea));
+ EXPECT_EQ(fruArea.back(), 0xf5);
+}
+
+TEST(UpdateAreaChecksumTest, InvalidArea)
+{
+ // Validates that an invalid area does not update the checksum.
+ std::vector<uint8_t> fruArea = {0x01, 0x00, 0x01, 0x02, 0x03,
+ 0x04, 0x00, 0x00, 0xAA};
+ EXPECT_FALSE(updateAreacksum(fruArea));
+}
+
+TEST(DisassembleFruDataTest, EmptyData)
+{
+ // Validates that an empty data vector returns false.
+ std::vector<uint8_t> fruData = {};
+ std::vector<std::vector<uint8_t>> areasData;
+ EXPECT_FALSE(disassembleFruData(fruData, areasData));
+}
+
+TEST(DisassembleFruDataTest, ValidData)
+{
+ // Taken from qemu fby35_bmc_fruid
+ std::vector<uint8_t> fruData = {
+ 0x01, 0x00, 0x00, 0x01, 0x0d, 0x00, 0x00, 0xf1, 0x01, 0x0c, 0x00, 0x36,
+ 0xe6, 0xd0, 0xc6, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0xd2, 0x42, 0x4d,
+ 0x43, 0x20, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x20, 0x4d, 0x6f,
+ 0x64, 0x75, 0x6c, 0x65, 0xcd, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58,
+ 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0xce, 0x58, 0x58, 0x58, 0x58, 0x58,
+ 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0xc3, 0x31, 0x2e,
+ 0x30, 0xc9, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0xd2,
+ 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58,
+ 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0xc1, 0x39, 0x01, 0x0c, 0x00, 0xc6,
+ 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0xd2, 0x59, 0x6f, 0x73, 0x65, 0x6d,
+ 0x69, 0x74, 0x65, 0x20, 0x56, 0x33, 0x2e, 0x35, 0x20, 0x45, 0x56, 0x54,
+ 0x32, 0xce, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58,
+ 0x58, 0x58, 0x58, 0x58, 0xc4, 0x45, 0x56, 0x54, 0x32, 0xcd, 0x58, 0x58,
+ 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0xc7,
+ 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0xc3, 0x31, 0x2e, 0x30, 0xc9,
+ 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0xc8, 0x43, 0x6f,
+ 0x6e, 0x66, 0x69, 0x67, 0x20, 0x41, 0xc1, 0x45,
+ };
+
+ std::vector<std::vector<uint8_t>> areasData;
+ EXPECT_TRUE(disassembleFruData(fruData, areasData));
+ EXPECT_GT(areasData.size(), 1);
+
+ // Internal area is size is zero
+ EXPECT_EQ(areasData[static_cast<size_t>(fruAreas::fruAreaInternal)].size(),
+ 0);
+ // Chassis are is zero
+ EXPECT_EQ(areasData[static_cast<size_t>(fruAreas::fruAreaChassis)].size(),
+ 0);
+ // Board area is 96 byte
+ EXPECT_EQ(areasData[static_cast<size_t>(fruAreas::fruAreaBoard)].size(),
+ 96);
+ // Product area is 96 byte
+ EXPECT_EQ(areasData[static_cast<size_t>(fruAreas::fruAreaProduct)].size(),
+ 96);
+
+ // Multi-record area is 64 byte.
+ EXPECT_EQ(
+ areasData[static_cast<size_t>(fruAreas::fruAreaMultirecord)].size(), 0);
+
+ EXPECT_TRUE(setField(fruAreas::fruAreaBoard,
+ areasData[static_cast<size_t>(fruAreas::fruAreaBoard)],
+ "BOARD_INFO_AM1", "01"));
+ EXPECT_TRUE(setField(fruAreas::fruAreaBoard,
+ areasData[static_cast<size_t>(fruAreas::fruAreaBoard)],
+ "BOARD_INFO_AM2", "MAC: 3C:6D:66:14:C8:7A"));
+ // set Product fields
+ EXPECT_TRUE(
+ setField(fruAreas::fruAreaProduct,
+ areasData[static_cast<size_t>(fruAreas::fruAreaProduct)],
+ "PRODUCT_ASSET_TAG", "123"));
+ EXPECT_TRUE(
+ setField(fruAreas::fruAreaProduct,
+ areasData[static_cast<size_t>(fruAreas::fruAreaProduct)],
+ "PRODUCT_PART_NUMBER", "699-13809-0404-600"));
+ EXPECT_TRUE(
+ setField(fruAreas::fruAreaProduct,
+ areasData[static_cast<size_t>(fruAreas::fruAreaProduct)],
+ "PRODUCT_PRODUCT_NAME", "OpenBMC-test1"));
+
+ std::vector<uint8_t> assembledData;
+ EXPECT_TRUE(assembleFruData(assembledData, areasData));
+}