fw-update: cpld code updater
This commit introduces a code updater for CPLD.
The Key features of this updater include:
- configuring chip vendor and name
- update CPLD through I2C
1. Display the fw inventory
curl ... -X GET
https://localhost/redfish/v1/UpdateService/FirmwareInventory
{
"@odata.id": "/redfish/v1/UpdateService/FirmwareInventory",
"@odata.type":
"#SoftwareInventoryCollection.SoftwareInventoryCollection",
"Members": [
...
{
"@odata.id": ".../LCMXO3LF_4300C_6194"
},
...
],
"Members@odata.count": 26,
"Name": "Software Inventory Collection"
}
2. Query CPLD version
curl ... -X GET
https://localhost/redfish/v1
/UpdateService/FirmwareInventory/LCMXO3LF_4300C_6194
{
"@odata.id": "/.../LCMXO3LF_4300C_6194",
"@odata.type":
"#SoftwareInventory.v1_1_0.SoftwareInventory",
"Description": "Unknown image",
"Id": "LCMXO3LF_4300C_6194",
"Name": "Software Inventory",
"Status": {
"Health": "Warning",
"HealthRollup": "OK",
"State": "Disabled"
},
"Updateable": false,
"Version": "00000220"
}
3. Trigger the fw update via redfish
curl -k
-H "X-Auth-Token: $token"
-H "Content-Type:multipart/form-data"
-X POST
-F UpdateParameters="{\"Targets\":
[\"/redfish/v1/UpdateService/FirmwareInventory/LCMXO3LF_4300C_6194\"],
\"@Redfish.OperationApplyTime\":\"Immediate\"};type=application/json"
-F "UpdateFile=@testcpld.pldm;type=application/octet-stream"
https://${bmc}/redfish/v1/UpdateService/update
{
"@odata.id": "/redfish/v1/TaskService/Tasks/0",
"@odata.type": "#Task.v1_4_3.Task",
"Id": "0",
"TaskState": "Running",
"TaskStatus": "OK"
}
4. Query Task status
curl ... -X GET
https://localhost/redfish/v1/TaskService/Tasks/0
{
"@odata.id": "/redfish/v1/TaskService/Tasks/0",
"@odata.type": "#Task.v1_4_3.Task",
"EndTime": "2025-05-28T08:10:22+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": "... progress 15 percent complete.",
"MessageArgs": [
"0",
"15"
],
"MessageId": "TaskEvent.1.0.TaskProgressChanged",
"MessageSeverity": "OK",
"Resolution": "None."
},
{
"@odata.type": "#Message.v1_1_1.Message",
"Message": "... progress 20 percent complete.",
"MessageArgs": [
"0",
"20"
],
"MessageId": "TaskEvent.1.0.TaskProgressChanged",
"MessageSeverity": "OK",
"Resolution": "None."
},
{
"@odata.type": "#Message.v1_1_1.Message",
"Message": "... progress 25 percent complete.",
"MessageArgs": [
"0",
"25"
],
"MessageId": "TaskEvent.1.0.TaskProgressChanged",
"MessageSeverity": "OK",
"Resolution": "None."
},
{
"@odata.type": "#Message.v1_1_1.Message",
"Message": "... progress 30 percent complete.",
"MessageArgs": [
"0",
"30"
],
"MessageId": "TaskEvent.1.0.TaskProgressChanged",
"MessageSeverity": "OK",
"Resolution": "None."
},
{
"@odata.type": "#Message.v1_1_1.Message",
"Message": "... progress 40 percent complete.",
"MessageArgs": [
"0",
"40"
],
"MessageId": "TaskEvent.1.0.TaskProgressChanged",
"MessageSeverity": "OK",
"Resolution": "None."
},
{
"@odata.type": "#Message.v1_1_1.Message",
"Message": "... progress 50 percent complete.",
"MessageArgs": [
"0",
"50"
],
"MessageId": "TaskEvent.1.0.TaskProgressChanged",
"MessageSeverity": "OK",
"Resolution": "None."
},
{
"@odata.type": "#Message.v1_1_1.Message",
"Message": "... progress 60 percent complete.",
"MessageArgs": [
"0",
"60"
],
"MessageId": "TaskEvent.1.0.TaskProgressChanged",
"MessageSeverity": "OK",
"Resolution": "None."
},
{
"@odata.type": "#Message.v1_1_1.Message",
"Message": "... progress 70 percent complete.",
"MessageArgs": [
"0",
"70"
],
"MessageId": "TaskEvent.1.0.TaskProgressChanged",
"MessageSeverity": "OK",
"Resolution": "None."
},
{
"@odata.type": "#Message.v1_1_1.Message",
"Message": "... progress 80 percent complete.",
"MessageArgs": [
"0",
"80"
],
"MessageId": "TaskEvent.1.0.TaskProgressChanged",
"MessageSeverity": "OK",
"Resolution": "None."
},
{
"@odata.type": "#Message.v1_1_1.Message",
"Message": "... progress 90 percent complete.",
"MessageArgs": [
"0",
"90"
],
"MessageId": "TaskEvent.1.0.TaskProgressChanged",
"MessageSeverity": "OK",
"Resolution": "None."
},
{
"@odata.type": "#Message.v1_1_1.Message",
"Message": "... 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"
},
"PercentComplete": 100,
"StartTime": "2025-05-28T08:10:06+00:00",
"TaskMonitor": "/redfish/v1/TaskService/TaskMonitors/0",
"TaskState": "Completed",
"TaskStatus": "OK"
}
5. Display the fw inventory with newly updated fw.
curl ... -X GET
https://localhost/redfish/v1/UpdateService/FirmwareInventory
{
"@odata.id": "/redfish/v1/UpdateService/FirmwareInventory",
"@odata.type":
"#SoftwareInventoryCollection.SoftwareInventoryCollection",
"Members": [
...
{
"@odata.id": ".../LCMXO3LF_4300C_1571"
},
...
],
"Members@odata.count": 26,
"Name": "Software Inventory Collection"
}
6. Query CPLD version again
After AC cycle ..., so the number would be different.
curl ... -X GET
https://localhost/redfish/v1
/UpdateService/FirmwareInventory/LCMXO3LF_4300C_4643
{
"@odata.id": "/.../LCMXO3LF_4300C_4643",
"@odata.type": "#SoftwareInventory.v1_1_0.SoftwareInventory",
"Description": "Unknown image",
"Id": "LCMXO3LF_4300C_4643",
"Name": "Software Inventory",
"Status": {
"Health": "Warning",
"HealthRollup": "OK",
"State": "Disabled"
},
"Updateable": false,
"Version": "00000224"
}
Change-Id: Ife8e30a00bfbb307e6e4eb55838a397c8a8162bd
Signed-off-by: Daniel Hsu <Daniel-Hsu@quantatw.com>
diff --git a/common/i2c/i2c.cpp b/common/i2c/i2c.cpp
index 99de04b..6f8414d 100644
--- a/common/i2c/i2c.cpp
+++ b/common/i2c/i2c.cpp
@@ -37,41 +37,45 @@
uint8_t readSize) const
// NOLINTEND(readability-static-accessed-through-instance)
{
+ bool result = true;
+
if (fd <= 0)
{
- co_return false;
+ result = false;
}
-
- struct i2c_msg msg[2];
- struct i2c_rdwr_ioctl_data readWriteData;
- int msgIndex = 0;
-
- if (writeSize)
+ else
{
- msg[msgIndex].addr = deviceNode;
- msg[msgIndex].flags = 0;
- msg[msgIndex].len = writeSize;
- msg[msgIndex].buf = writeData;
- msgIndex++;
- }
+ struct i2c_msg msg[2];
+ struct i2c_rdwr_ioctl_data readWriteData;
+ int msgIndex = 0;
- if (readSize)
- {
- msg[msgIndex].addr = deviceNode;
- msg[msgIndex].flags = I2C_M_RD;
- msg[msgIndex].len = readSize;
- msg[msgIndex].buf = readData;
- msgIndex++;
- }
+ if (writeSize)
+ {
+ msg[msgIndex].addr = deviceNode;
+ msg[msgIndex].flags = 0;
+ msg[msgIndex].len = writeSize;
+ msg[msgIndex].buf = writeData;
+ msgIndex++;
+ }
- readWriteData.msgs = msg;
- readWriteData.nmsgs = msgIndex;
+ if (readSize)
+ {
+ msg[msgIndex].addr = deviceNode;
+ msg[msgIndex].flags = I2C_M_RD;
+ msg[msgIndex].len = readSize;
+ msg[msgIndex].buf = readData;
+ msgIndex++;
+ }
- if (ioctl(fd, I2C_RDWR, &readWriteData) < 0)
- {
- co_return false;
+ readWriteData.msgs = msg;
+ readWriteData.nmsgs = msgIndex;
+
+ if (ioctl(fd, I2C_RDWR, &readWriteData) < 0)
+ {
+ result = false;
+ }
}
- co_return true;
+ co_return result;
}
int I2C::close() const
diff --git a/cpld/README.md b/cpld/README.md
new file mode 100644
index 0000000..db8af3d
--- /dev/null
+++ b/cpld/README.md
@@ -0,0 +1,21 @@
+# CPLD Update Daemon
+
+This daemon implements the update process for CPLD attached via I2C bus.
+
+## Configuration Example
+
+The following shows an example of entity manager configuration record for
+lattice LCMXO3LF-4300C CPLD.
+
+```json
+{
+ "Address": "0x40",
+ "Bus": 5,
+ "FirmwareInfo": {
+ "CompatibleHardware": "com.meta.Hardware.CPLD",
+ "VendorIANA": 40981
+ },
+ "Name": "LCMXO3LF_4300C",
+ "Type": "LatticeXO3Firmware"
+}
+```
diff --git a/cpld/cpld.cpp b/cpld/cpld.cpp
new file mode 100644
index 0000000..2392e48
--- /dev/null
+++ b/cpld/cpld.cpp
@@ -0,0 +1,52 @@
+#include "cpld.hpp"
+
+namespace phosphor::software::cpld
+{
+
+sdbusplus::async::task<bool> CPLDDevice::updateDevice(const uint8_t* image,
+ size_t image_size)
+{
+ if (cpldInterface == nullptr)
+ {
+ lg2::error("CPLD interface is not initialized");
+ co_return false;
+ }
+ else
+ {
+ setUpdateProgress(1);
+ if (!(co_await cpldInterface->updateFirmware(
+ false, image, image_size, [this](int percent) -> bool {
+ return this->setUpdateProgress(percent);
+ })))
+ {
+ lg2::error("Failed to update CPLD firmware");
+ co_return false;
+ }
+
+ setUpdateProgress(100);
+ lg2::info("Successfully updated CPLD");
+ co_return true;
+ }
+}
+
+sdbusplus::async::task<bool> CPLDDevice::getVersion(std::string& version)
+{
+ if (cpldInterface == nullptr)
+ {
+ lg2::error("CPLD interface is not initialized");
+ co_return false;
+ }
+ else
+ {
+ if (!(co_await cpldInterface->getVersion(version)))
+ {
+ lg2::error("Failed to get CPLD version");
+ co_return false;
+ }
+
+ lg2::info("CPLD version: {VERSION}", "VERSION", version);
+ co_return true;
+ }
+}
+
+} // namespace phosphor::software::cpld
diff --git a/cpld/cpld.hpp b/cpld/cpld.hpp
new file mode 100644
index 0000000..a6bf18b
--- /dev/null
+++ b/cpld/cpld.hpp
@@ -0,0 +1,39 @@
+#pragma once
+
+#include "common/include/device.hpp"
+#include "common/include/software_manager.hpp"
+#include "cpld_interface.hpp"
+
+#include <phosphor-logging/lg2.hpp>
+#include <sdbusplus/async/context.hpp>
+
+PHOSPHOR_LOG2_USING;
+
+namespace ManagerInf = phosphor::software::manager;
+
+namespace phosphor::software::cpld
+{
+
+class CPLDDevice : public Device
+{
+ public:
+ CPLDDevice(sdbusplus::async::context& ctx, const std::string& chiptype,
+ const std::string& chipname, const uint16_t& bus,
+ const uint8_t& address, SoftwareConfig& config,
+ ManagerInf::SoftwareManager* parent) :
+ Device(ctx, config, parent,
+ {RequestedApplyTimes::Immediate, RequestedApplyTimes::OnReset}),
+ cpldInterface(CPLDFactory::instance().create(chiptype, ctx, chipname,
+ bus, address))
+ {}
+
+ using Device::softwareCurrent;
+ sdbusplus::async::task<bool> updateDevice(const uint8_t* image,
+ size_t image_size) final;
+ sdbusplus::async::task<bool> getVersion(std::string& version);
+
+ private:
+ std::unique_ptr<CPLDInterface> cpldInterface;
+};
+
+} // namespace phosphor::software::cpld
diff --git a/cpld/cpld_interface.cpp b/cpld/cpld_interface.cpp
new file mode 100644
index 0000000..a67e03e
--- /dev/null
+++ b/cpld/cpld_interface.cpp
@@ -0,0 +1,43 @@
+#include "cpld_interface.hpp"
+
+#include "lattice/interface.hpp"
+
+namespace phosphor::software::cpld
+{
+
+CPLDFactory& CPLDFactory::instance()
+{
+ static CPLDFactory factory;
+ return factory;
+}
+
+void CPLDFactory::registerCPLD(const std::string& vendorName, Creator creator)
+{
+ creators[vendorName] = std::move(creator);
+}
+
+std::unique_ptr<CPLDInterface> CPLDFactory::create(
+ const std::string& vendorName, sdbusplus::async::context& ctx,
+ const std::string& chipName, uint16_t bus, uint8_t address) const
+{
+ auto it = creators.find(vendorName);
+ if (it != creators.end())
+ {
+ return (it->second)(ctx, chipName, bus, address);
+ }
+ return nullptr;
+}
+
+std::vector<std::string> CPLDFactory::getConfigs()
+{
+ std::vector<std::string> configs;
+ configs.reserve(creators.size());
+
+ std::transform(creators.begin(), creators.end(),
+ std::back_inserter(configs),
+ [](const auto& pair) { return pair.first; });
+
+ return configs;
+}
+
+} // namespace phosphor::software::cpld
diff --git a/cpld/cpld_interface.hpp b/cpld/cpld_interface.hpp
new file mode 100644
index 0000000..6dec356
--- /dev/null
+++ b/cpld/cpld_interface.hpp
@@ -0,0 +1,62 @@
+#pragma once
+
+#include <sdbusplus/async.hpp>
+
+#include <cstdint>
+#include <functional>
+#include <memory>
+#include <string>
+
+namespace phosphor::software::cpld
+{
+
+class CPLDInterface
+{
+ public:
+ CPLDInterface(sdbusplus::async::context& ctx, const std::string& chipname,
+ uint16_t bus, uint8_t address) :
+ ctx(ctx), chipname(chipname), bus(bus), address(address)
+ {}
+
+ virtual ~CPLDInterface() = default;
+ CPLDInterface(const CPLDInterface&) = delete;
+ CPLDInterface& operator=(const CPLDInterface&) = delete;
+ CPLDInterface(CPLDInterface&&) = delete;
+ CPLDInterface& operator=(CPLDInterface&&) = delete;
+
+ virtual sdbusplus::async::task<bool> updateFirmware(
+ bool force, const uint8_t* image, size_t imageSize,
+ std::function<bool(int)> progress = nullptr) = 0;
+
+ virtual sdbusplus::async::task<bool> getVersion(std::string& version) = 0;
+
+ protected:
+ sdbusplus::async::context& ctx;
+ std::string chipname;
+ uint16_t bus;
+ uint8_t address;
+};
+
+class CPLDFactory
+{
+ public:
+ using Creator = std::function<std::unique_ptr<CPLDInterface>(
+ sdbusplus::async::context& ctx, const std::string& chipName,
+ uint16_t bus, uint8_t address)>;
+ using ConfigProvider = std::function<std::vector<std::string>()>;
+
+ static CPLDFactory& instance();
+
+ void registerCPLD(const std::string& vendorName, Creator creator);
+
+ std::unique_ptr<CPLDInterface> create(
+ const std::string& vendorName, sdbusplus::async::context& ctx,
+ const std::string& chipName, uint16_t bus, uint8_t address) const;
+
+ std::vector<std::string> getConfigs();
+
+ private:
+ std::unordered_map<std::string, Creator> creators;
+};
+
+} // namespace phosphor::software::cpld
diff --git a/cpld/cpld_software_manager.cpp b/cpld/cpld_software_manager.cpp
new file mode 100644
index 0000000..faf14a5
--- /dev/null
+++ b/cpld/cpld_software_manager.cpp
@@ -0,0 +1,93 @@
+#include "cpld_software_manager.hpp"
+
+#include "common/include/dbus_helper.hpp"
+#include "cpld.hpp"
+
+#include <phosphor-logging/lg2.hpp>
+#include <sdbusplus/async.hpp>
+
+PHOSPHOR_LOG2_USING;
+
+using namespace phosphor::software::cpld;
+
+sdbusplus::async::task<bool> CPLDSoftwareManager::initDevice(
+ const std::string& service, const std::string& path, SoftwareConfig& config)
+{
+ std::string configIface =
+ "xyz.openbmc_project.Configuration." + config.configType;
+
+ std::optional<uint64_t> busNo = co_await dbusGetRequiredProperty<uint64_t>(
+ ctx, service, path, configIface, "Bus");
+ std::optional<uint64_t> address =
+ co_await dbusGetRequiredProperty<uint64_t>(ctx, service, path,
+ configIface, "Address");
+ std::optional<std::string> chipType =
+ co_await dbusGetRequiredProperty<std::string>(ctx, service, path,
+ configIface, "Type");
+ std::optional<std::string> chipName =
+ co_await dbusGetRequiredProperty<std::string>(ctx, service, path,
+ configIface, "Name");
+
+ if (!busNo.has_value() || !address.has_value() || !chipType.has_value() ||
+ !chipName.has_value())
+ {
+ error("missing config property");
+ co_return false;
+ }
+
+ lg2::debug(
+ "CPLD device type: {TYPE} - {NAME} on Bus: {BUS} at Address: {ADDR}",
+ "TYPE", chipType.value(), "NAME", chipName.value(), "BUS",
+ busNo.value(), "ADDR", address.value());
+
+ auto cpld = std::make_unique<CPLDDevice>(
+ ctx, chipType.value(), chipName.value(), busNo.value(), address.value(),
+ config, this);
+
+ std::string version = "unknown";
+ if (!(co_await cpld->getVersion(version)))
+ {
+ lg2::error("Failed to get CPLD version for {NAME}", "NAME",
+ chipName.value());
+ }
+
+ std::unique_ptr<Software> software = std::make_unique<Software>(ctx, *cpld);
+
+ software->setVersion(version);
+
+ std::set<RequestedApplyTimes> allowedApplyTimes = {
+ RequestedApplyTimes::Immediate, RequestedApplyTimes::OnReset};
+
+ software->enableUpdate(allowedApplyTimes);
+
+ cpld->softwareCurrent = std::move(software);
+
+ devices.insert({config.objectPath, std::move(cpld)});
+
+ co_return true;
+}
+
+void CPLDSoftwareManager::start()
+{
+ std::vector<std::string> configIntfs;
+ auto configs = CPLDFactory::instance().getConfigs();
+ configIntfs.reserve(configs.size());
+ for (const auto& config : configs)
+ {
+ configIntfs.push_back("xyz.openbmc_project.Configuration." + config);
+ }
+
+ ctx.spawn(initDevices(configIntfs));
+ ctx.run();
+}
+
+int main()
+{
+ sdbusplus::async::context ctx;
+
+ CPLDSoftwareManager cpldSoftwareManager(ctx);
+
+ cpldSoftwareManager.start();
+
+ return 0;
+}
diff --git a/cpld/cpld_software_manager.hpp b/cpld/cpld_software_manager.hpp
new file mode 100644
index 0000000..37530cf
--- /dev/null
+++ b/cpld/cpld_software_manager.hpp
@@ -0,0 +1,22 @@
+#pragma once
+
+#include "common/include/software_manager.hpp"
+
+namespace phosphor::software::cpld
+{
+
+class CPLDSoftwareManager : public phosphor::software::manager::SoftwareManager
+{
+ public:
+ CPLDSoftwareManager(sdbusplus::async::context& ctx) :
+ SoftwareManager(ctx, "CPLD")
+ {}
+
+ sdbusplus::async::task<bool> initDevice(const std::string& service,
+ const std::string& path,
+ SoftwareConfig& config) final;
+
+ void start();
+};
+
+} // namespace phosphor::software::cpld
diff --git a/cpld/lattice/interface.cpp b/cpld/lattice/interface.cpp
new file mode 100644
index 0000000..21663f0
--- /dev/null
+++ b/cpld/lattice/interface.cpp
@@ -0,0 +1,63 @@
+#include "interface.hpp"
+
+#include "lattice.hpp"
+
+#include <phosphor-logging/lg2.hpp>
+
+namespace phosphor::software::cpld
+{
+
+const std::vector<std::string> supportedTypes = {
+ "LatticeXO2Firmware",
+ "LatticeXO3Firmware",
+};
+
+sdbusplus::async::task<bool> LatticeCPLD::updateFirmware(
+ bool /*force*/, const uint8_t* image, size_t imageSize,
+ std::function<bool(int)> progressCallBack)
+{
+ lg2::info("Updating Lattice CPLD firmware");
+
+ std::replace(chipname.begin(), chipname.end(), '_', '-');
+ auto cpldManager = std::make_unique<CpldLatticeManager>(
+ ctx, bus, address, image, imageSize, chipname, "CFG0", false);
+
+ co_return co_await cpldManager->updateFirmware(progressCallBack);
+}
+
+sdbusplus::async::task<bool> LatticeCPLD::getVersion(std::string& version)
+{
+ lg2::info("Getting Lattice CPLD version");
+
+ std::replace(chipname.begin(), chipname.end(), '_', '-');
+ auto cpldManager = std::make_unique<CpldLatticeManager>(
+ ctx, bus, address, nullptr, 0, chipname, "CFG0", false);
+
+ co_return co_await cpldManager->getVersion(version);
+}
+
+} // namespace phosphor::software::cpld
+
+// Factory function to create lattice CPLD device
+namespace
+{
+using namespace phosphor::software::cpld;
+
+// Register all the CPLD type with the CPLD factory
+const bool vendorRegistered = [] {
+ for (const auto& type : supportedTypes)
+ {
+ CPLDFactory::instance().registerCPLD(
+ type,
+ [](sdbusplus::async::context& ctx, const std::string& chipname,
+ uint16_t bus, uint8_t address) {
+ // Create and return a LatticeCPLD instance
+ // Pass the parameters to the constructor
+ return std::make_unique<LatticeCPLD>(ctx, chipname, bus,
+ address);
+ });
+ }
+ return true;
+}();
+
+} // namespace
diff --git a/cpld/lattice/interface.hpp b/cpld/lattice/interface.hpp
new file mode 100644
index 0000000..67d0915
--- /dev/null
+++ b/cpld/lattice/interface.hpp
@@ -0,0 +1,21 @@
+#include "cpld/cpld_interface.hpp"
+
+namespace phosphor::software::cpld
+{
+
+class LatticeCPLD : public CPLDInterface
+{
+ public:
+ LatticeCPLD(sdbusplus::async::context& ctx, const std::string& chipname,
+ uint16_t bus, uint8_t address) :
+ CPLDInterface(ctx, chipname, bus, address)
+ {}
+
+ sdbusplus::async::task<bool> updateFirmware(
+ bool force, const uint8_t* image, size_t imageSize,
+ std::function<bool(int)> progress) final;
+
+ sdbusplus::async::task<bool> getVersion(std::string& version) final;
+};
+
+} // namespace phosphor::software::cpld
diff --git a/cpld/lattice/lattice.cpp b/cpld/lattice/lattice.cpp
new file mode 100644
index 0000000..ce70fa3
--- /dev/null
+++ b/cpld/lattice/lattice.cpp
@@ -0,0 +1,933 @@
+#include "lattice.hpp"
+
+#include <phosphor-logging/lg2.hpp>
+
+#include <algorithm>
+#include <fstream>
+#include <map>
+#include <thread>
+#include <vector>
+
+using sdbusplus::async::details::context_friend;
+
+constexpr uint8_t busyWaitmaxRetry = 30;
+constexpr uint8_t busyFlagBit = 0x80;
+constexpr std::chrono::milliseconds waitBusyTime(200);
+
+static constexpr std::string_view tagQF = "QF";
+static constexpr std::string_view tagUH = "UH";
+static constexpr std::string_view tagCFStart = "L000";
+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";
+
+constexpr uint8_t isOK = 0;
+constexpr uint8_t isReady = 0;
+constexpr uint8_t busyOrReadyBit = 4;
+constexpr uint8_t failOrOKBit = 5;
+
+constexpr bool enableUpdateEbrInit = false;
+
+enum cpldI2cCmd
+{
+ commandEraseFlash = 0x0E,
+ commandDisableConfigInterface = 0x26,
+ commandReadStatusReg = 0x3C,
+ commandResetConfigFlash = 0x46,
+ commandProgramDone = 0x5E,
+ commandProgramPage = 0x70,
+ commandEnableConfigMode = 0x74,
+ commandReadFwVersion = 0xC0,
+ commandProgramUserCode = 0xC2,
+ commandReadDeviceId = 0xE0,
+ commandReadBusyFlag = 0xF0,
+};
+
+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;
+}
+
+const std::map<std::string, std::vector<uint8_t>> xo2xo3DeviceIdMap = {
+ {"LCMXO3LF-4300C", {0x61, 0x2b, 0xc0, 0x43}},
+ {"LCMXO3LF-4300", {0x61, 0x2b, 0xc0, 0x43}},
+ {"LCMXO3LF-6900", {0x61, 0x2b, 0xd0, 0x43}},
+ {"LCMXO3D-4300", {0x01, 0x2e, 0x20, 0x43}},
+ {"LCMXO3D-9400", {0x21, 0x2e, 0x30, 0x43}},
+};
+
+static int findNumberSize(const std::string& end, const std::string& start,
+ const std::string& line)
+{
+ auto pos1 = line.find(start);
+ auto pos2 = line.find(end);
+
+ if (pos1 == std::string::npos || pos2 == std::string::npos || pos1 >= pos2)
+ {
+ return false;
+ }
+
+ return static_cast<int>(pos2 - pos1 - 1);
+}
+
+bool CpldLatticeManager::jedFileParser()
+{
+ bool cfStart = false;
+ bool ufmStart = false; // for isLCMXO3D
+ bool ufmPrepare = false;
+ bool versionStart = false;
+ bool checksumStart = false;
+ bool ebrInitDataStart = false;
+ int numberSize = 0;
+
+ if (image == nullptr || imageSize == 0)
+ {
+ lg2::error(
+ "Error: JED file is empty or not found. Please check the file.");
+ return false;
+ }
+
+ // Convert binary data to a string
+ std::string content(reinterpret_cast<const char*>(image), imageSize);
+ // Use stringstream to simulate file reading
+ std::istringstream iss(content);
+ std::string line;
+
+ // Parsing JED file
+ while (getline(iss, line))
+ {
+ if (line.rfind(tagQF, 0) == 0)
+ {
+ numberSize = findNumberSize("*", "F", line);
+ if (numberSize <= 0)
+ {
+ lg2::error("Error in parsing QF tag");
+ return false;
+ }
+ static constexpr auto start = tagQF.length();
+ fwInfo.QF = std::stoul(line.substr(start, numberSize));
+
+ lg2::debug("QF Size = {QF}", "QF", fwInfo.QF);
+ }
+ else if (line.rfind(tagCFStart, 0) == 0)
+ {
+ cfStart = true;
+ }
+ else if (enableUpdateEbrInit && line.rfind(tagEbrInitData, 0) == 0)
+ {
+ ebrInitDataStart = true;
+ }
+ else if (ufmPrepare)
+ {
+ ufmPrepare = false;
+ ufmStart = true;
+ continue;
+ }
+ else if (line.rfind(tagUserCode, 0) == 0)
+ {
+ versionStart = true;
+ }
+ else if (line.rfind(tagChecksum, 0) == 0)
+ {
+ checksumStart = true;
+ }
+
+ if (line.rfind("NOTE DEVICE NAME:", 0) == 0)
+ {
+ lg2::error(line.c_str());
+ if (line.find(chip) != std::string::npos)
+ {
+ lg2::debug("[OK] The image device name match with chip name");
+ }
+ else
+ {
+ lg2::debug("STOP UPDATEING: The image not match with chip.");
+ return false;
+ }
+ }
+
+ if (cfStart)
+ {
+ // L000
+ if ((line.rfind(tagCFStart, 0)) && (line.size() != 1))
+ {
+ if ((line.rfind('0', 0) == 0) || (line.rfind('1', 0) == 0))
+ {
+ while (!line.empty())
+ {
+ auto binaryStr = line.substr(0, 8);
+ try
+ {
+ fwInfo.cfgData.push_back(
+ std::stoi(binaryStr, 0, 2));
+ line.erase(0, 8);
+ }
+ catch (const std::invalid_argument& error)
+ {
+ break;
+ }
+ catch (...)
+ {
+ lg2::error("Error while parsing CF section");
+ return false;
+ }
+ }
+ }
+ else
+ {
+ lg2::debug("CF size = {CF}", "CF", fwInfo.cfgData.size());
+ cfStart = false;
+ if (!ebrInitDataStart)
+ {
+ ufmPrepare = true;
+ }
+ }
+ }
+ }
+ else if (enableUpdateEbrInit && ebrInitDataStart)
+ {
+ // NOTE EBR_INIT DATA
+ if ((line.rfind(tagEbrInitData, 0)) && (line.size() != 1))
+ {
+ if ((line.rfind('L', 0)) && (line.size() != 1))
+ {
+ if ((line.rfind('0', 0) == 0) || (line.rfind('1', 0) == 0))
+ {
+ while (!line.empty())
+ {
+ auto binaryStr = line.substr(0, 8);
+ try
+ {
+ fwInfo.cfgData.push_back(
+ std::stoi(binaryStr, 0, 2));
+ line.erase(0, 8);
+ }
+ catch (const std::invalid_argument& error)
+ {
+ break;
+ }
+ catch (...)
+ {
+ lg2::error("Error while parsing CF section");
+ return false;
+ }
+ }
+ }
+ else
+ {
+ lg2::debug("CF size with EBR_INIT Data = {CF}", "CF",
+ fwInfo.cfgData.size());
+ ebrInitDataStart = false;
+ ufmPrepare = true;
+ }
+ }
+ }
+ }
+ else if ((checksumStart) && (line.size() != 1))
+ {
+ checksumStart = false;
+ numberSize = findNumberSize("*", "C", line);
+ if (numberSize <= 0)
+ {
+ lg2::error("Error in parsing checksum");
+ return false;
+ }
+ static constexpr auto start = tagChecksum.length();
+ std::istringstream iss(line.substr(start, numberSize));
+ iss >> std::hex >> fwInfo.checksum;
+
+ lg2::debug("Checksum = {CHECKSUM}", "CHECKSUM", fwInfo.checksum);
+ }
+ else if (versionStart)
+ {
+ if ((line.rfind(tagUserCode, 0)) && (line.size() != 1))
+ {
+ versionStart = false;
+
+ if (line.rfind(tagUH, 0) == 0)
+ {
+ numberSize = findNumberSize("*", "H", line);
+ if (numberSize <= 0)
+ {
+ lg2::error("Error in parsing version");
+ return false;
+ }
+ static constexpr auto start = tagUH.length();
+ std::istringstream iss(line.substr(start, numberSize));
+ iss >> std::hex >> fwInfo.version;
+
+ lg2::debug("UserCode = {USERCODE}", "USERCODE",
+ fwInfo.version);
+ }
+ }
+ }
+ else if (ufmStart)
+ {
+ if ((line.rfind('L', 0)) && (line.size() != 1))
+ {
+ if ((line.rfind('0', 0) == 0) || (line.rfind('1', 0) == 0))
+ {
+ while (!line.empty())
+ {
+ auto binaryStr = line.substr(0, 8);
+ try
+ {
+ fwInfo.ufmData.push_back(
+ std::stoi(binaryStr, 0, 2));
+ line.erase(0, 8);
+ }
+ catch (const std::invalid_argument& error)
+ {
+ break;
+ }
+ catch (...)
+ {
+ lg2::error("Error while parsing UFM section");
+ return false;
+ }
+ }
+ }
+ else
+ {
+ lg2::debug("UFM size = {UFM}", "UFM",
+ fwInfo.ufmData.size());
+ ufmStart = false;
+ }
+ }
+ }
+ }
+
+ return true;
+}
+
+bool CpldLatticeManager::verifyChecksum()
+{
+ // Compute check sum
+ unsigned int jedFileCheckSum = 0;
+ for (unsigned i = 0; i < fwInfo.cfgData.size(); i++)
+ {
+ jedFileCheckSum += reverse_bit(fwInfo.cfgData.at(i));
+ }
+ for (unsigned i = 0; i < fwInfo.ufmData.size(); i++)
+ {
+ jedFileCheckSum += reverse_bit(fwInfo.ufmData.at(i));
+ }
+ lg2::debug("jedFileCheckSum = {JEDFILECHECKSUM}", "JEDFILECHECKSUM",
+ jedFileCheckSum);
+ jedFileCheckSum = jedFileCheckSum & 0xffff;
+
+ if ((fwInfo.checksum != jedFileCheckSum) || (fwInfo.checksum == 0))
+ {
+ lg2::error("CPLD JED File CheckSum Error = {JEDFILECHECKSUM}",
+ "JEDFILECHECKSUM", jedFileCheckSum);
+ return false;
+ }
+
+ lg2::debug("JED File Checksum compare success");
+ return true;
+}
+
+sdbusplus::async::task<bool> CpldLatticeManager::readDeviceId()
+{
+ auto sched = context_friend::get_scheduler(ctx);
+ std::vector<uint8_t> command = {commandReadDeviceId, 0x0, 0x0, 0x0};
+ constexpr size_t resSize = 4;
+ std::vector<uint8_t> readData(resSize, 0);
+ bool success = co_await stdexec::starts_on(
+ sched, i2cInterface.sendReceive(command.data(), command.size(),
+ readData.data(), resSize));
+ if (!success)
+ {
+ lg2::error(
+ "Fail to read device Id. Please check the I2C bus and address.");
+ co_return false;
+ }
+
+ auto chipWantToUpdate = xo2xo3DeviceIdMap.find(chip);
+
+ if (chipWantToUpdate != xo2xo3DeviceIdMap.end() &&
+ chipWantToUpdate->second == readData)
+ {
+ 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 not match with chip. Only the following chip names are supported: ");
+ for (const auto& chip : xo2xo3DeviceIdMap)
+ {
+ lg2::error(chip.first.c_str());
+ }
+ co_return false;
+}
+
+sdbusplus::async::task<bool> CpldLatticeManager::enableProgramMode()
+{
+ auto sched = context_friend::get_scheduler(ctx);
+ std::vector<uint8_t> command = {commandEnableConfigMode, 0x08, 0x0, 0x0};
+ bool success = co_await stdexec::starts_on(
+ sched,
+ i2cInterface.sendReceive(command.data(), command.size(), nullptr, 0));
+
+ if (!success)
+ {
+ 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> CpldLatticeManager::eraseFlash()
+{
+ auto sched = context_friend::get_scheduler(ctx);
+ std::vector<uint8_t> command;
+
+ 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")
+ {
+ command = {commandEraseFlash, 0x00, 0x01, 0x00};
+ }
+ else if (target == "CFG1")
+ {
+ command = {commandEraseFlash, 0x00, 0x02, 0x00};
+ }
+ else
+ {
+ lg2::error("Error: unknown target.");
+ co_return false;
+ }
+ }
+ else
+ {
+ command = {commandEraseFlash, 0xC, 0x0, 0x0};
+ }
+
+ bool success = co_await stdexec::starts_on(
+ sched,
+ i2cInterface.sendReceive(command.data(), command.size(), nullptr, 0));
+ if (!success)
+ {
+ 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> CpldLatticeManager::resetConfigFlash()
+{
+ auto sched = context_friend::get_scheduler(ctx);
+ std::vector<uint8_t> command;
+ 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")
+ {
+ command = {commandResetConfigFlash, 0x00, 0x01, 0x00};
+ }
+ else if (target == "CFG1")
+ {
+ command = {commandResetConfigFlash, 0x00, 0x02, 0x00};
+ }
+ else
+ {
+ lg2::error(
+ "Error: unknown target. Only CFG0 and CFG1 are supported.");
+ co_return false;
+ }
+ }
+ else
+ {
+ command = {commandResetConfigFlash, 0x0, 0x0, 0x0};
+ }
+
+ co_return co_await stdexec::starts_on(
+ sched,
+ i2cInterface.sendReceive(command.data(), command.size(), nullptr, 0));
+}
+
+sdbusplus::async::task<bool> CpldLatticeManager::writeProgramPage()
+{
+ /*
+ Program one NVCM/Flash page. Can be
+ used to program the NVCM0/CFG or
+ NVCM1/UFM.
+ */
+ auto sched = context_friend::get_scheduler(ctx);
+ std::vector<uint8_t> command = {commandProgramPage, 0x0, 0x0, 0x01};
+ size_t iterSize = 16;
+
+ for (size_t i = 0; i < fwInfo.cfgData.size(); i += iterSize)
+ {
+ double progressRate =
+ ((double(i) / 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())
+ ? iterSize
+ : (fwInfo.cfgData.size() - i);
+ std::vector<uint8_t> data = command;
+
+ data.insert(
+ data.end(), fwInfo.cfgData.begin() + static_cast<std::ptrdiff_t>(i),
+ fwInfo.cfgData.begin() + static_cast<std::ptrdiff_t>(i + len));
+
+ bool success = co_await stdexec::starts_on(
+ sched,
+ i2cInterface.sendReceive(data.data(), data.size(), nullptr, 0));
+ if (!success)
+ {
+ co_return false;
+ }
+
+ /*
+ 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()))
+ {
+ lg2::error("Wait busy and verify fail");
+ co_return false;
+ }
+
+ data.clear();
+ }
+
+ co_return true;
+}
+
+sdbusplus::async::task<bool> CpldLatticeManager::programUserCode()
+{
+ auto sched = context_friend::get_scheduler(ctx);
+ std::vector<uint8_t> command = {commandProgramUserCode, 0x0, 0x0, 0x0};
+ for (int i = 3; i >= 0; i--)
+ {
+ command.push_back((fwInfo.version >> (i * 8)) & 0xFF);
+ }
+ bool success = co_await stdexec::starts_on(
+ sched,
+ i2cInterface.sendReceive(command.data(), command.size(), nullptr, 0));
+
+ if (!success)
+ {
+ co_return false;
+ }
+
+ if (!(co_await waitBusyAndVerify()))
+ {
+ lg2::error("Wait busy and verify fail");
+ co_return false;
+ }
+
+ co_return true;
+}
+
+sdbusplus::async::task<bool> CpldLatticeManager::programDone()
+{
+ auto sched = context_friend::get_scheduler(ctx);
+ std::vector<uint8_t> command = {commandProgramDone, 0x0, 0x0, 0x0};
+ bool success = co_await stdexec::starts_on(
+ sched,
+ i2cInterface.sendReceive(command.data(), command.size(), nullptr, 0));
+
+ if (!success)
+ {
+ co_return false;
+ }
+ if (!(co_await waitBusyAndVerify()))
+ {
+ lg2::error("Wait busy and verify fail");
+ co_return false;
+ }
+
+ co_return true;
+}
+
+sdbusplus::async::task<bool> CpldLatticeManager::disableConfigInterface()
+{
+ auto sched = context_friend::get_scheduler(ctx);
+ std::vector<uint8_t> command = {commandDisableConfigInterface, 0x0, 0x0};
+
+ bool success = co_await stdexec::starts_on(
+ sched,
+ i2cInterface.sendReceive(command.data(), command.size(), nullptr, 0));
+
+ co_return success;
+}
+
+sdbusplus::async::task<bool> CpldLatticeManager::waitBusyAndVerify()
+{
+ uint8_t retry = 0;
+
+ while (retry <= busyWaitmaxRetry)
+ {
+ uint8_t busyFlag = 0xff;
+
+ if (!(co_await readBusyFlag(busyFlag)))
+ {
+ 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
+ uint8_t statusReg = 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> CpldLatticeManager::readBusyFlag(uint8_t& busyFlag)
+{
+ auto sched = context_friend::get_scheduler(ctx);
+ std::vector<uint8_t> command = {commandReadBusyFlag, 0x0, 0x0, 0x0};
+ constexpr size_t resSize = 1;
+ std::vector<uint8_t> readData(resSize, 0);
+ bool success = co_await stdexec::starts_on(
+ sched, i2cInterface.sendReceive(command.data(), command.size(),
+ readData.data(), resSize));
+
+ if (!success || (readData.size() != resSize))
+ {
+ co_return false;
+ }
+ busyFlag = readData.at(0);
+ co_return true;
+}
+
+sdbusplus::async::task<bool> CpldLatticeManager::readStatusReg(
+ uint8_t& statusReg)
+{
+ auto sched = context_friend::get_scheduler(ctx);
+ std::vector<uint8_t> command = {commandReadStatusReg, 0x0, 0x0, 0x0};
+ constexpr size_t resSize = 4;
+ std::vector<uint8_t> readData(resSize, 0);
+ bool success = co_await stdexec::starts_on(
+ sched, i2cInterface.sendReceive(command.data(), command.size(),
+ readData.data(), resSize));
+
+ if (!success || (readData.size() != resSize))
+ {
+ 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 = readData.at(2);
+ co_return true;
+}
+
+sdbusplus::async::task<bool> CpldLatticeManager::readUserCode(
+ uint32_t& userCode)
+{
+ auto sched = context_friend::get_scheduler(ctx);
+ std::vector<uint8_t> command = {commandReadFwVersion, 0x0, 0x0, 0x0};
+ constexpr size_t resSize = 4;
+ std::vector<uint8_t> readData(resSize, 0);
+ bool success = co_await stdexec::starts_on(
+ sched, i2cInterface.sendReceive(command.data(), command.size(),
+ readData.data(), resSize));
+
+ if (!success)
+ {
+ co_return false;
+ }
+
+ for (size_t i = 0; i < resSize; i++)
+ {
+ userCode |= readData.at(i) << ((3 - i) * 8);
+ }
+ co_return true;
+}
+
+sdbusplus::async::task<bool> CpldLatticeManager::XO2XO3FamilyUpdate(
+ std::function<bool(int)> progressCallBack)
+{
+ if (progressCallBack == nullptr)
+ {
+ lg2::error("Error: progressCallBack is null.");
+ co_return false;
+ }
+
+ if (!(co_await readDeviceId()))
+ {
+ co_return false;
+ }
+ progressCallBack(10);
+
+ if (!jedFileParser())
+ {
+ lg2::error("JED file parsing failed");
+ co_return false;
+ }
+ progressCallBack(15);
+
+ if (!verifyChecksum())
+ {
+ lg2::error("Checksum verification failed");
+ co_return false;
+ }
+ progressCallBack(20);
+
+ if (!isLCMXO3D)
+ {
+ lg2::error("is not LCMXO3D");
+ }
+
+ lg2::debug("Starts to update ...");
+ lg2::debug("Enable program mode.");
+ progressCallBack(25);
+
+ co_await waitBusyAndVerify();
+
+ if (!(co_await enableProgramMode()))
+ {
+ lg2::error("Enable program mode failed.");
+ co_return false;
+ }
+ progressCallBack(30);
+
+ lg2::debug("Erase flash.");
+ if (!(co_await eraseFlash()))
+ {
+ lg2::error("Erase flash failed.");
+ co_return false;
+ }
+ progressCallBack(40);
+
+ lg2::debug("Reset config flash.");
+ if (!(co_await resetConfigFlash()))
+ {
+ lg2::error("Reset config flash failed.");
+ co_return false;
+ }
+ progressCallBack(50);
+
+ lg2::debug("Write program page ...");
+ if (!(co_await writeProgramPage()))
+ {
+ lg2::error("Write program page failed.");
+ co_return false;
+ }
+ lg2::debug("Write program page done.");
+ progressCallBack(60);
+
+ lg2::debug("Program user code.");
+ if (!(co_await programUserCode()))
+ {
+ lg2::error("Program user code failed.");
+ co_return false;
+ }
+ progressCallBack(70);
+
+ if (!(co_await programDone()))
+ {
+ lg2::error("Program not done.");
+ co_return false;
+ }
+ progressCallBack(80);
+
+ lg2::debug("Disable config interface.");
+ if (!(co_await disableConfigInterface()))
+ {
+ lg2::error("Disable Config Interface failed.");
+ co_return false;
+ }
+ progressCallBack(90);
+
+ lg2::debug("Update completed!");
+
+ co_return true;
+}
+
+sdbusplus::async::task<bool> CpldLatticeManager::updateFirmware(
+ std::function<bool(int)> progressCallBack)
+{
+ if (xo2xo3DeviceIdMap.find(chip) != xo2xo3DeviceIdMap.end())
+ {
+ co_return co_await XO2XO3FamilyUpdate(progressCallBack);
+ }
+ lg2::error("Unsupported chip type: {CHIP}", "CHIP", chip);
+ co_return false;
+}
+
+std::string 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> CpldLatticeManager::getVersion(
+ std::string& version)
+{
+ uint32_t userCode = 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;
+}
diff --git a/cpld/lattice/lattice.hpp b/cpld/lattice/lattice.hpp
new file mode 100644
index 0000000..0bdc2bb
--- /dev/null
+++ b/cpld/lattice/lattice.hpp
@@ -0,0 +1,63 @@
+#pragma once
+#include "common/include/i2c/i2c.hpp"
+
+#include <chrono>
+#include <iostream>
+#include <string_view>
+#include <utility>
+
+struct cpldI2cInfo
+{
+ unsigned long int QF; // Quantity of Fuses
+ unsigned int* UFM; // User Flash Memory
+ unsigned int version;
+ unsigned int checksum;
+ std::vector<uint8_t> cfgData;
+ std::vector<uint8_t> ufmData;
+};
+
+class CpldLatticeManager
+{
+ public:
+ CpldLatticeManager(sdbusplus::async::context& ctx, const uint16_t bus,
+ const uint8_t address, const uint8_t* image,
+ size_t imageSize, const std::string& chip,
+ const std::string& target, const bool debugMode) :
+ ctx(ctx), image(image), imageSize(imageSize), chip(chip),
+ target(target), debugMode(debugMode),
+ i2cInterface(phosphor::i2c::I2C(bus, address))
+ {}
+ sdbusplus::async::task<bool> updateFirmware(
+ std::function<bool(int)> progressCallBack);
+ sdbusplus::async::task<bool> getVersion(std::string& version);
+
+ private:
+ sdbusplus::async::context& ctx;
+ cpldI2cInfo fwInfo{};
+ const uint8_t* image;
+ size_t imageSize;
+ std::string chip;
+ std::string target;
+ bool isLCMXO3D = false;
+ bool debugMode = false;
+ phosphor::i2c::I2C i2cInterface;
+
+ sdbusplus::async::task<bool> XO2XO3FamilyUpdate(
+ std::function<bool(int)> progressCallBack);
+
+ int indexof(const char* str, const char* ptn);
+ bool jedFileParser();
+ bool verifyChecksum();
+ sdbusplus::async::task<bool> readDeviceId();
+ sdbusplus::async::task<bool> enableProgramMode();
+ sdbusplus::async::task<bool> eraseFlash();
+ sdbusplus::async::task<bool> resetConfigFlash();
+ sdbusplus::async::task<bool> writeProgramPage();
+ sdbusplus::async::task<bool> programUserCode();
+ sdbusplus::async::task<bool> programDone();
+ sdbusplus::async::task<bool> disableConfigInterface();
+ sdbusplus::async::task<bool> readBusyFlag(uint8_t& busyFlag);
+ sdbusplus::async::task<bool> readStatusReg(uint8_t& statusReg);
+ sdbusplus::async::task<bool> waitBusyAndVerify();
+ sdbusplus::async::task<bool> readUserCode(uint32_t& userCode);
+};
diff --git a/cpld/meson.build b/cpld/meson.build
new file mode 100644
index 0000000..2ab8793
--- /dev/null
+++ b/cpld/meson.build
@@ -0,0 +1,31 @@
+cpld_src = files('cpld.cpp', 'cpld_interface.cpp', 'cpld_software_manager.cpp')
+
+cpld_vendor_src = files('lattice/interface.cpp', 'lattice/lattice.cpp')
+
+executable(
+ 'phosphor-cpld-software-update',
+ cpld_src,
+ cpld_vendor_src,
+ include_directories: [include_directories('.'), common_include, libi2c_inc],
+ dependencies: [
+ pdi_dep,
+ phosphor_logging_dep,
+ sdbusplus_dep,
+ libpldm_dep,
+ libi2c_dep,
+ ],
+ link_with: [libpldmutil, software_common_lib, libi2c_dev],
+ link_args: '-li2c',
+ install_dir: get_option('libexecdir') / 'phosphor-code-mgmt',
+ install: true,
+)
+
+systemd_system_unit_dir = dependency('systemd').get_variable(
+ 'systemdsystemunitdir',
+ pkgconfig_define: ['prefix', get_option('prefix')],
+)
+
+install_data(
+ 'xyz.openbmc_project.Software.CPLD.service',
+ install_dir: systemd_system_unit_dir,
+)
diff --git a/cpld/xyz.openbmc_project.Software.CPLD.service b/cpld/xyz.openbmc_project.Software.CPLD.service
new file mode 100644
index 0000000..4ec7273
--- /dev/null
+++ b/cpld/xyz.openbmc_project.Software.CPLD.service
@@ -0,0 +1,14 @@
+[Unit]
+Description=CPLD Code Update Daemon
+After=xyz.openbmc_project.ObjectMapper
+After=xyz.openbmc_project.EntityManager.service
+
+[Service]
+Restart=always
+Type=dbus
+BusName=xyz.openbmc_project.Software.CPLD
+RemainAfterExit=no
+ExecStart=/usr/libexec/phosphor-code-mgmt/phosphor-cpld-software-update
+
+[Install]
+WantedBy=multi-user.target
diff --git a/meson.build b/meson.build
index 6a03c9a..f1a5428 100644
--- a/meson.build
+++ b/meson.build
@@ -72,6 +72,8 @@
'i2cvr-software-update',
).allowed() or get_option(
'eepromdevice-software-update',
+).allowed() or get_option(
+ 'cpld-software-update',
).allowed()
if common_build
@@ -90,8 +92,13 @@
subdir('bios')
endif
-if get_option('i2cvr-software-update').allowed()
+if get_option('i2cvr-software-update').allowed() or get_option(
+ 'cpld-software-update',
+).allowed()
subdir('common/i2c/')
+endif
+
+if get_option('i2cvr-software-update').allowed()
subdir('i2c-vr')
endif
@@ -99,6 +106,10 @@
subdir('eeprom-device')
endif
+if get_option('cpld-software-update').allowed()
+ subdir('cpld')
+endif
+
if build_tests.allowed()
subdir('test')
endif
diff --git a/meson.options b/meson.options
index c178e69..6c09029 100644
--- a/meson.options
+++ b/meson.options
@@ -57,6 +57,13 @@
)
option(
+ 'cpld-software-update',
+ type: 'feature',
+ value: 'enabled',
+ description: 'Enable update of CPLD',
+)
+
+option(
'side-switch-on-boot',
type: 'feature',
value: 'enabled',