Add new oem command to set accel power mode

Signed-off-by: Gaurav Gandhi <gauravgandhi@google.com>
Change-Id: Ie0b47477b7ae9e8ae206ac0645571a7a79dbda40
diff --git a/README.md b/README.md
index cb531ee..95489f7 100644
--- a/README.md
+++ b/README.md
@@ -456,3 +456,56 @@
 | Byte(s) | Value | Data       |
 | ------- | ----- | ---------- |
 | 0x00    | 0x11  | Subcommand |
+
+### SysGetAccelVrSettings - SubCommand 0x12
+
+Get the accel's VR setting value for the given chip and settings ID
+
+Currently 3 settings are supported. [0] IdleMode [1] PowerBreak [2] Loadline
+
+On success, the response contains 2 bytes containing the setting value.
+
+If not enough data is proveded, `IPMI_CC_REQ_DATA_LEN_INVALID` is returned.
+
+Request
+
+| Byte(s) | Value | Data       |
+| ------- | ----- | ---------- |
+| 0x00    | 0x12  | Subcommand |
+| 0x01    |       | Chip ID    |
+| 0x02    |       | SettingsID |
+
+Response (if applicable)
+
+| Byte(s)    | Value | Data           |
+| ---------- | ----- | -------------- |
+| 0x00       | 0x12  | Subcommand     |
+| 0x01..0x02 | 0x12  | Settings Value |
+
+### SysSetAccelVrSettings - SubCommand 0x13
+
+Update the VR settings of a given accel device for a specific settings id.
+
+Currently 3 settings are supported. [0] IdleMode [1] PowerBreak [2] Loadline
+
+The settings value parameter is a 2 byte value and is expected in little endian
+format
+
+On success, `IPMI_CC_OK` is returned.
+
+If not enough data is proveded, `IPMI_CC_REQ_DATA_LEN_INVALID` is returned.
+
+Request
+
+| Byte(s)    | Value | Data           |
+| ---------- | ----- | -------------- |
+| 0x00       | 0x13  | Subcommand     |
+| 0x01       |       | Chip ID        |
+| 0x02       |       | Settings ID    |
+| 0x03..0x04 | 0x13  | Settings Value |
+
+Response (if applicable)
+
+| Byte(s) | Value | Data       |
+| ------- | ----- | ---------- |
+| 0x00    | 0x13  | Subcommand |
diff --git a/commands.hpp b/commands.hpp
index 7c8858d..41494e3 100644
--- a/commands.hpp
+++ b/commands.hpp
@@ -57,6 +57,10 @@
     SysGetBmcMode = 16,
     // The Sys Linux Boot Done command
     SysLinuxBootDone = 17,
+    // Google CustomAccel Get VR Settings
+    SysGetAccelVrSettings = 18,
+    // Google CustomAccel Set VR Settings
+    SysSetAccelVrSettings = 19,
 };
 
 } // namespace ipmi
diff --git a/google_accel_oob.cpp b/google_accel_oob.cpp
index 7e44864..0c2ffad 100644
--- a/google_accel_oob.cpp
+++ b/google_accel_oob.cpp
@@ -15,6 +15,8 @@
 #include "google_accel_oob.hpp"
 
 #include "commands.hpp"
+#include "errors.hpp"
+#include "handler.hpp"
 
 #include <sdbusplus/bus.hpp>
 #include <stdplus/print.hpp>
@@ -282,5 +284,57 @@
     return ::ipmi::responseSuccess(SysOEMCommands::SysAccelOobWrite, replyBuf);
 }
 
