Fix Get and Set SM Signal Fan PWM Command

The Get SM Signal and Set SM Signal IPMI commands function under the
assumption that there is a 1:1 relationship between the fan Pulse Width
Modulator (PWM), and the fan tachometer. Not all hardware follows this
model. The code changes in this commit updates these IPMI commands to handle
the introduction of HW that uses one PWM output to control two fans.

For systems that have a 1:1 PWM/Tachometer relationship, this code improves
upon the original "signal instance" lookup. The "signal instance" now
specifies a "fan number". Systems may not have a linear relationship between
"fan header numbers" and the PWM that controls the fan on that header. Prior
to this commit the "signal instance" had to be the PWM number. Now the Get/Set
commands associate a linear fan number to the respective PWM.

Systems that use one PWM to control multiple fans, and by extension multiple
tachometers use a secondary lookup to determine the fan to modify/read. The
JSON file describing the fan PWM/tachometer is updated to add a "PWMName"
record. The instance number is combined with the PWMName entry to identify
the correct HW to read/modify. The PWMName entry in the JSON file nomenclature
defines two fans (i.e. "PWMName:" "Pwm_3_4"), which are associated to a PWM,
(i.e. "Pwm": 1).

Tested:
On system with default PWM name:
    IPMI command:
    ipmitool raw 0x30 0x14 0x0d 6 0x00     # read fan 7 pwm
    32
    ipmitool raw 0x30 0x15 0x05 6 0x1 0x48 # set fan 7 pwm (72%)
    ipmitool raw 0x30 0x14 0x0d 6 0x00     # read fan 7 pwm
    48
    root@intel-obmc:~# busctl get-property
    xyz.openbmc_project.EntityManager
    /xyz/openbmc_project/inventory/system/chassis/Fan_7
    xyz.openbmc_project.Configuration.AspeedFan.Connector Pwm
        t 12
    (In this system, Fan 7 is connected to PWM 12)

    Dbus tree showing default name:
    busctl tree xyz.openbmc_project.FanSensor
    `-/xyz/openbmc_project/sensors
          |-/xyz/openbmc_project/sensors/fan_pwm
          | |-/xyz/openbmc_project/sensors/fan_pwm/Pwm_1
          | |-/xyz/openbmc_project/sensors/fan_pwm/Pwm_13
          | |-/xyz/openbmc_project/sensors/fan_pwm/Pwm_14
          | |-/xyz/openbmc_project/sensors/fan_pwm/Pwm_15
          | |-/xyz/openbmc_project/sensors/fan_pwm/Pwm_16
          | |-/xyz/openbmc_project/sensors/fan_pwm/Pwm_2
          | |-/xyz/openbmc_project/sensors/fan_pwm/Pwm_3
          | |-/xyz/openbmc_project/sensors/fan_pwm/Pwm_4
          | |-/xyz/openbmc_project/sensors/fan_pwm/Pwm_5
          | `-/xyz/openbmc_project/sensors/fan_pwm/Pwm_6

On system with non-default PWM name:
    IPMI command:
    ipmitool raw 0x30 0x14 0x0d 1 0x00     # read fan 2 pwm
    64
    ipmitool raw 0x30 0x15 0x05 0 0x1 0x48 # set fan 2 pwm
    ipmitool raw 0x30 0x14 0x0d 0 0x00     # read fan 1 pwm
    48                                     # shared with fan 2

    Dbus tree showing non-default PWM name:
    busctl tree xyz.openbmc_project.FanSensor
    `-/xyz/openbmc_project/sensors
          |-/xyz/openbmc_project/sensors/fan_pwm
          | |-/xyz/openbmc_project/sensors/fan_pwm/Pwm_11_12
          | |-/xyz/openbmc_project/sensors/fan_pwm/Pwm_1_2
          | |-/xyz/openbmc_project/sensors/fan_pwm/Pwm_3_4
          | |-/xyz/openbmc_project/sensors/fan_pwm/Pwm_5_6
          | |-/xyz/openbmc_project/sensors/fan_pwm/Pwm_7_8
          | `-/xyz/openbmc_project/sensors/fan_pwm/Pwm_9_10

Change-Id: I9a40256ed892170f8ba88a3a617b5a2e11693c9f
Signed-off-by: Alex Schendel <alex.schendel@intel.com>
diff --git a/include/manufacturingcommands.hpp b/include/manufacturingcommands.hpp
index e0ff303..001928a 100644
--- a/include/manufacturingcommands.hpp
+++ b/include/manufacturingcommands.hpp
@@ -43,7 +43,7 @@
 static constexpr const char* ledPathPrefix =
     "/xyz/openbmc_project/led/physical/";
 static constexpr const char* fanPwmPath =
