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_base_cpld.cpp b/cpld/lattice/lattice_base_cpld.cpp
new file mode 100644
index 0000000..9055b42
--- /dev/null
+++ b/cpld/lattice/lattice_base_cpld.cpp
@@ -0,0 +1,548 @@
+#include "lattice_base_cpld.hpp"
+
+#include <algorithm>
+#include <cstddef>
+#include <fstream>
+#include <map>
+#include <numeric>
+#include <vector>
+
+namespace phosphor::software::cpld
+{
+
+constexpr uint8_t busyWaitmaxRetry = 30;
+constexpr uint8_t busyFlagBit = 0x80;
+
+static constexpr std::string_view tagFuseQuantity = "QF";
+static constexpr std::string_view tagUserCodeHex = "UH";
+static constexpr std::string_view tagCFStart = "L000";
+static constexpr std::string_view tagData = "NOTE TAG DATA";
+static constexpr std::string_view tagUserFlashMemory = "NOTE USER MEMORY DATA";
+static constexpr std::string_view tagChecksum = "C";
+static constexpr std::string_view tagUserCode = "NOTE User Electronic";
+static constexpr std::string_view tagEbrInitData = "NOTE EBR_INIT DATA";
+static constexpr std::string_view tagEndConfig = "NOTE END CONFIG DATA";
+static constexpr std::string_view tagDevName = "NOTE DEVICE NAME";
+
+constexpr uint8_t isOK = 0;
+constexpr uint8_t isReady = 0;
+constexpr uint8_t busyOrReadyBit = 4;
+constexpr uint8_t failOrOKBit = 5;
+
+static uint8_t reverse_bit(uint8_t b)
+{
+ b = (b & 0xF0) >> 4 | (b & 0x0F) << 4;
+ b = (b & 0xCC) >> 2 | (b & 0x33) << 2;
+ b = (b & 0xAA) >> 1 | (b & 0x55) << 1;
+ return b;
+}
+
+std::string LatticeBaseCPLD::uint32ToHexStr(uint32_t value)
+{
+ std::ostringstream oss;
+ oss << std::setfill('0') << std::setw(8) << std::hex << std::uppercase
+ << value;
+ return oss.str();
+}
+
+sdbusplus::async::task<bool> LatticeBaseCPLD::updateFirmware(
+ const uint8_t* image, size_t imageSize,
+ std::function<bool(int)> progressCallBack)
+{
+ if (progressCallBack == nullptr)
+ {
+ lg2::error("Error: progressCallBack is null.");
+ co_return false;
+ }
+
+ if (!image || imageSize == 0)
+ {
+ lg2::error("Error: image is null.");
+ co_return false;
+ }
+
+ lg2::debug("CPLD image size: {IMAGESIZE}", "IMAGESIZE", imageSize);
+ auto result = co_await prepareUpdate(image, imageSize);
+ if (!result)
+ {
+ lg2::error("Prepare update failed.");
+ co_return false;
+ }
+ lg2::debug("Prepare update success");
+ progressCallBack(50);
+
+ result = co_await doUpdate();
+ if (!result)
+ {
+ lg2::error("Do update failed.");
+ co_return false;
+ }
+ lg2::debug("Do update success");
+ progressCallBack(90);
+
+ result = co_await finishUpdate();
+ if (!result)
+ {
+ lg2::error("Finish update failed.");
+ co_return false;
+ }
+ lg2::debug("Finish update success");
+ progressCallBack(100);
+
+ co_return true;
+}
+
+bool LatticeBaseCPLD::jedFileParser(const uint8_t* image, size_t imageSize)
+{
+ enum class ParseState
+ {
+ none,
+ cfg,
+ endCfg,
+ ufm,
+ checksum,
+ userCode
+ };
+ ParseState state = ParseState::none;
+
+ if (image == nullptr || imageSize == 0)
+ {
+ lg2::error(
+ "Error: JED file is empty or not found. Please check the file.");
+ return false;
+ }
+
+ std::string content(reinterpret_cast<const char*>(image), imageSize);
+ std::istringstream iss(content);
+ std::string line;
+
+ auto pushPage = [](std::string& line, std::vector<uint8_t>& sector) {
+ if (line[0] == '0' || line[0] == '1')
+ {
+ while (line.size() >= 8)
+ {
+ try
+ {
+ sector.push_back(static_cast<uint8_t>(
+ std::stoi(line.substr(0, 8), 0, 2)));
+ line.erase(0, 8);
+ }
+ catch (...)
+ {
+ break;
+ }
+ }
+ }
+ };
+
+ while (getline(iss, line))
+ {
+ if (!line.empty() && line.back() == '\r')
+ {
+ line.pop_back();
+ }
+ if (line.empty())
+ {
+ continue;
+ }
+
+ if (line.starts_with(tagFuseQuantity))
+ {
+ ssize_t numberSize = static_cast<ssize_t>(line.find('*')) -
+ static_cast<ssize_t>(line.find('F')) - 1;
+ if (numberSize > 0)
+ {
+ fwInfo.fuseQuantity = std::stoul(
+ line.substr(tagFuseQuantity.length(), numberSize));
+ lg2::debug("fuseQuantity Size = {QFSIZE}", "QFSIZE",
+ fwInfo.fuseQuantity);
+ }
+ }
+ else if (line.starts_with(tagCFStart) ||
+ line.starts_with(tagEbrInitData))
+ {
+ state = ParseState::cfg;
+ continue;
+ }
+ else if (line.starts_with(tagEndConfig))
+ {
+ state = ParseState::endCfg;
+ continue;
+ }
+ else if (line.starts_with(tagUserFlashMemory) ||
+ line.starts_with(tagData))
+ {
+ state = ParseState::ufm;
+ continue;
+ }
+ else if (line.starts_with(tagUserCode))
+ {
+ state = ParseState::userCode;
+ continue;
+ }
+ else if (line.starts_with(tagChecksum))
+ {
+ state = ParseState::checksum;
+ }
+ else if (line.starts_with(tagDevName))
+ {
+ lg2::debug("{DEVNAME}", "DEVNAME", line);
+ if (line.find(chip) == std::string::npos)
+ {
+ lg2::debug("STOP UPDATING: The image does not match the chip.");
+ return -1;
+ }
+ }
+
+ switch (state)
+ {
+ case ParseState::cfg:
+ pushPage(line, fwInfo.cfgData);
+ break;
+ case ParseState::endCfg:
+ pushPage(line, sumOnly);
+ break;
+ case ParseState::ufm:
+ pushPage(line, fwInfo.ufmData);
+ break;
+ case ParseState::checksum:
+ if (line.size() > 1)
+ {
+ state = ParseState::none;
+ ssize_t numberSize =
+ static_cast<ssize_t>(line.find('*')) -
+ static_cast<ssize_t>(line.find('C')) - 1;
+ if (numberSize <= 0)
+ {
+ lg2::debug("Error in parsing checksum");
+ return -1;
+ }
+ static constexpr auto start = tagChecksum.length();
+ std::istringstream iss(line.substr(start, numberSize));
+ iss >> std::hex >> fwInfo.checksum;
+ lg2::debug("Checksum = 0x{CHECKSUM}", "CHECKSUM",
+ fwInfo.checksum);
+ }
+ break;
+ case ParseState::userCode:
+ if (line.starts_with(tagUserCodeHex))
+ {
+ state = ParseState::none;
+ ssize_t numberSize =
+ static_cast<ssize_t>(line.find('*')) -
+ static_cast<ssize_t>(line.find('H')) - 1;
+ if (numberSize <= 0)
+ {
+ lg2::debug("Error in parsing usercode");
+ return -1;
+ }
+ std::istringstream iss(
+ line.substr(tagUserCodeHex.length(), numberSize));
+ iss >> std::hex >> fwInfo.version;
+ lg2::debug("UserCode = 0x{USERCODE}", "USERCODE",
+ fwInfo.version);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ lg2::debug("CFG Size = {CFGSIZE}", "CFGSIZE", fwInfo.cfgData.size());
+ if (!fwInfo.ufmData.empty())
+ {
+ lg2::debug("userFlashMemory size = {UFMSIZE}", "UFMSIZE",
+ fwInfo.ufmData.size());
+ }
+
+ return true;
+}
+
+bool LatticeBaseCPLD::verifyChecksum()
+{
+ uint32_t calculated = 0U;
+ auto addByte = [](uint32_t sum, uint8_t byte) {
+ return sum + reverse_bit(byte);
+ };
+
+ calculated = std::accumulate(fwInfo.cfgData.begin(), fwInfo.cfgData.end(),
+ calculated, addByte);
+ calculated =
+ std::accumulate(sumOnly.begin(), sumOnly.end(), calculated, addByte);
+ calculated = std::accumulate(fwInfo.ufmData.begin(), fwInfo.ufmData.end(),
+ calculated, addByte);
+
+ lg2::debug("Calculated checksum = {CALCULATED}", "CALCULATED", lg2::hex,
+ calculated);
+ lg2::debug("Checksum from JED file = {JEDFILECHECKSUM}", "JEDFILECHECKSUM",
+ lg2::hex, fwInfo.checksum);
+
+ if (fwInfo.checksum != (calculated & 0xFFFF))
+ {
+ lg2::error("JED file checksum compare fail, "
+ "Calculated checksum = {CALCULATED}, "
+ "Checksum from JED file = {JEDFILECHECKSUM}",
+ "CALCULATED", lg2::hex, calculated, "JEDFILECHECKSUM",
+ lg2::hex, fwInfo.checksum);
+ return false;
+ }
+
+ lg2::debug("JED file checksum compare success");
+ return true;
+}
+
+sdbusplus::async::task<bool> LatticeBaseCPLD::enableProgramMode()
+{
+ std::vector<uint8_t> request = {commandEnableConfigMode, 0x08, 0x0, 0x0};
+ std::vector<uint8_t> response;
+
+ if (!i2cInterface.sendReceive(request, response))
+ {
+ lg2::error("Failed to send enable program mode 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> LatticeBaseCPLD::resetConfigFlash()
+{
+ std::vector<uint8_t> request;
+ std::vector<uint8_t> response;
+ if (isLCMXO3D)
+ {
+ /*
+ Set Page Address pointer to the
+ beginning of the different internal
+ Flash sectors. The bit in YYYY
+ defines which sector is selected.
+ Bit Flash sector selected
+ 8 CFG0
+ 9 CFG1
+ 10 FEA
+ 11 PUBKEY
+ 12 AESKEY
+ 13 CSEC
+ 14 UFM0
+ 15 UFM1
+ 16 UFM2
+ 17 UFM3
+ 18 USEC
+ 19 Reserved
+ 20 Reserved
+ 21 Reserved
+ 22 Reserved
+ commandResetConfigFlash = 0x46, YY YY 00
+ */
+ if (target.empty() || target == "CFG0")
+ {
+ request = {commandResetConfigFlash, 0x00, 0x01, 0x00};
+ }
+ else if (target == "CFG1")
+ {
+ request = {commandResetConfigFlash, 0x00, 0x02, 0x00};
+ }
+ else
+ {
+ lg2::error(
+ "Error: unknown target. Only CFG0 and CFG1 are supported.");
+ co_return false;
+ }
+ }
+ else
+ {
+ request = {commandResetConfigFlash, 0x0, 0x0, 0x0};
+ }
+
+ co_return i2cInterface.sendReceive(request, response);
+}
+
+sdbusplus::async::task<bool> LatticeBaseCPLD::programDone()
+{
+ std::vector<uint8_t> request = {commandProgramDone, 0x0, 0x0, 0x0};
+ std::vector<uint8_t> response;
+
+ if (!i2cInterface.sendReceive(request, response))
+ {
+ lg2::error("Failed to send program done 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> LatticeBaseCPLD::disableConfigInterface()
+{
+ std::vector<uint8_t> request = {commandDisableConfigInterface, 0x0, 0x0};
+ std::vector<uint8_t> response;
+ co_return i2cInterface.sendReceive(request, response);
+}
+
+sdbusplus::async::task<bool> LatticeBaseCPLD::waitBusyAndVerify()
+{
+ uint8_t retry = 0;
+
+ while (retry <= busyWaitmaxRetry)
+ {
+ uint8_t busyFlag = 0xff;
+
+ auto readBusyFlagResult = co_await readBusyFlag(busyFlag);
+ if (!readBusyFlagResult)
+ {
+ lg2::error("Fail to read busy flag.");
+ co_return false;
+ }
+
+ if (busyFlag & busyFlagBit)
+ {
+ co_await sdbusplus::async::sleep_for(ctx, waitBusyTime);
+ retry++;
+ if (retry > busyWaitmaxRetry)
+ {
+ lg2::error(
+ "Status Reg : Busy! Please check the I2C bus and address.");
+ co_return false;
+ }
+ }
+ else
+ {
+ break;
+ }
+ } // while loop busy check
+
+ // Check out status reg
+ auto statusReg = std::make_unique<uint8_t>(0xff);
+
+ if (!(co_await readStatusReg(*statusReg)))
+ {
+ lg2::error("Fail to read status register.");
+ co_return false;
+ }
+
+ if (((*statusReg >> busyOrReadyBit) & 1) == isReady &&
+ ((*statusReg >> failOrOKBit) & 1) == isOK)
+ {
+ lg2::debug("Status Reg : OK");
+ co_return true;
+ }
+
+ lg2::error("Status Reg : Fail! Please check the I2C bus and address.");
+ co_return false;
+}
+
+sdbusplus::async::task<bool> LatticeBaseCPLD::readBusyFlag(uint8_t& busyFlag)
+{
+ constexpr size_t resSize = 1;
+ std::vector<uint8_t> request = {commandReadBusyFlag, 0x0, 0x0, 0x0};
+ std::vector<uint8_t> response(resSize, 0);
+
+ auto success = i2cInterface.sendReceive(request, response);
+ if (!success && response.size() != resSize)
+ {
+ co_return false;
+ }
+ busyFlag = response.at(0);
+ co_return true;
+}
+
+sdbusplus::async::task<bool> LatticeBaseCPLD::readStatusReg(uint8_t& statusReg)
+{
+ std::vector<uint8_t> request = {commandReadStatusReg, 0x0, 0x0, 0x0};
+ std::vector<uint8_t> response(4, 0);
+
+ if (!i2cInterface.sendReceive(request, response))
+ {
+ lg2::error("Failed to send read status register request.");
+ co_return false;
+ }
+ /*
+ Read Status Register
+ [LSC_READ_STATUS]
+ 0x3C 00 00 00 N/A YY YY YY YY Bit 1 0
+ 12 Busy Ready
+ 13 Fail OK
+ */
+ statusReg = response.at(2);
+ co_return true;
+}
+
+sdbusplus::async::task<bool> LatticeBaseCPLD::getVersion(std::string& version)
+{
+ auto userCode = std::make_unique<uint32_t>(0);
+
+ if (target.empty())
+ {
+ if (!(co_await readUserCode(*userCode)))
+ {
+ lg2::error("Read usercode failed.");
+ co_return false;
+ }
+
+ lg2::debug("CPLD version: {VERSION}", "VERSION", *userCode);
+ }
+ else if (target == "CFG0" || target == "CFG1")
+ {
+ isLCMXO3D = true;
+ co_await waitBusyAndVerify();
+
+ if (!(co_await enableProgramMode()))
+ {
+ lg2::error("Enable program mode failed.");
+ co_return false;
+ }
+
+ if (!(co_await resetConfigFlash()))
+ {
+ lg2::error("Reset config flash failed.");
+ co_return false;
+ }
+
+ if (!(co_await readUserCode(*userCode)))
+ {
+ lg2::error("Read usercode failed.");
+ co_return false;
+ }
+
+ if (!(co_await programDone()))
+ {
+ lg2::error("Program not done.");
+ co_return false;
+ }
+
+ if (!(co_await disableConfigInterface()))
+ {
+ lg2::error("Disable Config Interface failed.");
+ co_return false;
+ }
+
+ lg2::debug("CPLD {TARGET} version: {VERSION}", "TARGET", target,
+ "VERSION", *userCode);
+ }
+ else
+ {
+ lg2::error("Error: unknown target.");
+ co_return false;
+ }
+
+ if (*userCode == 0)
+ {
+ lg2::error("User code is zero, cannot get version.");
+ co_return false;
+ }
+ version = uint32ToHexStr(*userCode);
+ co_return true;
+}
+
+} // namespace phosphor::software::cpld