dbus-sdr: sensorcommands: Add a basic handler to set sensor readings

FEATURE_DYNAMIC_SENSORS_WRITE is used to enable the set sensor handler.
To enable the sensor writes add `--enable-dynamic_sensors_write` to the
compile flags.

Convert sensor writes to double from raw IPMI value.
The 8-bit value in the IPMI Set Sensor command is not a
literal value. It is a floating point value encoded using
the m, b, rExp, and bExp that are reported in the SDR.

Convert the raw 8-bit value to a floating point value according to the
IPMI spec. This implementation only supports linear sensors.
sdr_convert_sensor_reading() from ipmitool is a good reference should
you need a more feature complete implementation in the future [1].

  [1]: https://github.com/ipmitool/ipmitool/blob/42a023ff0726c80e8cc7d30315b987fe568a981d/lib/ipmi_sdr.c#L360

Interpreting bSigned correctly if IPMI indicates the byte is signed
Breaking up the complicated math expression into multiple lines
Making sure std::pow uses the correct type override of double
Adding the input byte to the logging

Tested:

// Before
$ ipmitool sensor list | grep fan
fan0_pwm         | 69.776     | unspecified | ok    | na        | na        | na        | na        | na        | na
fan1_pwm         | 69.776     | unspecified | ok    | na        | na        | na        | na        | na        | na
fan2_pwm         | 69.776     | unspecified | ok    | na        | na        | na        | na        | na        | na
fan3_pwm         | 68.600     | unspecified | ok    | na        | na        | na        | na        | na        | na
fan4_pwm         | 89.768     | unspecified | ok    | na        | na        | na        | na        | na        | na
fan0_tach        | 8428.000   | RPM        | ok    | na        | na        | na        | na        | na        | na
fan1_tach        | 8330.000   | RPM        | ok    | na        | na        | na        | na        | na        | na
fan2_tach        | 8330.000   | RPM        | ok    | na        | na        | na        | na        | na        | na
fan3_tach        | 8918.000   | RPM        | ok    | na        | na        | na        | na        | na        | na
fan4_tach        | 8134.000   | RPM        | ok    | na        | na        | na        | na        | na        | na

// After setting fan3 to 0 pwm.
$ ipmitool sensor list | grep fan
fan0_pwm         | 69.776     | unspecified | ok    | na        | na        | na        | na        | na        | na
fan1_pwm         | 69.776     | unspecified | ok    | na        | na        | na        | na        | na        | na
fan2_pwm         | 69.776     | unspecified | ok    | na        | na        | na        | na        | na        | na
fan3_pwm         | 3.920      | unspecified | ok    | na        | na        | na        | na        | na        | na
fan4_pwm         | 89.768     | unspecified | ok    | na        | na        | na        | na        | na        | na
fan0_tach        | 8428.000   | RPM        | ok    | na        | na        | na        | na        | na        | na
fan1_tach        | 8330.000   | RPM        | ok    | na        | na        | na        | na        | na        | na
fan2_tach        | 8330.000   | RPM        | ok    | na        | na        | na        | na        | na        | na
fan3_tach        | 0.000      | RPM        | ok    | na        | na        | na        | na        | na        | na
fan4_tach        | 8134.000   | RPM        | ok    | na        | na        | na        | na        | na        | na       | na        | na        | na        | na

Signed-off-by: Peter Lundgren <peterlundgren@google.com>
Signed-off-by: Josh Lehan <krellan@google.com>
Change-Id: Ib26c443480382224092a83662e060df3b759da5c
Signed-off-by: Willy Tu <wltu@google.com>
diff --git a/dbus-sdr/sensorcommands.cpp b/dbus-sdr/sensorcommands.cpp
index 42fb9de..830e8a5 100644
--- a/dbus-sdr/sensorcommands.cpp
+++ b/dbus-sdr/sensorcommands.cpp
@@ -267,6 +267,86 @@
     return ipmi::responseSuccess();
 }
 