-    "/xyz/openbmc_project/sensors/fan_pwm/Pwm_";
+    "/xyz/openbmc_project/sensors/fan_pwm/";
 static constexpr const char* fanTachBasePath =
     "/xyz/openbmc_project/sensors/fan_tach/";
 
diff --git a/src/manufacturingcommands.cpp b/src/manufacturingcommands.cpp
index 2de20d6..7928943 100644
--- a/src/manufacturingcommands.cpp
+++ b/src/manufacturingcommands.cpp
@@ -279,6 +279,78 @@
     return 0;
 }
 
+static bool findPwmName(ipmi::Context::ptr& ctx, uint8_t instance,
+                        std::string& pwmName)
+{
+    boost::system::error_code ec{};
+    ObjectValueTree obj;
+
+    // GetAll the objects under service FruDevice
+    ec = getManagedObjects(ctx, "xyz.openbmc_project.EntityManager",
+                           "/xyz/openbmc_project/inventory", obj);
+    if (ec)
+    {
+        phosphor::logging::log<phosphor::logging::level::ERR>(
+            "GetMangagedObjects failed",
+            phosphor::logging::entry("ERROR=%s", ec.message().c_str()));
+        return false;
+    }
+    for (const auto& [path, objData] : obj)
+    {
+        for (const auto& [intf, propMap] : objData)
+        {
+            // Currently, these are the three different fan types supported.
+            if (intf == "xyz.openbmc_project.Configuration.AspeedFan" ||
+                intf == "xyz.openbmc_project.Configuration.I2CFan" ||
+                intf == "xyz.openbmc_project.Configuration.NuvotonFan")
+            {
+                auto findIndex = propMap.find("Index");
+                if (findIndex == propMap.end())
+                {
+                    continue;
+                }
+
+                auto fanIndex = std::get_if<uint64_t>(&findIndex->second);
+                if (!fanIndex || *fanIndex != instance)
+                {
+                    continue;
+                }
+                auto connector = objData.find(intf + std::string(".Connector"));
+                if (connector == objData.end())
+                {
+                    return false;
+                }
+                auto findPwmName = connector->second.find("PwmName");
+                if (findPwmName != connector->second.end())
+                {
+                    auto fanPwmName =
+                        std::get_if<std::string>(&findPwmName->second);
+                    if (!fanPwmName)
+                    {
+                        phosphor::logging::log<phosphor::logging::level::INFO>(
+                            "PwmName parse ERROR.");
+                        return false;
+                    }
+                    pwmName = *fanPwmName;
+                    return true;
+                }
+                auto findPwm = connector->second.find("Pwm");
+                if (findPwm == connector->second.end())
+                {
+                    return false;
+                }
+                auto fanPwm = std::get_if<uint64_t>(&findPwm->second);
+                if (!fanPwm)
+                {
+                    return false;
+                }
+                pwmName = "Pwm_" + std::to_string(*fanPwm + 1);
+                return true;
+            }
+        }
+    }
+    return false;
+}
 ipmi::RspType<uint8_t,                // Signal value
               std::optional<uint16_t> // Fan tach value
               >
@@ -331,7 +403,13 @@
         case SmSignalGet::smFanPwmGet:
         {
             ipmi::Value reply;
-            std::string fullPath = fanPwmPath + std::to_string(instance + 1);
+            std::string pwmName, fullPath;
+            if (!findPwmName(ctx, instance, pwmName))
+            {
+                // The default PWM name is Pwm_#
+                pwmName = "Pwm_" + std::to_string(instance + 1);
+            }
+            fullPath = fanPwmPath + pwmName;
             if (mtm.getProperty(fanService, fullPath, fanIntf, "Value",
                                 &reply) < 0)
             {
@@ -597,9 +675,12 @@
                         mtm.revertFanPWM = true;
                     }
                     mtm.revertTimer.start(revertTimeOut);
-                    std::string fanPwmInstancePath =
-                        fanPwmPath + std::to_string(instance + 1);
-
+                    std::string pwmName, fanPwmInstancePath;
+                    if (!findPwmName(ctx, instance, pwmName))
+                    {
+                        pwmName = "Pwm_" + std::to_string(instance + 1);
+                    }
+                    fanPwmInstancePath = fanPwmPath + pwmName;
                     ret =
                         mtm.setProperty(fanService, fanPwmInstancePath, fanIntf,
                                         "Value", static_cast<double>(pwmValue));