eeprom device code updater
This commit introduces a code updater for EEPROM-based devices,
such as Retimer connected to an EEPROM.
Key features include:
- Configuring MUX GPIOs to switch EEPROM access to the BMC
- Writing firmware directly to the EEPROM flash memory
The behavior and configuration of this updater are managed via EM,
enabling flexibility and adaptability across different hardware setups.
https://gerrit.openbmc.org/c/openbmc/entity-manager/+/77198
Tested on Harma:
1. Display the fw inventory
```
curl --silent $creds https://$bmc/redfish/v1/UpdateService/FirmwareInventory
```
```
{
"@odata.id": "/redfish/v1/UpdateService/FirmwareInventory",
"@odata.type": "#SoftwareInventoryCollection.SoftwareInventoryCollection",
"Members": [
{...},
{
"@odata.id": "/redfish/v1/UpdateService/FirmwareInventory/MB_Retimer_9355"
},
{...}
],
"Members@odata.count": 26,
"Name": "Software Inventory Collection"
}
```
2. Query Retimer version.
```
curl $creds https://$bmc/redfish/v1/UpdateService/FirmwareInventory/MB_Retimer_9355
```
```
{
"@odata.id": "/redfish/v1/UpdateService/FirmwareInventory/MB_Retimer_9355",
"@odata.type": "#SoftwareInventory.v1_1_0.SoftwareInventory",
"Description": "Unknown image",
"Id": "MB_Retimer_9355",
"Name": "Software Inventory",
"Status": {
"Health": "Warning",
"HealthRollup": "OK",
"State": "Disabled"
},
"Updateable": false,
"Version": "1.31.23"
}
```
3. Trigger the fw update via redfish.
```
curl -k ${creds} \
-H "Content-Type:multipart/form-data" \
-X POST \
-F UpdateParameters="{\"Targets\":[\"/redfish/v1/UpdateService/FirmwareInventory/MB_Retimer_9355\"],\"@Redfish.OperationApplyTime\":\"Immediate\"};type=application/json" \
-F "UpdateFile=@${fwpath};type=application/octet-stream" \
https://${bmc}/redfish/v1/UpdateService/update
```
4. Task is returned
```
{
"@odata.id": "/redfish/v1/TaskService/Tasks/0",
"@odata.type": "#Task.v1_4_3.Task",
"Id": "0",
"TaskState": "Running",
"TaskStatus": "OK"
}
```
5. Query Task status
```
curl --silent $creds https://$bmc/redfish/v1/TaskService/Tasks/0
```
```
{
"@odata.id": "/redfish/v1/TaskService/Tasks/0",
"@odata.type": "#Task.v1_4_3.Task",
"EndTime": "2025-04-17T08:35:58+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 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 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 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 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 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-04-17T08:35:12+00:00",
"TaskMonitor": "/redfish/v1/TaskService/TaskMonitors/0",
"TaskState": "Completed",
"TaskStatus": "OK"
}
```
6. Display the fw inventory with newly updated fw.
```
curl --silent $creds https://$bmc/redfish/v1/UpdateService/FirmwareInventory
```
```
{
"@odata.id": "/redfish/v1/UpdateService/FirmwareInventory",
"@odata.type": "#SoftwareInventoryCollection.SoftwareInventoryCollection",
"Members": [
{...},
{
"@odata.id": "/redfish/v1/UpdateService/FirmwareInventory/MB_Retimer_5686"
},
{...}
],
"Members@odata.count": 26,
"Name": "Software Inventory Collection"
}
```
7. Query the new fw version.
```
curl $creds https://$bmc/redfish/v1/UpdateService/FirmwareInventory/MB_Retimer_5686
```
```
{
"@odata.id": "/redfish/v1/UpdateService/FirmwareInventory/MB_Retimer_5686",
"@odata.type": "#SoftwareInventory.v1_1_0.SoftwareInventory",
"Description": "Unknown image",
"Id": "MB_Retimer_5686",
"Name": "Software Inventory",
"Status": {
"Health": "OK",
"HealthRollup": "OK",
"State": "Enabled"
},
"Updateable": false,
"Version": "2.8.19"
}
```
Change-Id: Ic09f9289fa16d5df738e792348c0c817a15cb808
Signed-off-by: Kevin Tung <Kevin.Tung@quantatw.com>
diff --git a/eeprom-device/eeprom_device_software_manager.cpp b/eeprom-device/eeprom_device_software_manager.cpp
new file mode 100644
index 0000000..b29cf82
--- /dev/null
+++ b/eeprom-device/eeprom_device_software_manager.cpp
@@ -0,0 +1,198 @@
+#include "eeprom_device_software_manager.hpp"
+
+#include "common/include/dbus_helper.hpp"
+#include "eeprom_device.hpp"
+
+#include <phosphor-logging/lg2.hpp>
+#include <sdbusplus/async.hpp>
+#include <xyz/openbmc_project/ObjectMapper/client.hpp>
+
+#include <fstream>
+#include <optional>
+#include <sstream>
+
+PHOSPHOR_LOG2_USING;
+
+namespace SoftwareInf = phosphor::software;
+
+const std::vector<std::string> emConfigTypes = {"PT5161L"};
+
+void EEPROMDeviceSoftwareManager::start()
+{
+ std::vector<std::string> configIntfs;
+ configIntfs.reserve(emConfigTypes.size());
+
+ std::transform(emConfigTypes.begin(), emConfigTypes.end(),
+ std::back_inserter(configIntfs),
+ [](const std::string& type) {
+ return "xyz.openbmc_project.Configuration." + type;
+ });
+
+ ctx.spawn(initDevices(configIntfs));
+ ctx.run();
+}
+
+// NOLINTBEGIN(readability-static-accessed-through-instance)
+sdbusplus::async::task<bool> EEPROMDeviceSoftwareManager::initDevice(
+ const std::string& service, const std::string& path, SoftwareConfig& config)
+// NOLINTEND(readability-static-accessed-through-instance)
+{
+ const std::string configIface =
+ "xyz.openbmc_project.Configuration." + config.configType;
+
+ std::optional<uint64_t> bus = 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> type =
+ co_await dbusGetRequiredProperty<std::string>(ctx, service, path,
+ configIface, "Type");
+
+ std::optional<std::string> fwDevice =
+ co_await dbusGetRequiredProperty<std::string>(
+ ctx, service, path, configIface, "FirmwareDevice");
+
+ if (!bus.has_value() || !address.has_value() || !type.has_value() ||
+ !fwDevice.has_value())
+ {
+ error("Missing EEPROM device config property");
+ co_return false;
+ }
+
+ debug("EEPROM Device: Bus={BUS}, Address={ADDR}, Type={TYPE}, "
+ "Firmware Device={DEVICE}",
+ "BUS", bus.value(), "ADDR", address.value(), "TYPE", type.value(),
+ "DEVICE", fwDevice.value());
+
+ std::unique_ptr<DeviceVersion> deviceVersion =
+ getVersionProvider(type.value(), bus.value(), address.value());
+
+ if (!deviceVersion)
+ {
+ error("Failed to get version provider for chip type: {CHIP}", "CHIP",
+ type.value());
+ co_return false;
+ }
+
+ std::string version = deviceVersion->getVersion();
+
+ using ObjectMapper =
+ sdbusplus::client::xyz::openbmc_project::ObjectMapper<>;
+
+ auto mapper = ObjectMapper(ctx)
+ .service(ObjectMapper::default_service)
+ .path(ObjectMapper::instance_path);
+
+ auto res =
+ co_await mapper.get_sub_tree("/xyz/openbmc_project/inventory", 0, {});
+
+ bus.reset();
+ address.reset();
+ type.reset();
+
+ for (auto& [p, v] : res)
+ {
+ if (!p.ends_with(fwDevice.value()))
+ {
+ continue;
+ }
+
+ for (auto& [s, ifaces] : v)
+ {
+ for (std::string& iface : ifaces)
+ {
+ if (iface.starts_with("xyz.openbmc_project.Configuration."))
+ {
+ bus = co_await dbusGetRequiredProperty<uint64_t>(
+ ctx, s, p, iface, "Bus");
+
+ address = co_await dbusGetRequiredProperty<uint64_t>(
+ ctx, s, p, iface, "Address");
+
+ type = co_await dbusGetRequiredProperty<std::string>(
+ ctx, s, p, iface, "Type");
+ break;
+ }
+ }
+ if (bus.has_value() && address.has_value() && type.has_value())
+ {
+ break;
+ }
+ }
+ break;
+ }
+
+ if (!bus.has_value() || !address.has_value() || !type.has_value())
+ {
+ error("Missing EEPROM config property");
+ co_return false;
+ }
+
+ debug("EEPROM: Bus={BUS}, Address={ADDR}, Type={TYPE}", "BUS", bus.value(),
+ "ADDR", address.value(), "TYPE", type.value());
+
+ const std::string configIfaceMux = configIface + ".MuxOutputs";
+ std::vector<std::string> gpioLines;
+ std::vector<bool> gpioPolarities;
+
+ for (size_t i = 0; true; i++)
+ {
+ const std::string iface = configIfaceMux + std::to_string(i);
+
+ std::optional<std::string> name =
+ co_await dbusGetRequiredProperty<std::string>(ctx, service, path,
+ iface, "Name");
+
+ std::optional<std::string> polarity =
+ co_await dbusGetRequiredProperty<std::string>(ctx, service, path,
+ iface, "Polarity");
+
+ if (!name.has_value() || !polarity.has_value())
+ {
+ break;
+ }
+
+ gpioLines.push_back(name.value());
+ gpioPolarities.push_back(polarity.value() == "High");
+ }
+
+ for (size_t i = 0; i < gpioLines.size(); i++)
+ {
+ debug("Mux gpio {NAME} polarity = {VALUE}", "NAME", gpioLines[i],
+ "VALUE", gpioPolarities[i]);
+ }
+
+ auto eepromDevice = std::make_unique<EEPROMDevice>(
+ ctx, static_cast<uint16_t>(bus.value()),
+ static_cast<uint8_t>(address.value()), type.value(), gpioLines,
+ gpioPolarities, std::move(deviceVersion), config, this);
+
+ std::unique_ptr<SoftwareInf::Software> software =
+ std::make_unique<SoftwareInf::Software>(ctx, *eepromDevice);
+
+ software->setVersion(version.empty() ? "Unknown" : version);
+
+ std::set<RequestedApplyTimes> allowedApplyTimes = {
+ RequestedApplyTimes::Immediate, RequestedApplyTimes::OnReset};
+
+ software->enableUpdate(allowedApplyTimes);
+
+ eepromDevice->softwareCurrent = std::move(software);
+
+ devices.insert({config.objectPath, std::move(eepromDevice)});
+
+ co_return true;
+}
+
+int main()
+{
+ sdbusplus::async::context ctx;
+
+ EEPROMDeviceSoftwareManager eepromDeviceSoftwareManager(ctx);
+
+ eepromDeviceSoftwareManager.start();
+ return 0;
+}