cpld: Virtualize CpldLatticeManager for XO3/XO5 separation
This change makes `CpldLatticeManager` a virtual base class, allowing
other classes to inherit from it. This refactoring enables a cleaner
separation of XO3 and XO5 implementations while sharing common CPLD
management logic.
Test on harma:
```
1. Check firmware info
curl -k -u root:0penBmc -X GET
https://10.10.15.8/redfish/v1/UpdateService/FirmwareInventory/Harma_MB_CPLD_5688
{
"@odata.id": "/redfish/v1/UpdateService/FirmwareInventory/Harma_MB_CPLD_5688",
"@odata.type": "#SoftwareInventory.v1_1_0.SoftwareInventory",
"Description": "Unknown image",
"Id": "Harma_MB_CPLD_5688",
"Name": "Software Inventory",
"Status": {
"Health": "OK",
"HealthRollup": "OK",
"State": "Enabled"
},
"Updateable": true,
"Version": "00000220"
}
2. Trigger Update
curl -k -u root:0penBmc \
-H "Content-Type:multipart/form-data" \
-X POST \
-F UpdateParameters="{\"Targets\":[\"${targetpath}\"], \
\"@Redfish.OperationApplyTime\":\"Immediate\"};type=application/json" \
-F "UpdateFile=@${fwpath};type=application/octet-stream" \
https://${bmc}/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-08-13T07:22:06+00:00",
"TaskMonitor": "/redfish/v1/TaskService/TaskMonitors/0",
"TaskState": "Running",
"TaskStatus": "OK"
}
3. After AC cycle check firmware info again
curl -k -u root:0penBmc -X GET
https://10.10.15.8/redfish/v1/UpdateService/FirmwareInventory/Harma_MB_CPLD_5688
{
"@odata.id": "/redfish/v1/UpdateService/FirmwareInventory/Harma_MB_CPLD_5688",
"@odata.type": "#SoftwareInventory.v1_1_0.SoftwareInventory",
"Description": "Unknown image",
"Id": "Harma_MB_CPLD_5688",
"Name": "Software Inventory",
"Status": {
"Health": "OK",
"HealthRollup": "OK",
"State": "Enabled"
},
"Updateable": true,
"Version": "00000224"
}
```
Change-Id: Ic7265dbeeb9f93d4f466cba75ca38fc86342c689
Signed-off-by: Daniel Hsu <Daniel-Hsu@quantatw.com>
diff --git a/cpld/lattice/lattice_xo3_cpld.cpp b/cpld/lattice/lattice_xo3_cpld.cpp
new file mode 100644
index 0000000..ee95ba4
--- /dev/null
+++ b/cpld/lattice/lattice_xo3_cpld.cpp
@@ -0,0 +1,384 @@
+#include "lattice_xo3_cpld.hpp"
+
+#include <phosphor-logging/lg2.hpp>
+
+#include <fstream>
+#include <vector>
+
+namespace phosphor::software::cpld
+{
+
+sdbusplus::async::task<bool> LatticeXO3CPLD::readDeviceId()
+{
+ std::vector<uint8_t> request = {commandReadDeviceId, 0x0, 0x0, 0x0};
+ std::vector<uint8_t> response = {0, 0, 0, 0};
+
+ if (!i2cInterface.sendReceive(request, response))
+ {
+ lg2::error(
+ "Fail to read device Id. Please check the I2C bus and address.");
+ co_return false;
+ }
+
+ auto chipWantToUpdate = std::find_if(
+ supportedDeviceMap.begin(), supportedDeviceMap.end(),
+ [this](const auto& pair) {
+ auto chipModel =
+ getLatticeChipStr(pair.first, latticeStringType::modelString);
+ return chipModel == this->chip;
+ });
+
+ if (chipWantToUpdate != supportedDeviceMap.end() &&
+ chipWantToUpdate->second.deviceId == response)
+ {
+ if (chip.rfind("LCMXO3D", 0) == 0)
+ {
+ isLCMXO3D = true;
+ if (!target.empty() && target != "CFG0" && target != "CFG1")
+ {
+ lg2::error("Unknown target. Only CFG0 and CFG1 are supported.");
+ co_return false;
+ }
+ }
+
+ lg2::debug("Device ID match with chip. Chip name: {CHIPNAME}",
+ "CHIPNAME", chip);
+ co_return true;
+ }
+
+ lg2::error("The device id doesn't match with chip.");
+ co_return false;
+}
+
+sdbusplus::async::task<bool> LatticeXO3CPLD::eraseFlash()
+{
+ std::vector<uint8_t> request;
+ std::vector<uint8_t> response;
+
+ if (isLCMXO3D)
+ {
+ /*
+ Erase the different internal
+ memories. The bit in YYY defines
+ which memory is erased in Flash
+ access mode.
+ Bit 1=Enable
+ 8 Erase CFG0
+ 9 Erase CFG1
+ 10 Erase UFM0
+ 11 Erase UFM1
+ 12 Erase UFM2
+ 13 Erase UFM3
+ 14 Erase CSEC
+ 15 Erase USEC
+ 16 Erase PUBKEY
+ 17 Erase AESKEY
+ 18 Erase FEA
+ 19 Reserved
+ commandEraseFlash = 0x0E, 0Y YY 00
+ */
+ if (target.empty() || target == "CFG0")
+ {
+ request = {commandEraseFlash, 0x00, 0x01, 0x00};
+ }
+ else if (target == "CFG1")
+ {
+ request = {commandEraseFlash, 0x00, 0x02, 0x00};
+ }
+ else
+ {
+ lg2::error("Error: unknown target.");
+ co_return false;
+ }
+ }
+ else
+ {
+ request = {commandEraseFlash, 0xC, 0x0, 0x0};
+ }
+
+ if (!i2cInterface.sendReceive(request, response))
+ {
+ lg2::error("Failed to send erase flash request.");
+ co_return false;
+ }
+
+ if (!(co_await waitBusyAndVerify()))
+ {
+ lg2::error("Wait busy and verify fail");
+ co_return false;
+ }
+ co_await sdbusplus::async::sleep_for(ctx, waitBusyTime);
+ co_return true;
+}
+
+sdbusplus::async::task<bool> LatticeXO3CPLD::writeProgramPage()
+{
+ /*
+ Program one NVCM/Flash page. Can be
+ used to program the NVCM0/CFG or
+ NVCM1/UFM.
+ */
+ size_t iterSize = 16;
+
+ for (size_t i = 0; (i * iterSize) < fwInfo.cfgData.size(); i++)
+ {
+ size_t byteOffset = i * iterSize;
+ double progressRate =
+ ((double(byteOffset) / double(fwInfo.cfgData.size())) * 100);
+ std::cout << "Update :" << std::fixed << std::dec
+ << std::setprecision(2) << progressRate << "% \r";
+
+ uint8_t len = ((byteOffset + iterSize) < fwInfo.cfgData.size())
+ ? iterSize
+ : (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));
+
+ size_t retry = 0;
+ const size_t maxWriteRetry = 10;
+ while (retry < maxWriteRetry)
+ {
+ if (!(co_await programSinglePage(i, pageData)))
+ {
+ retry++;
+ continue;
+ }
+
+ if (!(co_await verifySinglePage(i, pageData)))
+ {
+ retry++;
+ continue;
+ }
+
+ break;
+ }
+
+ if (retry >= maxWriteRetry)
+ {
+ lg2::error("Program and verify page failed");
+ co_return false;
+ }
+ }
+
+ if (!(co_await waitBusyAndVerify()))
+ {
+ lg2::error("Wait busy and verify fail");
+ co_return false;
+ }
+
+ co_return true;
+}
+
+sdbusplus::async::task<bool> LatticeXO3CPLD::readUserCode(uint32_t& userCode)
+{
+ constexpr size_t resSize = 4;
+ std::vector<uint8_t> request = {commandReadFwVersion, 0x0, 0x0, 0x0};
+ std::vector<uint8_t> response(resSize, 0);
+
+ if (!i2cInterface.sendReceive(request, response))
+ {
+ lg2::error("Failed to send read user code request.");
+ co_return false;
+ }
+
+ for (size_t i = 0; i < resSize; i++)
+ {
+ userCode |= response.at(i) << ((3 - i) * 8);
+ }
+ co_return true;
+}
+
+sdbusplus::async::task<bool> LatticeXO3CPLD::programUserCode()
+{
+ std::vector<uint8_t> request = {commandProgramUserCode, 0x0, 0x0, 0x0};
+ std::vector<uint8_t> response;
+ for (int i = 3; i >= 0; i--)
+ {
+ request.push_back((fwInfo.version >> (i * 8)) & 0xFF);
+ }
+
+ if (!i2cInterface.sendReceive(request, response))
+ {
+ lg2::error("Failed to send program user code request.");
+ co_return false;
+ }
+ if (!(co_await waitBusyAndVerify()))
+ {
+ lg2::error("Wait busy and verify fail");
+ co_return false;
+ }
+
+ co_return true;
+}
+
+sdbusplus::async::task<bool> LatticeXO3CPLD::prepareUpdate(const uint8_t* image,
+ size_t imageSize)
+{
+ if (!(co_await readDeviceId()))
+ {
+ co_return false;
+ }
+
+ if (!jedFileParser(image, imageSize))
+ {
+ lg2::error("JED file parsing failed");
+ co_return false;
+ }
+ lg2::debug("JED file parsing success");
+
+ if (!verifyChecksum())
+ {
+ lg2::error("Checksum verification failed");
+ co_return false;
+ }
+
+ if (!isLCMXO3D)
+ {
+ lg2::error("is not LCMXO3D");
+ }
+
+ co_return true;
+}
+
+sdbusplus::async::task<bool> LatticeXO3CPLD::doUpdate()
+{
+ co_await waitBusyAndVerify();
+
+ if (!(co_await enableProgramMode()))
+ {
+ lg2::error("Enable program mode failed.");
+ co_return false;
+ }
+
+ if (!(co_await eraseFlash()))
+ {
+ lg2::error("Erase flash failed.");
+ co_return false;
+ }
+
+ if (!(co_await resetConfigFlash()))
+ {
+ lg2::error("Reset config flash failed.");
+ co_return false;
+ }
+
+ if (!(co_await writeProgramPage()))
+ {
+ lg2::error("Write program page failed.");
+ co_return false;
+ }
+
+ if (!(co_await programUserCode()))
+ {
+ lg2::error("Program user code failed.");
+ co_return false;
+ }
+
+ if (!(co_await programDone()))
+ {
+ lg2::error("Program not done.");
+ co_return false;
+ }
+
+ co_return true;
+}
+
+sdbusplus::async::task<bool> LatticeXO3CPLD::finishUpdate()
+{
+ // NOLINTNEXTLINE(clang-analyzer-core.uninitialized.Branch)
+ if (!(co_await disableConfigInterface()))
+ {
+ lg2::error("Disable Config Interface failed.");
+ co_return false;
+ }
+
+ co_return true;
+}
+
+sdbusplus::async::task<bool> LatticeXO3CPLD::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> LatticeXO3CPLD::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;
+}
+
+} // namespace phosphor::software::cpld