+ipmi::RspType<> ipmiSetSensorReading(ipmi::Context::ptr ctx,
+                                     uint8_t sensorNumber, uint8_t operation,
+                                     uint8_t reading, uint15_t assertOffset,
+                                     bool resvd1, uint15_t deassertOffset,
+                                     bool resvd2, uint8_t eventData1,
+                                     uint8_t eventData2, uint8_t eventData3)
+{
+    std::string connection;
+    std::string path;
+    ipmi::Cc status = getSensorConnection(ctx, sensorNumber, connection, path);
+    if (status)
+    {
+        return ipmi::response(status);
+    }
+
+    DbusInterfaceMap sensorMap;
+    if (!getSensorMap(ctx, connection, path, sensorMap))
+    {
+        return ipmi::responseResponseError();
+    }
+    auto sensorObject = sensorMap.find("xyz.openbmc_project.Sensor.Value");
+
+    if (sensorObject == sensorMap.end() ||
+        sensorObject->second.find("Value") == sensorObject->second.end())
+    {
+        return ipmi::responseResponseError();
+    }
+
+    double max = 0;
+    double min = 0;
+    getSensorMaxMin(sensorMap, max, min);
+
+    int16_t mValue = 0;
+    int16_t bValue = 0;
+    int8_t rExp = 0;
+    int8_t bExp = 0;
+    bool bSigned = false;
+
+    if (!getSensorAttributes(max, min, mValue, rExp, bValue, bExp, bSigned))
+    {
+        return ipmi::responseResponseError();
+    }
+
+    double value = bSigned ? ((int8_t)reading) : reading;
+
+    value *= ((double)mValue);
+    value += ((double)bValue) * std::pow(10.0, bExp);
+    value *= std::pow(10.0, rExp);
+
+    if constexpr (debug)
+    {
+        phosphor::logging::log<phosphor::logging::level::INFO>(
+            "IPMI SET_SENSOR",
+            phosphor::logging::entry("SENSOR_NUM=%d", sensorNumber),
+            phosphor::logging::entry("BYTE=%u", (unsigned int)reading),
+            phosphor::logging::entry("VALUE=%f", value));
+    }
+
+    try
+    {
+        setDbusProperty(ctx, connection, path,
+                        "xyz.openbmc_project.Sensor.Value", "Value",
+                        ipmi::Value(value));
+    }
+    // setDbusProperty intended to resolve dbus exception/rc within the
+    // function but failed to achieve that. Catch SdBusError in the ipmi
+    // callback functions for now (e.g. ipmiSetSensorReading).
+    catch (const sdbusplus::exception::SdBusError& e)
+    {
+        using namespace phosphor::logging;
+        log<level::ERR>(
+            "Failed to set property", entry("PROPERTY=%s", "Value"),
+            entry("PATH=%s", path.c_str()),
+            entry("INTERFACE=%s", "xyz.openbmc_project.Sensor.Value"),
+            entry("WHAT=%s", e.what()));
+        return ipmi::responseResponseError();
+    }
+    return ipmi::responseSuccess();
+}
+
 ipmi::RspType<uint8_t, uint8_t, uint8_t, std::optional<uint8_t>>
     ipmiSenGetSensorReading(ipmi::Context::ptr ctx, uint8_t sensnum)
 {
@@ -1558,6 +1638,13 @@
                           ipmi::sensor_event::cmdPlatformEvent,
                           ipmi::Privilege::Operator, ipmiSenPlatformEvent);
 
+#ifdef FEATURE_DYNAMIC_SENSORS_WRITE
+    // <Set Sensor Reading and Event Status>
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnSensor,
+                          ipmi::sensor_event::cmdSetSensorReadingAndEvtSts,
+                          ipmi::Privilege::Operator, ipmiSetSensorReading);
+#endif
+
     // <Get Sensor Reading>
     ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnSensor,
                           ipmi::sensor_event::cmdGetSensorReading,