cpld: lattice: add pre-page write with read-back verify
Observed that lattice CPLD page data may shift during update if other
processes issue I2C commands (e.g., i2cdetect) to the CPLD update
address.
Add support for setting page address and implement page-level write
followed by read-back verification. This helps detect and prevent
unintended page shifts caused by external I2C commands during the
update process.
Tested on Catalina:
1. Trigger the fw update via redfish.
```
root@bmc:~# curl -k -u root:0penBmc -H "Content-Type:multipart/form-data" -X POST -F UpdateParameters="{\"Targets\":[\"/redfish/v1/UpdateService/FirmwareInventory/Catalina_PDB_CPLD_2161\"],\"@Redfish.OperationApplyTime\":\"OnReset\"};type=application/json" -F "UpdateFile=@F0N_PDB_CPLD_00021100.pldm;type=application/octet-stream" https://127.0.0.1/redfish/v1/UpdateService/update-multipart
{
"@odata.id": "/redfish/v1/TaskService/Tasks/0",
"@odata.type": "#Task.v1_4_3.Task",
"HidePayload": false,
"Id": "0",
"Messages": [
{
"@odata.type": "#Message.v1_1_1.Message",
"Message": "The task with Id '0' has started.",
"MessageArgs": [
"0"
],
"MessageId": "TaskEvent.1.0.TaskStarted",
"MessageSeverity": "OK",
"Resolution": "None."
}
],
"Name": "Task 0",
"Payload": {
"HttpHeaders": [],
"HttpOperation": "POST",
"TargetUri": "/redfish/v1/UpdateService/update-multipart"
},
"PercentComplete": 0,
"StartTime": "2025-09-01T08:47:55+00:00",
"TaskMonitor": "/redfish/v1/TaskService/TaskMonitors/0",
"TaskState": "Running",
"TaskStatus": "OK"
}
```
2. Check the task status completed without error.
```
root@bmc:~# curl -u root:0penBmc -k https://127.0.0.1/redfish/v1/TaskService/Tasks/0
{
"@odata.id": "/redfish/v1/TaskService/Tasks/0",
"@odata.type": "#Task.v1_4_3.Task",
"EndTime": "2025-09-01T08:48:32+00:00",
"HidePayload": false,
"Id": "0",
"Messages": [
{
"@odata.type": "#Message.v1_1_1.Message",
"Message": "The task with Id '0' has started.",
"MessageArgs": [
"0"
],
"MessageId": "TaskEvent.1.0.TaskStarted",
"MessageSeverity": "OK",
"Resolution": "None."
},
{
"@odata.type": "#Message.v1_1_1.Message",
"Message": "The task with Id '0' has changed to progress 1 percent complete.",
"MessageArgs": [
"0",
"1"
],
"MessageId": "TaskEvent.1.0.TaskProgressChanged",
"MessageSeverity": "OK",
"Resolution": "None."
},
{
"@odata.type": "#Message.v1_1_1.Message",
"Message": "The task with Id '0' has changed to progress 10 percent complete.",
"MessageArgs": [
"0",
"10"
],
"MessageId": "TaskEvent.1.0.TaskProgressChanged",
"MessageSeverity": "OK",
"Resolution": "None."
},
{
"@odata.type": "#Message.v1_1_1.Message",
"Message": "The task with Id '0' has changed to progress 15 percent complete.",
"MessageArgs": [
"0",
"15"
],
"MessageId": "TaskEvent.1.0.TaskProgressChanged",
"MessageSeverity": "OK",
"Resolution": "None."
},
{
"@odata.type": "#Message.v1_1_1.Message",
"Message": "The task with Id '0' has changed to progress 20 percent complete.",
"MessageArgs": [
"0",
"20"
],
"MessageId": "TaskEvent.1.0.TaskProgressChanged",
"MessageSeverity": "OK",
"Resolution": "None."
},
{
"@odata.type": "#Message.v1_1_1.Message",
"Message": "The task with Id '0' has changed to progress 25 percent complete.",
"MessageArgs": [
"0",
"25"
],
"MessageId": "TaskEvent.1.0.TaskProgressChanged",
"MessageSeverity": "OK",
"Resolution": "None."
},
{
"@odata.type": "#Message.v1_1_1.Message",
"Message": "The task with Id '0' has changed to progress 30 percent complete.",
"MessageArgs": [
"0",
"30"
],
"MessageId": "TaskEvent.1.0.TaskProgressChanged",
"MessageSeverity": "OK",
"Resolution": "None."
},
{
"@odata.type": "#Message.v1_1_1.Message",
"Message": "The task with Id '0' has changed to progress 40 percent complete.",
"MessageArgs": [
"0",
"40"
],
"MessageId": "TaskEvent.1.0.TaskProgressChanged",
"MessageSeverity": "OK",
"Resolution": "None."
},
{
"@odata.type": "#Message.v1_1_1.Message",
"Message": "The task with Id '0' has changed to progress 50 percent complete.",
"MessageArgs": [
"0",
"50"
],
"MessageId": "TaskEvent.1.0.TaskProgressChanged",
"MessageSeverity": "OK",
"Resolution": "None."
},
{
"@odata.type": "#Message.v1_1_1.Message",
"Message": "The task with Id '0' has changed to progress 60 percent complete.",
"MessageArgs": [
"0",
"60"
],
"MessageId": "TaskEvent.1.0.TaskProgressChanged",
"MessageSeverity": "OK",
"Resolution": "None."
},
{
"@odata.type": "#Message.v1_1_1.Message",
"Message": "The task with Id '0' has changed to progress 70 percent complete.",
"MessageArgs": [
"0",
"70"
],
"MessageId": "TaskEvent.1.0.TaskProgressChanged",
"MessageSeverity": "OK",
"Resolution": "None."
},
{
"@odata.type": "#Message.v1_1_1.Message",
"Message": "The task with Id '0' has changed to progress 80 percent complete.",
"MessageArgs": [
"0",
"80"
],
"MessageId": "TaskEvent.1.0.TaskProgressChanged",
"MessageSeverity": "OK",
"Resolution": "None."
},
{
"@odata.type": "#Message.v1_1_1.Message",
"Message": "The task with Id '0' has changed to progress 90 percent complete.",
"MessageArgs": [
"0",
"90"
],
"MessageId": "TaskEvent.1.0.TaskProgressChanged",
"MessageSeverity": "OK",
"Resolution": "None."
},
{
"@odata.type": "#Message.v1_1_1.Message",
"Message": "The task with Id '0' has changed to progress 100 percent complete.",
"MessageArgs": [
"0",
"100"
],
"MessageId": "TaskEvent.1.0.TaskProgressChanged",
"MessageSeverity": "OK",
"Resolution": "None."
},
{
"@odata.type": "#Message.v1_1_1.Message",
"Message": "The task with Id '0' has completed.",
"MessageArgs": [
"0"
],
"MessageId": "TaskEvent.1.0.TaskCompletedOK",
"MessageSeverity": "OK",
"Resolution": "None."
}
],
"Name": "Task 0",
"Payload": {
"HttpHeaders": [],
"HttpOperation": "POST",
"JsonBody": "null",
"TargetUri": "/redfish/v1/UpdateService/update-multipart"
},
"PercentComplete": 100,
"StartTime": "2025-09-01T08:47:55+00:00",
"TaskState": "Completed",
"TaskStatus": "OK"
}
```
3. Check CPLD version after a AC cycle
```
root@bmc:~# curl -u root:0penBmc -k https://127.0.0.1/redfish/v1/UpdateService/FirmwareInventory/Catalina_PDB_CPLD_7118
{
"@odata.id": "/redfish/v1/UpdateService/FirmwareInventory/Catalina_PDB_CPLD_7118",
"@odata.type": "#SoftwareInventory.v1_1_0.SoftwareInventory",
"Description": "Unknown image",
"Id": "Catalina_PDB_CPLD_7118",
"Name": "Software Inventory",
"Status": {
"Health": "OK",
"HealthRollup": "OK",
"State": "Enabled"
},
"Updateable": true,
"Version": "00021100"
}
```
Change-Id: I10f26817c4784814e86f186dc947a9a24d070b93
Signed-off-by: Potin Lai <potin.lai@quantatw.com>
diff --git a/cpld/lattice/lattice.cpp b/cpld/lattice/lattice.cpp
index b3802f4..2fb7c86 100644
--- a/cpld/lattice/lattice.cpp
+++ b/cpld/lattice/lattice.cpp
@@ -3,6 +3,7 @@
#include <phosphor-logging/lg2.hpp>
#include <algorithm>
+#include <cstddef>
#include <fstream>
#include <map>
#include <numeric>
@@ -37,7 +38,9 @@
commandResetConfigFlash = 0x46,
commandProgramDone = 0x5E,
commandProgramPage = 0x70,
+ commandReadPage = 0x73,
commandEnableConfigMode = 0x74,
+ commandSetPageAddress = 0xB4,
commandReadFwVersion = 0xC0,
commandProgramUserCode = 0xC2,
commandReadDeviceId = 0xE0,
@@ -435,6 +438,90 @@
co_return i2cInterface.sendReceive(request, response);
}
+sdbusplus::async::task<bool> CpldLatticeManager::programSinglePage(
+ uint16_t pageOffset, std::span<const uint8_t> pageData)
+{
+ // Set Page Offset
+ std::vector<uint8_t> emptyResp(0);
+ std::vector<uint8_t> setPageAddrCmd = {
+ commandSetPageAddress, 0x0, 0x0, 0x0, 0x00, 0x00, 0x00, 0x00};
+ setPageAddrCmd[6] = static_cast<uint8_t>(pageOffset >> 8); // high byte
+ setPageAddrCmd[7] = static_cast<uint8_t>(pageOffset); // low byte
+
+ // NOLINTNEXTLINE(clang-analyzer-core.uninitialized.Branch)
+ bool success = co_await i2cInterface.sendReceive(
+ setPageAddrCmd.data(), setPageAddrCmd.size(), nullptr, 0);
+ if (!success)
+ {
+ lg2::error("Write page address failed");
+ co_return false;
+ }
+
+ // Write Page Data
+ constexpr uint8_t pageCount = 1;
+ std::vector<uint8_t> writeCmd = {commandProgramPage, 0x0, 0x0, pageCount};
+ writeCmd.insert(writeCmd.end(), pageData.begin(), pageData.end());
+
+ success = co_await i2cInterface.sendReceive(writeCmd.data(),
+ writeCmd.size(), nullptr, 0);
+ if (!success)
+ {
+ lg2::error("Write page data failed");
+ co_return false;
+ }
+
+ co_await sdbusplus::async::sleep_for(ctx, std::chrono::microseconds(200));
+
+ if (!(co_await waitBusyAndVerify()))
+ {
+ lg2::error("Wait busy and verify fail");
+ co_return false;
+ }
+
+ co_return true;
+}
+
+sdbusplus::async::task<bool> CpldLatticeManager::verifySinglePage(
+ uint16_t pageOffset, std::span<const uint8_t> pageData)
+{
+ // Set Page Offset
+ std::vector<uint8_t> emptyResp(0);
+ std::vector<uint8_t> setPageAddrCmd = {
+ commandSetPageAddress, 0x0, 0x0, 0x0, 0x00, 0x00, 0x00, 0x00};
+ setPageAddrCmd[6] = static_cast<uint8_t>(pageOffset >> 8); // high byte
+ setPageAddrCmd[7] = static_cast<uint8_t>(pageOffset); // low byte
+
+ if (!i2cInterface.sendReceive(setPageAddrCmd, emptyResp))
+ {
+ lg2::error("Write page address failed");
+ co_return false;
+ }
+
+ // Read Page Data
+ constexpr uint8_t pageCount = 1;
+ std::vector<uint8_t> readData(pageData.size());
+ std::vector<uint8_t> readCmd = {commandReadPage, 0x0, 0x0, pageCount};
+
+ if (!i2cInterface.sendReceive(readCmd, readData))
+ {
+ lg2::error("Read page data failed");
+ co_return false;
+ }
+
+ constexpr size_t pageSize = 16;
+ auto mismatch_pair =
+ std::mismatch(pageData.begin(), pageData.end(), readData.begin());
+ if (mismatch_pair.first != pageData.end())
+ {
+ size_t idx = std::distance(pageData.begin(), mismatch_pair.first);
+ lg2::error("Verify failed at {INDEX}", "INDEX",
+ ((static_cast<size_t>(pageSize * pageOffset)) + idx));
+ co_return false;
+ }
+
+ co_return true;
+}
+
sdbusplus::async::task<bool> CpldLatticeManager::writeProgramPage()
{
/*
@@ -442,47 +529,54 @@
used to program the NVCM0/CFG or
NVCM1/UFM.
*/
- std::vector<uint8_t> request = {commandProgramPage, 0x0, 0x0, 0x01};
- std::vector<uint8_t> response;
size_t iterSize = 16;
- for (size_t i = 0; i < fwInfo.cfgData.size(); i += iterSize)
+ for (size_t i = 0; (i * iterSize) < fwInfo.cfgData.size(); i++)
{
+ size_t byteOffset = i * iterSize;
double progressRate =
- ((double(i) / double(fwInfo.cfgData.size())) * 100);
+ ((double(byteOffset) / double(fwInfo.cfgData.size())) * 100);
std::cout << "Update :" << std::fixed << std::dec
<< std::setprecision(2) << progressRate << "% \r";
- uint8_t len = ((i + iterSize) < fwInfo.cfgData.size())
+ uint8_t len = ((byteOffset + iterSize) < fwInfo.cfgData.size())
? iterSize
- : (fwInfo.cfgData.size() - i);
- std::vector<uint8_t> data = request;
+ : (fwInfo.cfgData.size() - byteOffset);
+ auto pageData = std::vector<uint8_t>(
+ fwInfo.cfgData.begin() + static_cast<std::ptrdiff_t>(byteOffset),
+ fwInfo.cfgData.begin() +
+ static_cast<std::ptrdiff_t>(byteOffset + len));
- data.insert(
- data.end(), fwInfo.cfgData.begin() + static_cast<std::ptrdiff_t>(i),
- fwInfo.cfgData.begin() + static_cast<std::ptrdiff_t>(i + len));
-
- if (!i2cInterface.sendReceive(data, response))
+ size_t retry = 0;
+ const size_t maxWriteRetry = 10;
+ while (retry < maxWriteRetry)
{
- lg2::error("Failed to send program page request. {CURRENT}",
- "CURRENT", uint32ToHexStr(i));
- co_return false;
+ if (!(co_await programSinglePage(i, pageData)))
+ {
+ retry++;
+ continue;
+ }
+
+ if (!(co_await verifySinglePage(i, pageData)))
+ {
+ retry++;
+ continue;
+ }
+
+ break;
}
- /*
- Reference spec
- Important! If don't sleep, it will take a long time to update.
- */
- co_await sdbusplus::async::sleep_for(ctx,
- std::chrono::microseconds(200));
-
- if (!(co_await waitBusyAndVerify()))
+ if (retry >= maxWriteRetry)
{
- lg2::error("Wait busy and verify fail");
+ lg2::error("Program and verify page failed");
co_return false;
}
+ }
- data.clear();
+ if (!(co_await waitBusyAndVerify()))
+ {
+ lg2::error("Wait busy and verify fail");
+ co_return false;
}
co_return true;
diff --git a/cpld/lattice/lattice.hpp b/cpld/lattice/lattice.hpp
index 0a81917..8df7d84 100644
--- a/cpld/lattice/lattice.hpp
+++ b/cpld/lattice/lattice.hpp
@@ -77,4 +77,8 @@
sdbusplus::async::task<bool> readStatusReg(uint8_t& statusReg);
sdbusplus::async::task<bool> waitBusyAndVerify();
sdbusplus::async::task<bool> readUserCode(uint32_t& userCode);
+ sdbusplus::async::task<bool> programSinglePage(
+ uint16_t pageOffset, std::span<const uint8_t> pageData);
+ sdbusplus::async::task<bool> verifySinglePage(
+ uint16_t pageOffset, std::span<const uint8_t> pageData);
};