+Resp accelGetVrSettings(::ipmi::Context::ptr ctx, std::span<const uint8_t> data,
+                        HandlerInterface* handler)
+{
+    uint16_t value;
+    if (data.size_bytes() != 2)
+    {
+        stdplus::println(
+            stderr,
+            "accelGetVrSettings command has incorrect size: %zuB != %dB\n",
+            data.size_bytes(), 2);
+        return ::ipmi::responseReqDataLenInvalid();
+    }
+
+    try
+    {
+        value = handler->accelGetVrSettings(ctx, /*chip_id*/ data[0],
+                                            /*settings_id*/ data[1]);
+    }
+    catch (const IpmiException& e)
+    {
+        return ::ipmi::response(e.getIpmiError());
+    }
+    return ::ipmi::responseSuccess(
+        SysOEMCommands::SysGetAccelVrSettings,
+        std::vector<uint8_t>{static_cast<uint8_t>(value),
+                             static_cast<uint8_t>(value >> 8)});
+}
+
+Resp accelSetVrSettings(::ipmi::Context::ptr ctx, std::span<const uint8_t> data,
+                        HandlerInterface* handler)
+{
+    if (data.size_bytes() != 4)
+    {
+        stdplus::println(
+            stderr,
+            "accelSetVrSettings command has incorrect size: %zuB != %dB\n",
+            data.size_bytes(), 4);
+        return ::ipmi::responseReqDataLenInvalid();
+    }
+    uint16_t value = static_cast<uint16_t>(data[2] | data[3] << 8);
+    try
+    {
+        handler->accelSetVrSettings(ctx, /*chip_id*/ data[0],
+                                    /*settings_id*/ data[1], /*value*/ value);
+    }
+    catch (const IpmiException& e)
+    {
+        return ::ipmi::response(e.getIpmiError());
+    }
+    return ::ipmi::responseSuccess(SysOEMCommands::SysSetAccelVrSettings,
+                                   std::vector<uint8_t>{});
+}
 } // namespace ipmi
 } // namespace google
diff --git a/google_accel_oob.hpp b/google_accel_oob.hpp
index 2624f2b..7b2bbf9 100644
--- a/google_accel_oob.hpp
+++ b/google_accel_oob.hpp
@@ -36,5 +36,10 @@
 
 Resp accelOobWrite(std::span<const uint8_t> data, HandlerInterface* handler);
 
+// Handle the accel power setting command
+Resp accelSetVrSettings(::ipmi::Context::ptr ctx, std::span<const uint8_t> data,
+                        HandlerInterface* handler);
+Resp accelGetVrSettings(::ipmi::Context::ptr ctx, std::span<const uint8_t> data,
+                        HandlerInterface* handler);
 } // namespace ipmi
 } // namespace google
diff --git a/handler.cpp b/handler.cpp
index 5e65e07..d4a8897 100644
--- a/handler.cpp
+++ b/handler.cpp
@@ -68,6 +68,7 @@
 using namespace phosphor::logging;
 using InternalFailure =
     sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
+using Value = std::variant<double>;
 
 uint8_t isBmcInBareMetalMode(const std::unique_ptr<FileSystemInterface>& fs)
 {
@@ -699,5 +700,72 @@
     }
 }
 
