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.hpp b/cpld/lattice/lattice_base_cpld.hpp
new file mode 100644
index 0000000..cb45c39
--- /dev/null
+++ b/cpld/lattice/lattice_base_cpld.hpp
@@ -0,0 +1,167 @@
+#pragma once
+#include "common/include/i2c/i2c.hpp"
+
+#include <phosphor-logging/lg2.hpp>
+
+#include <chrono>
+#include <iostream>
+#include <string_view>
+#include <unordered_map>
+#include <utility>
+
+namespace phosphor::software::cpld
+{
+
+enum class latticeChip
+{
+ LCMXO2_4000HC,
+ LCMXO3LF_2100C,
+ LCMXO3LF_4300C,
+ LCMXO3D_4300,
+ LCMXO3D_9400,
+ UNSUPPORTED = -1,
+};
+
+enum class latticeStringType
+{
+ typeString,
+ modelString,
+};
+
+inline std::string getLatticeChipStr(latticeChip chip,
+ latticeStringType stringType)
+{
+ static const std::unordered_map<latticeChip, std::string> chipStringMap = {
+ {latticeChip::LCMXO2_4000HC, "LCMXO2_4000HC"},
+ {latticeChip::LCMXO3LF_2100C, "LCMXO3LF_2100C"},
+ {latticeChip::LCMXO3LF_4300C, "LCMXO3LF_4300C"},
+ {latticeChip::LCMXO3D_4300, "LCMXO3D_4300"},
+ {latticeChip::LCMXO3D_9400, "LCMXO3D_9400"},
+ };
+ auto chipString = chipStringMap.at(chip);
+ if (chipStringMap.find(chip) == chipStringMap.end())
+ {
+ lg2::error("Unsupported chip enum: {CHIPENUM}", "CHIPENUM", chip);
+ return "";
+ }
+
+ switch (stringType)
+ {
+ case latticeStringType::typeString:
+ return std::string("Lattice" + chipString + "Firmware");
+ case latticeStringType::modelString:
+ std::replace(chipString.begin(), chipString.end(), '_', '-');
+ return chipString;
+ default:
+ lg2::error("Unsupported string type: {STRINGTYPE}", "STRINGTYPE",
+ stringType);
+ return "";
+ }
+};
+
+enum class latticeChipFamily
+{
+ XO2,
+ XO3,
+};
+
+struct cpldInfo
+{
+ latticeChipFamily chipFamily;
+ std::vector<uint8_t> deviceId;
+};
+
+const std::map<latticeChip, cpldInfo> supportedDeviceMap = {
+ {latticeChip::LCMXO2_4000HC,
+ {latticeChipFamily::XO2, {0x01, 0x2b, 0xc0, 0x43}}},
+ {latticeChip::LCMXO3LF_2100C,
+ {latticeChipFamily::XO3, {0x61, 0x2b, 0xb0, 0x43}}},
+ {latticeChip::LCMXO3LF_4300C,
+ {latticeChipFamily::XO3, {0x61, 0x2b, 0xc0, 0x43}}},
+ {latticeChip::LCMXO3D_4300,
+ {latticeChipFamily::XO3, {0x01, 0x2e, 0x20, 0x43}}},
+ {latticeChip::LCMXO3D_9400,
+ {latticeChipFamily::XO3, {0x21, 0x2e, 0x30, 0x43}}},
+};
+
+struct cpldI2cInfo
+{
+ unsigned long int fuseQuantity;
+ unsigned int* userFlashMemory;
+ unsigned int version;
+ unsigned int checksum;
+ std::vector<uint8_t> cfgData;
+ std::vector<uint8_t> ufmData;
+};
+
+enum cpldI2cCmd
+{
+ commandEraseFlash = 0x0E,
+ commandDisableConfigInterface = 0x26,
+ commandReadStatusReg = 0x3C,
+ commandResetConfigFlash = 0x46,
+ commandProgramDone = 0x5E,
+ commandProgramPage = 0x70,
+ commandReadPage = 0x73,
+ commandEnableConfigMode = 0x74,
+ commandSetPageAddress = 0xB4,
+ commandReadFwVersion = 0xC0,
+ commandProgramUserCode = 0xC2,
+ commandReadDeviceId = 0xE0,
+ commandReadBusyFlag = 0xF0,
+};
+
+constexpr std::chrono::milliseconds waitBusyTime(200);
+
+class LatticeBaseCPLD
+{
+ public:
+ LatticeBaseCPLD(sdbusplus::async::context& ctx, const uint16_t bus,
+ const uint8_t address, const std::string& chip,
+ const std::string& target, const bool debugMode) :
+ ctx(ctx), chip(chip), target(target), debugMode(debugMode),
+ i2cInterface(phosphor::i2c::I2C(bus, address))
+ {}
+ virtual ~LatticeBaseCPLD() = default;
+ LatticeBaseCPLD(const LatticeBaseCPLD&) = delete;
+ LatticeBaseCPLD& operator=(const LatticeBaseCPLD&) = delete;
+ LatticeBaseCPLD(LatticeBaseCPLD&&) noexcept = delete;
+ LatticeBaseCPLD& operator=(LatticeBaseCPLD&&) noexcept = delete;
+
+ sdbusplus::async::task<bool> updateFirmware(
+ const uint8_t* image, size_t imageSize,
+ std::function<bool(int)> progressCallBack);
+
+ sdbusplus::async::task<bool> getVersion(std::string& version);
+
+ protected:
+ sdbusplus::async::context& ctx;
+ cpldI2cInfo fwInfo{};
+ std::string chip;
+ std::string target;
+ std::vector<uint8_t> sumOnly;
+ bool isLCMXO3D = false;
+ bool debugMode = false;
+ phosphor::i2c::I2C i2cInterface;
+
+ virtual sdbusplus::async::task<bool> prepareUpdate(const uint8_t*,
+ size_t) = 0;
+ virtual sdbusplus::async::task<bool> doUpdate() = 0;
+ virtual sdbusplus::async::task<bool> finishUpdate() = 0;
+
+ bool jedFileParser(const uint8_t* image, size_t imageSize);
+ bool verifyChecksum();
+ sdbusplus::async::task<bool> enableProgramMode();
+ sdbusplus::async::task<bool> resetConfigFlash();
+ sdbusplus::async::task<bool> programDone();
+ sdbusplus::async::task<bool> disableConfigInterface();
+ sdbusplus::async::task<bool> waitBusyAndVerify();
+
+ private:
+ virtual sdbusplus::async::task<bool> readUserCode(uint32_t&) = 0;
+ sdbusplus::async::task<bool> readBusyFlag(uint8_t& busyFlag);
+ sdbusplus::async::task<bool> readStatusReg(uint8_t& statusReg);
+ static std::string uint32ToHexStr(uint32_t value);
+};
+
+} // namespace phosphor::software::cpld