SPI device code updater
This code updater is for updating spi flash devices.
It can for example update the host firmware on different server boards
and has following features:
- power down the host before update
- set mux gpios to access spi flash
- (very limited) communication with ME (Management Engine)
- use flashrom to utilize fw with IFD (Intel Flash Descriptor)
- otherwise directly write to the flash chip.
The behavior of this code updater can be configured via EM.
Tested: on Tyan S8030 and Tyan S5549 Board. Steps below.
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/717f6f4d"
},
{
"@odata.id": "/redfish/v1/UpdateService/FirmwareInventory/HostSPIFlash_4950"
},
{
"@odata.id": "/redfish/v1/UpdateService/FirmwareInventory/bios_active"
}
],
"Members@odata.count": 3,
"Name": "Software Inventory Collection"
}
```
2. Query BIOS version.
The version is "unknown" here since currently there is no interface
enabled via which to query it.
```
curl $creds https://$bmc/redfish/v1/UpdateService/FirmwareInventory/HostSPIFlash_4950
```
```
{
"@odata.id": "/redfish/v1/UpdateService/FirmwareInventory/HostSPIFlash_4950",
"@odata.type": "#SoftwareInventory.v1_1_0.SoftwareInventory",
"Description": "Unknown image",
"Id": "HostSPIFlash_4950",
"Name": "Software Inventory",
"Status": {
"Health": "Warning",
"HealthRollup": "OK",
"State": "Disabled"
},
"Updateable": true,
"Version": "unknown"
}
```
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/HostSPIFlash_6041\"],\"@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-02-18T14:05:46+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 10 percent complete.",
"MessageArgs": [
"0",
"10"
],
"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 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 70 percent complete.",
"MessageArgs": [
"0",
"70"
],
"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": 90,
"StartTime": "2025-02-18T14:04:47+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/717f6f4d"
},
{
"@odata.id": "/redfish/v1/UpdateService/FirmwareInventory/HostSPIFlash_8728"
},
{
"@odata.id": "/redfish/v1/UpdateService/FirmwareInventory/bios_active"
}
],
"Members@odata.count": 3,
"Name": "Software Inventory Collection"
}
```
7. Query the new fw version.
The version is 'mycompversion' since that's what has been set in the
pldm fw update package for testing.
```
curl $creds https://$bmc/redfish/v1/UpdateService/FirmwareInventory/HostSPIFlash_8728
```
```
{
"@odata.id": "/redfish/v1/UpdateService/FirmwareInventory/HostSPIFlash_8728",
"@odata.type": "#SoftwareInventory.v1_1_0.SoftwareInventory",
"Description": "Unknown image",
"Id": "HostSPIFlash_8728",
"Name": "Software Inventory",
"Status": {
"Health": "OK",
"HealthRollup": "OK",
"State": "Enabled"
},
"Updateable": false,
"Version": "mycompversion"
}
```
Change-Id: I27803b7fded71af2364c2f55fad841a410603dec
Signed-off-by: Alexander Hansen <alexander.hansen@9elements.com>
diff --git a/bios/bios_software_manager.cpp b/bios/bios_software_manager.cpp
new file mode 100644
index 0000000..61a9fef
--- /dev/null
+++ b/bios/bios_software_manager.cpp
@@ -0,0 +1,104 @@
+#include "bios_software_manager.hpp"
+
+#include "common/include/dbus_helper.hpp"
+#include "common/include/software_manager.hpp"
+#include "spi_device.hpp"
+
+#include <gpiod.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <sdbusplus/async.hpp>
+#include <sdbusplus/bus.hpp>
+#include <xyz/openbmc_project/ObjectMapper/client.hpp>
+
+using namespace phosphor::software;
+
+PHOSPHOR_LOG2_USING;
+
+BIOSSoftwareManager::BIOSSoftwareManager(sdbusplus::async::context& ctx,
+ bool isDryRun) :
+ SoftwareManager(ctx, configTypeBIOS), dryRun(isDryRun)
+{}
+
+// NOLINTBEGIN(readability-static-accessed-through-instance)
+sdbusplus::async::task<bool> BIOSSoftwareManager::initDevice(
+ const std::string& service, const std::string& path, SoftwareConfig& config)
+// NOLINTEND(readability-static-accessed-through-instance)
+{
+ std::string configIface =
+ "xyz.openbmc_project.Configuration." + config.configType;
+
+ std::optional<int64_t> spiControllerIndex =
+ co_await dbusGetRequiredProperty<uint64_t>(
+ ctx, service, path, configIface, "SPIControllerIndex");
+
+ std::optional<int64_t> spiDeviceIndex =
+ co_await dbusGetRequiredProperty<uint64_t>(
+ ctx, service, path, configIface, "SPIDeviceIndex");
+
+ const std::string configIfaceMux = configIface + ".MuxOutputs";
+
+ std::vector<std::string> names;
+ std::vector<uint64_t> values;
+
+ 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;
+ }
+
+ names.push_back(name.value());
+ values.push_back((polarity == "High") ? 1 : 0);
+ }
+
+ if (!spiControllerIndex.has_value() || !spiDeviceIndex.has_value())
+ {
+ error("Error: Missing property");
+ co_return false;
+ }
+
+ enum FlashLayout layout = flashLayoutFlat;
+ enum FlashTool tool = flashToolNone;
+
+ debug("SPI device: {INDEX1}:{INDEX2}", "INDEX1", spiControllerIndex.value(),
+ "INDEX2", spiDeviceIndex.value());
+
+ std::unique_ptr<SPIDevice> spiDevice;
+ try
+ {
+ spiDevice = std::make_unique<SPIDevice>(
+ ctx, spiControllerIndex.value(), spiDeviceIndex.value(), dryRun,
+ names, values, config, this, layout, tool);
+ }
+ catch (std::exception& e)
+ {
+ co_return false;
+ }
+
+ std::unique_ptr<Software> software =
+ std::make_unique<Software>(ctx, *spiDevice);
+
+ // enable this software to be updated
+ std::set<RequestedApplyTimes> allowedApplyTimes = {
+ RequestedApplyTimes::Immediate, RequestedApplyTimes::OnReset};
+
+ software->enableUpdate(allowedApplyTimes);
+
+ spiDevice->softwareCurrent = std::move(software);
+
+ spiDevice->softwareCurrent->setVersion(SPIDevice::getVersion());
+
+ devices.insert({config.objectPath, std::move(spiDevice)});
+
+ co_return true;
+}