+static constexpr char ACCEL_POWER_SERVICE[] = "xyz.openbmc_project.AccelPower";
+static constexpr char ACCEL_POWER_PATH_PREFIX[] =
+    "/xyz/openbmc_project/control/accel_power_";
+static constexpr char POWER_MODE_IFC[] =
+    "xyz.openbmc_project.Control.Power.Mode";
+
+void Handler::accelSetVrSettings(::ipmi::Context::ptr ctx, uint8_t chip_id,
+                                 uint8_t settings_id, uint16_t value) const
+{
+    int vrSettingsReq;
+    boost::system::error_code ec;
+    if (_vrSettingsMap.find(settings_id) == _vrSettingsMap.end())
+    {
+        log<level::ERR>("Settings ID is not supported",
+                        entry("settings_id=%d", settings_id));
+        throw IpmiException(::ipmi::ccParmOutOfRange);
+    }
+
+    vrSettingsReq = static_cast<int>(settings_id | value << 8);
+    std::string object_name(
+        std::format("{}{}", ACCEL_POWER_PATH_PREFIX, chip_id));
+
+    std::variant<int> val = vrSettingsReq;
+    ctx->bus->yield_method_call(ctx->yield, ec, ACCEL_POWER_SERVICE,
+                                object_name.c_str(),
+                                "org.freedesktop.DBus.Properties", "Set",
+                                POWER_MODE_IFC, "PowerMode", val);
+    if (ec)
+    {
+        log<level::ERR>("Failed to set PowerMode property");
+        throw IpmiException(::ipmi::ccUnspecifiedError);
+    }
+}
+
+static constexpr char EXTERNAL_SENSOR_SERVICE[] =
+    "xyz.openbmc_project.ExternalSensor";
+static constexpr char EXTERNAL_SENSOR_PATH_PREFIX[] =
+    "/xyz/openbmc_project/sensors/power/";
+static constexpr char SENSOR_VALUE_IFC[] = "xyz.openbmc_project.Sensor.Value";
+
+uint16_t Handler::accelGetVrSettings(::ipmi::Context::ptr ctx, uint8_t chip_id,
+                                     uint8_t settings_id) const
+{
+    Value value;
+    boost::system::error_code ec;
+    std::string object_name(std::format("{}{}{}", EXTERNAL_SENSOR_PATH_PREFIX,
+                                        _vrSettingsMap.at(settings_id),
+                                        chip_id));
+
+    if (_vrSettingsMap.find(settings_id) == _vrSettingsMap.end())
+    {
+        log<level::ERR>("Settings ID is not supported",
+                        entry("settings_id=%d", settings_id));
+        throw IpmiException(::ipmi::ccParmOutOfRange);
+    }
+
+    value = ctx->bus->yield_method_call<std::variant<double>>(
+        ctx->yield, ec, EXTERNAL_SENSOR_SERVICE, object_name.c_str(),
+        "org.freedesktop.DBus.Properties", "Get", SENSOR_VALUE_IFC, "Value");
+    if (ec)
+    {
+        log<level::ERR>("accelGetVrSettings: Failed to call GetObject ");
+        throw IpmiException(::ipmi::ccUnspecifiedError);
+    }
+
+    return static_cast<uint16_t>(std::get<double>(value));
+}
 } // namespace ipmi
 } // namespace google
diff --git a/handler.hpp b/handler.hpp
index 16b8208..b06d986 100644
--- a/handler.hpp
+++ b/handler.hpp
@@ -15,6 +15,7 @@
 #pragma once
 
 #include <ipmid/api-types.hpp>
+#include <ipmid/message.hpp>
 
 #include <cstdint>
 #include <map>
@@ -211,6 +212,27 @@
      * untrusted OS.
      */
     virtual void linuxBootDone() const = 0;
+
+    /**
+     * Update the VR settings for the given settings_id
+     *
+     * @param[in] chip_id    - Accel Device#
+     * @param[in] settings_id  - ID of the setting to update
+     * @param[in] value  - Value of the setting
+     */
+    virtual void accelSetVrSettings(::ipmi::Context::ptr ctx, uint8_t chip_id,
+                                    uint8_t settings_id,
+                                    uint16_t value) const = 0;
+
+    /**
+     * Read current VR settings value for the given settings_id
+     *
+     * @param[in] chip_id    - Accel Device#
+     * @param[in] settings_id  - ID of the setting to read
+     */
+    virtual uint16_t accelGetVrSettings(::ipmi::Context::ptr ctx,
+                                        uint8_t chip_id,
+                                        uint8_t settings_id) const = 0;
 };
 
 } // namespace ipmi
diff --git a/handler_impl.hpp b/handler_impl.hpp
index 7b58523..fe12728 100644
--- a/handler_impl.hpp
+++ b/handler_impl.hpp
@@ -74,6 +74,10 @@
     void accelOobWrite(std::string_view name, uint64_t address,
                        uint8_t num_bytes, uint64_t data) const override;
     void linuxBootDone() const override;
