Add i2c voltage regulator updater
- /i2c-vr: Classes following the design in [1]
- /i2c-vr/vr.cpp, vr.hpp: General representation of a voltage regulators
communication interface
- /vr-i2c/xdpe1x2xx: Support for Infineon XDPE1x2xx class of voltage
regulators
- /common/i2c/: Basic I2C communication interface
The configuration of a voltage regulator relies on the EM-Schema in [2]
1: https://github.com/openbmc/docs/blob/master/designs/code-update.md
2: https://gerrit.openbmc.org/c/openbmc/entity-manager/+/77463
Tested on QEMU/Yosemite4:
1. Display the firmware inventory
```
curl --insecure --user root:0penBmc \
https://127.0.0.1:2443/redfish/v1/UpdateService/FirmwareInventory
```
Output:
```
{
"@odata.id": "/redfish/v1/UpdateService/FirmwareInventory",
"@odata.type": "#SoftwareInventoryCollection.SoftwareInventoryCollection",
"Members": [
{
"@odata.id": "/redfish/v1/UpdateService/FirmwareInventory/16bae6fd"
},
{
"@odata.id": "/redfish/v1/UpdateService/FirmwareInventory/DummyDeviceFirmwareName_6637"
},
{
"@odata.id": "/redfish/v1/UpdateService/FirmwareInventory/Management_Board_cpld"
},
{...}
```
2. Query voltage regulator version
The dummy device sets the default version to 0xBEEF (Decimal: 48879)
```
curl --insecure --user root:0penBmc \
https://127.0.0.1:2443/redfish/v1/UpdateService/FirmwareInventory/
DummyDeviceFirmwareName_4390
```
Output:
```
{
"@odata.id": "/redfish/v1/UpdateService/FirmwareInventory/DummyDeviceFirmwareName_4390",
"@odata.type": "#SoftwareInventory.v1_1_0.SoftwareInventory",
"Description": "Other image",
"Id": "DummyDeviceFirmwareName_4390",
"Name": "Software Inventory",
"Status": {
"Health": "Warning",
"HealthRollup": "OK",
"State": "Disabled"
},
"Updateable": false,
"Version": "48879"
}
```
3. Trigger the update
```
curl -k --insecure --user root:0penBmc \
-H "Content-Type:multipart/form-data" \
-X POST \
-F UpdateParameters="{\"Targets\":[\"/redfish/v1/UpdateService/FirmwareInventory/DummyDeviceFirmwareName_4390\"],\"@Redfish.OperationApplyTime\":\"Immediate\"};type=application/json" \
-F "UpdateFile=@fw_vr_update.bin;type=application/octet-stream" \
https://127.0.0.1:2443/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 the task
```
curl --insecure --user root:0penBmc \
https://127.0.0.1:2443/redfish/v1/TaskService/Tasks/0
```
Output:
```
{
"@odata.id": "/redfish/v1/TaskService/Tasks/0",
"@odata.type": "#Task.v1_4_3.Task",
"EndTime": "2025-03-10T13:47:34+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 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": 0,
"StartTime": "2025-03-10T13:47:34+00:00",
"TaskMonitor": "/redfish/v1/TaskService/TaskMonitors/0",
"TaskState": "Completed",
"TaskStatus": "OK"
}
```
6. Display fw inventory after update
```
curl --insecure --user root:0penBmc \
https://127.0.0.1:2443/redfish/v1/UpdateService/FirmwareInventory
```
Output:
```
{
"@odata.id": "/redfish/v1/UpdateService/FirmwareInventory",
"@odata.type": "#SoftwareInventoryCollection.SoftwareInventoryCollection",
"Members": [
{
"@odata.id": "/redfish/v1/UpdateService/FirmwareInventory/16bae6fd"
},
{
"@odata.id": "/redfish/v1/UpdateService/FirmwareInventory/DummyDeviceFirmwareName_282"
},
{...}
}
```
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 --insecure --user root:0penBmc \
https://127.0.0.1:2443/redfish/v1/UpdateService/FirmwareInventory/
DummyDeviceFirmwareName_282
```
Output:
```
{
"@odata.id": "/redfish/v1/UpdateService/FirmwareInventory/DummyDeviceFirmwareName_282",
"@odata.type": "#SoftwareInventory.v1_1_0.SoftwareInventory",
"Description": "Other image",
"Id": "DummyDeviceFirmwareName_282",
"Name": "Software Inventory",
"Status": {
"Health": "OK",
"HealthRollup": "OK",
"State": "Enabled"
},
"Updateable": false,
"Version": "mycompversion"
}
```
8. Update in progress error
```
curl -k --insecure --user root:0penBmc \
-H "Content-Type:multipart/form-data" \
-X POST \
-F UpdateParameters="{\"Targets\":[\"/redfish/v1/UpdateService/FirmwareInventory/${TARGET}\"],\"@Redfish.OperationApplyTime\":\"Immediate\"};type=application/json" \
-F "UpdateFile=@${FWPATH};type=application/octet-stream" \
https://127.0.0.1:2443/redfish/v1/UpdateService/update
```
Output:
```
{
"error": {
"@Message.ExtendedInfo": [
{
"@odata.type": "#Message.v1_1_1.Message",
"Message": "The request failed due to an internal service error. The service is still operational.",
"MessageArgs": [],
"MessageId": "Base.1.19.InternalError",
"MessageSeverity": "Critical",
"Resolution": "Resubmit the request. If the problem persists, consider resetting the service."
}
],
"code": "Base.1.19.InternalError",
"message": "The request failed due to an internal service error. The service is still operational."
}
```
Change-Id: I2e11a6c10ae40ed7719ceb86dfd6a38dd5b27017
Signed-off-by: Christopher Meis <christopher.meis@9elements.com>
diff --git a/common/i2c/i2c.cpp b/common/i2c/i2c.cpp
new file mode 100644
index 0000000..99de04b
--- /dev/null
+++ b/common/i2c/i2c.cpp
@@ -0,0 +1,82 @@
+#include "i2c.hpp"
+
+#include <unistd.h>
+
+extern "C"
+{
+#include <i2c/smbus.h>
+#include <linux/i2c-dev.h>
+#include <linux/i2c.h>
+}
+
+namespace phosphor::i2c
+{
+
+int I2C::open()
+{
+ int ret = 0;
+ fd = ::open(busStr.c_str(), O_RDWR);
+ if (fd < 0)
+ {
+ return fd;
+ }
+
+ ret = ioctl(fd, I2C_SLAVE_FORCE, deviceNode);
+ if (ret < 0)
+ {
+ close();
+ return ret;
+ }
+
+ return 0;
+}
+
+// NOLINTBEGIN(readability-static-accessed-through-instance)
+sdbusplus::async::task<bool> I2C::sendReceive(
+ uint8_t* writeData, uint8_t writeSize, uint8_t* readData,
+ uint8_t readSize) const
+// NOLINTEND(readability-static-accessed-through-instance)
+{
+ if (fd <= 0)
+ {
+ co_return false;
+ }
+
+ struct i2c_msg msg[2];
+ struct i2c_rdwr_ioctl_data readWriteData;
+ int msgIndex = 0;
+
+ if (writeSize)
+ {
+ msg[msgIndex].addr = deviceNode;
+ msg[msgIndex].flags = 0;
+ msg[msgIndex].len = writeSize;
+ msg[msgIndex].buf = writeData;
+ msgIndex++;
+ }
+
+ if (readSize)
+ {
+ msg[msgIndex].addr = deviceNode;
+ msg[msgIndex].flags = I2C_M_RD;
+ msg[msgIndex].len = readSize;
+ msg[msgIndex].buf = readData;
+ msgIndex++;
+ }
+
+ readWriteData.msgs = msg;
+ readWriteData.nmsgs = msgIndex;
+
+ if (ioctl(fd, I2C_RDWR, &readWriteData) < 0)
+ {
+ co_return false;
+ }
+ co_return true;
+}
+
+int I2C::close() const
+{
+ return ::close(fd);
+}
+
+} // namespace phosphor::i2c
diff --git a/common/i2c/meson.build b/common/i2c/meson.build
new file mode 100644
index 0000000..fd8a702
--- /dev/null
+++ b/common/i2c/meson.build
@@ -0,0 +1,8 @@
+libi2c_dev = static_library('i2c_dev', 'i2c.cpp', link_args: '-li2c')
+libi2c_inc = include_directories('.')
+libi2c_dep = declare_dependency(
+ common_include,
+ link_with: libi2c_dev,
+ include_directories: libi2c_inc,
+ link_args: '-li2c',
+)
diff --git a/common/include/i2c/i2c.hpp b/common/include/i2c/i2c.hpp
new file mode 100644
index 0000000..e86d574
--- /dev/null
+++ b/common/include/i2c/i2c.hpp
@@ -0,0 +1,61 @@
+#pragma once
+
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+
+#include <sdbusplus/async.hpp>
+
+#include <cstdint>
+#include <cstring>
+#include <string>
+
+extern "C"
+{
+#include <i2c/smbus.h>
+#include <linux/i2c-dev.h>
+#include <linux/i2c.h>
+}
+
+namespace phosphor::i2c
+{
+
+class I2C
+{
+ public:
+ explicit I2C(uint16_t bus, uint16_t node) :
+ busStr("/dev/i2c-" + std::to_string(bus)), deviceNode(node)
+ {
+ open();
+ }
+
+ I2C(I2C& i2c) = delete;
+ I2C& operator=(I2C other) = delete;
+ I2C(I2C&& other) = delete;
+ I2C& operator=(I2C&& other) = delete;
+
+ ~I2C()
+ {
+ this->close();
+ }
+
+ sdbusplus::async::task<bool> sendReceive(
+ uint8_t* writeData, uint8_t writeSize, uint8_t* readData,
+ uint8_t readSize) const;
+
+ bool isOpen() const
+ {
+ return (fd != invalidFd);
+ }
+
+ int close() const;
+
+ private:
+ static constexpr int invalidFd = -1;
+ int fd = invalidFd;
+ std::string busStr;
+ uint16_t deviceNode;
+ int open();
+}; // end class I2C
+
+} // namespace phosphor::i2c
diff --git a/common/meson.build b/common/meson.build
index 1e29ae8..6887c38 100644
--- a/common/meson.build
+++ b/common/meson.build
@@ -9,6 +9,7 @@
'src/software.cpp',
'src/software_update.cpp',
'src/host_power.cpp',
- include_directories: ['.', 'include/', common_include],
+ 'i2c/i2c.cpp',
+ include_directories: ['.', 'include/', 'include/i2c/', common_include],
dependencies: [pdi_dep, phosphor_logging_dep, sdbusplus_dep, libpldm_dep],
)