+    void accelSetVrSettings(::ipmi::Context::ptr ctx, uint8_t chip_id,
+                            uint8_t settings_id, uint16_t value) const override;
+    uint16_t accelGetVrSettings(::ipmi::Context::ptr ctx, uint8_t chip_id,
+                                uint8_t settings_id) const override;
 
   protected:
     // Exposed for dependency injection
@@ -102,6 +106,9 @@
         {0x1E, "cooling_unit"},
         {0x20, "memory_device"}};
 
+    const std::unordered_map<uint8_t, std::string> _vrSettingsMap{
+        {0, "idle_mode_"}, {1, "power_break_"}, {2, "loadline_"}};
+
     nlohmann::json _entityConfig{};
 
     std::vector<std::tuple<uint32_t, std::string>> _pcie_i2c_map;
diff --git a/ipmi.cpp b/ipmi.cpp
index 2c765be..d8d94e9 100644
--- a/ipmi.cpp
+++ b/ipmi.cpp
@@ -46,7 +46,7 @@
 namespace ipmi
 {
 
-Resp handleSysCommand(HandlerInterface* handler, ::ipmi::Context::ptr,
+Resp handleSysCommand(HandlerInterface* handler, ::ipmi::Context::ptr ctx,
                       uint8_t cmd, std::span<const uint8_t> data)
 {
     switch (cmd)
@@ -87,6 +87,10 @@
             return pcieBifurcation(data, handler);
         case SysLinuxBootDone:
             return linuxBootDone(data, handler);
+        case SysGetAccelVrSettings:
+            return accelGetVrSettings(ctx, data, handler);
+        case SysSetAccelVrSettings:
+            return accelSetVrSettings(ctx, data, handler);
         default:
             stdplus::print(stderr, "Invalid subcommand: {:#x}\n", cmd);
             return ::ipmi::responseInvalidCommand();
diff --git a/test/google_accel_oob_unittest.cpp b/test/google_accel_oob_unittest.cpp
index 163b602..5797cd3 100644
--- a/test/google_accel_oob_unittest.cpp
+++ b/test/google_accel_oob_unittest.cpp
@@ -26,6 +26,7 @@
 namespace ipmi
 {
 
+using ::testing::_;
 using ::testing::Return;
 
 TEST(GoogleAccelOobTest, DeviceCount_Success)
@@ -235,5 +236,96 @@
     EXPECT_EQ(reply->data, kTestData);
 }
 
+TEST(GoogleAccelOobTest, SetVrSettings_Success)
+{
+    ::testing::StrictMock<HandlerMock> h;
+    constexpr uint8_t kChipId = 2;
+    constexpr uint8_t kSettingsId = 1;
+    constexpr uint16_t kTestValue = 0xAABB;
+
+    std::vector<uint8_t> testData = {kChipId, kSettingsId, 0xBB, 0xAA};
+
+    EXPECT_CALL(h, accelSetVrSettings(_, kChipId, kSettingsId, kTestValue))
+        .WillOnce(Return());
+
+    Resp r = accelSetVrSettings(nullptr, testData, &h);
+
+    const auto response = std::get<0>(r);
+    EXPECT_EQ(response, IPMI_CC_OK);
+
+    const auto payload = std::get<1>(r);
+    ASSERT_EQ(payload.has_value(), true);
+    const auto payload_tuple = payload.value();
+    const auto reply_cmd = std::get<0>(payload_tuple);
+    EXPECT_EQ(reply_cmd, SysSetAccelVrSettings);
+    const auto reply_buff = std::get<1>(payload_tuple);
+    ASSERT_EQ(reply_buff.size(), 0);
+}
+
+TEST(GoogleAccelOobTest, SetVrSettings_HandleIncorrectDataSize)
+{
+    ::testing::StrictMock<HandlerMock> h;
+    constexpr uint8_t kChipId = 2;
+    uint8_t kSettingsId = 1;
+
+    std::vector<uint8_t> testData = {kChipId, kSettingsId};
+
+    EXPECT_CALL(h, accelSetVrSettings(_, _, _, _)).Times(0);
+
+    Resp r = accelSetVrSettings(nullptr, testData, &h);
+
+    const auto response = std::get<0>(r);
+    EXPECT_EQ(response, IPMI_CC_REQ_DATA_LEN_INVALID);
+
+    const auto payload = std::get<1>(r);
+    ASSERT_EQ(payload.has_value(), false);
+}
+
+TEST(GoogleAccelOobTest, GetVrSettings_Success)
+{
+    ::testing::StrictMock<HandlerMock> h;
+    constexpr uint8_t kChipId = 3;
+    constexpr uint8_t kSettingsId = 2;
+
+    std::vector<uint8_t> testData = {kChipId, kSettingsId};
+
+    EXPECT_CALL(h, accelGetVrSettings(_, kChipId, kSettingsId))
+        .WillOnce(Return(0xAABB));
+
+    Resp r = accelGetVrSettings(nullptr, testData, &h);
+
+    const auto response = std::get<0>(r);
+    EXPECT_EQ(response, IPMI_CC_OK);
+
+    const auto payload = std::get<1>(r);
+    ASSERT_EQ(payload.has_value(), true);
+    const auto payload_tuple = payload.value();
+    const auto reply_cmd = std::get<0>(payload_tuple);
+    EXPECT_EQ(reply_cmd, SysGetAccelVrSettings);
+    const auto reply_buff = std::get<1>(payload_tuple);
+    ASSERT_EQ(reply_buff.size(), 2);
+
+    EXPECT_EQ(reply_buff.at(0), 0xBB);
+    EXPECT_EQ(reply_buff.at(1), 0xAA);
+}
+
+TEST(GoogleAccelOobTest, GetVrSettings_HandleIncorrectDataSize)
+{
+    ::testing::StrictMock<HandlerMock> h;
+    constexpr uint8_t kChipId = 2;
+    uint8_t kSettingsId = 1;
+
+    std::vector<uint8_t> testData = {kChipId, kSettingsId, 0xCC};
+
+    EXPECT_CALL(h, accelGetVrSettings(_, _, _)).Times(0);
+
+    Resp r = accelGetVrSettings(nullptr, testData, &h);
+
+    const auto response = std::get<0>(r);
+    EXPECT_EQ(response, IPMI_CC_REQ_DATA_LEN_INVALID);
+
+    const auto payload = std::get<1>(r);
+    ASSERT_EQ(payload.has_value(), false);
+}
 } // namespace ipmi
 } // namespace google
diff --git a/test/handler_mock.hpp b/test/handler_mock.hpp
index f62564e..579dfc6 100644
--- a/test/handler_mock.hpp
+++ b/test/handler_mock.hpp
@@ -16,6 +16,8 @@
 
 #include "handler.hpp"
 
+#include <ipmid/message.hpp>
+
 #include <cstddef>
 #include <cstdint>
 #include <string>
@@ -64,6 +66,11 @@
     MOCK_METHOD(std::vector<uint8_t>, pcieBifurcation, (uint8_t), (override));
     MOCK_METHOD(uint8_t, getBmcMode, (), (override));
     MOCK_METHOD(void, linuxBootDone, (), (const, override));
+    MOCK_METHOD(void, accelSetVrSettings,
+                (::ipmi::Context::ptr, uint8_t, uint8_t, uint16_t),
+                (const, override));
+    MOCK_METHOD(uint16_t, accelGetVrSettings,
+                (::ipmi::Context::ptr, uint8_t, uint8_t), (const, override));
 };
 
 } // namespace ipmi