dcmi: implement get sensor info

This commit implements the plumbing around the 'Get Sensor
Info' command. It doesn't read the sensor information yet.
That code is coming up in a subsequent commit.

Refactor some of the get temperature readings code, in order to reuse
certain common aspects for this commit.

Change-Id: I0cb531b5b75e98cc9e76509f84f832be9997fee0
Signed-off-by: Deepak Kodihalli <dkodihal@in.ibm.com>
diff --git a/dcmihandler.cpp b/dcmihandler.cpp
index bfc8466..54c8799 100644
--- a/dcmihandler.cpp
+++ b/dcmihandler.cpp
@@ -40,6 +40,18 @@
 namespace dcmi
 {
 
+// Refer Table 6-14, DCMI Entity ID Extension, DCMI v1.5 spec
+static const std::map<uint8_t, std::string> entityIdToName
+{
+    {0x40, "inlet"},
+    {0x37, "inlet"},
+    {0x41, "cpu"},
+    {0x03, "cpu"},
+    {0x42, "baseboard"},
+    {0x07, "baseboard"}
+};
+
+
 uint32_t getPcap(sdbusplus::bus::bus& bus)
 {
     auto settingService = ipmi::getService(bus,
@@ -232,6 +244,25 @@
     return value.get<std::string>();
 }
 
+Json parseSensorConfig()
+{
+    std::ifstream jsonFile(configFile);
+    if (!jsonFile.is_open())
+    {
+        log<level::ERR>("Temperature readings JSON file not found");
+        elog<InternalFailure>();
+    }
+
+    auto data = Json::parse(jsonFile, nullptr, false);
+    if (data.is_discarded())
+    {
+        log<level::ERR>("Temperature readings JSON parser failure");
+        elog<InternalFailure>();
+    }
+
+    return data;
+}
+
 } // namespace dcmi
 
 ipmi_ret_t getPowerLimit(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
@@ -742,25 +773,6 @@
 namespace temp_readings
 {
 
-Json parseConfig()
-{
-    std::ifstream jsonFile(configFile);
-    if (!jsonFile.is_open())
-    {
-        log<level::ERR>("Temperature readings JSON file not found");
-        elog<InternalFailure>();
-    }
-
-    auto data = Json::parse(jsonFile, nullptr, false);
-    if (data.is_discarded())
-    {
-        log<level::ERR>("Temperature readings JSON parser failure");
-        elog<InternalFailure>();
-    }
-
-    return data;
-}
-
 Temperature readTemp(const std::string& dbusService,
                      const std::string& dbusPath)
 {
@@ -815,7 +827,7 @@
         elog<InternalFailure>();
     }
 
-    auto data = parseConfig();
+    auto data = parseSensorConfig();
     static const std::vector<Json> empty{};
     std::vector<Json> readings = data.value(type, empty);
     size_t numInstances = readings.size();
@@ -868,7 +880,7 @@
     sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()};
 
     size_t numInstances = 0;
-    auto data = parseConfig();
+    auto data = parseSensorConfig();
     static const std::vector<Json> empty{};
     std::vector<Json> readings = data.value(type, empty);
     numInstances = readings.size();
@@ -925,17 +937,6 @@
                        ipmi_request_t request, ipmi_response_t response,
                        ipmi_data_len_t data_len, ipmi_context_t context)
 {
-    // Refer Table 6-14, DCMI Entity ID Extension, DCMI v1.5 spec
-    static const std::map<uint8_t, std::string> entityIdToName
-    {
-        {0x40, "inlet"},
-        {0x37, "inlet"},
-        {0x41, "cpu"},
-        {0x03, "cpu"},
-        {0x42, "baseboard"},
-        {0x07, "baseboard"}
-    };
-
     auto requestData =
         reinterpret_cast<const dcmi::GetTempReadingsRequest*>(request);
     auto responseData =
@@ -949,8 +950,8 @@
     }
     *data_len = 0;
 
-    auto it = entityIdToName.find(requestData->entityId);
-    if (it == entityIdToName.end())
+    auto it = dcmi::entityIdToName.find(requestData->entityId);
+    if (it == dcmi::entityIdToName.end())
     {
         log<level::ERR>("Unknown Entity ID",
                         entry("ENTITY_ID=%d", requestData->entityId));
@@ -1107,6 +1108,118 @@
     return rc;
 }
 
+namespace dcmi
+{
+namespace sensor_info
+{
+
+std::tuple<Response, NumInstances> read(const std::string& type,
+                                        uint8_t instance,
+                                        const Json& config)
+{
+    Response empty{};
+    return std::make_tuple(empty, 0);
+}
+
+std::tuple<ResponseList, NumInstances> readAll(const std::string& type,
+                                               uint8_t instanceStart,
+                                               const Json& config)
+{
+    ResponseList empty{};
+    return std::make_tuple(empty, 0);
+}
+
+} // namespace sensor_info
+} // namespace dcmi
+
+ipmi_ret_t getSensorInfo(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
+                         ipmi_request_t request, ipmi_response_t response,
+                         ipmi_data_len_t data_len, ipmi_context_t context)
+{
+    auto requestData =
+        reinterpret_cast<const dcmi::GetSensorInfoRequest*>(request);
+    auto responseData =
+        reinterpret_cast<dcmi::GetSensorInfoResponseHdr*>(response);
+
+    if (*data_len != sizeof(dcmi::GetSensorInfoRequest))
+    {
+        log<level::ERR>("Malformed request data",
+                        entry("DATA_SIZE=%d", *data_len));
+        return IPMI_CC_REQ_DATA_LEN_INVALID;
+    }
+    *data_len = 0;
+
+    auto it = dcmi::entityIdToName.find(requestData->entityId);
+    if (it == dcmi::entityIdToName.end())
+    {
+        log<level::ERR>("Unknown Entity ID",
+                        entry("ENTITY_ID=%d", requestData->entityId));
+        return IPMI_CC_INVALID_FIELD_REQUEST;
+    }
+
+    if (requestData->groupID != dcmi::groupExtId)
+    {
+        log<level::ERR>("Invalid Group ID",
+                        entry("GROUP_ID=%d", requestData->groupID));
+        return IPMI_CC_INVALID_FIELD_REQUEST;
+    }
+
+    if (requestData->sensorType != dcmi::temperatureSensorType)
+    {
+        log<level::ERR>("Invalid sensor type",
+                        entry("SENSOR_TYPE=%d", requestData->sensorType));
+        return IPMI_CC_INVALID_FIELD_REQUEST;
+    }
+
+    dcmi::sensor_info::ResponseList sensors{};
+    static dcmi::Json config{};
+    static bool parsed = false;
+
+    try
+    {
+        if (!parsed)
+        {
+            config = dcmi::parseSensorConfig();
+            parsed = true;
+        }
+
+        if (!requestData->entityInstance)
+        {
+            // Read all instances
+            std::tie(sensors, responseData->numInstances) =
+                dcmi::sensor_info::readAll(it->second,
+                                           requestData->instanceStart,
+                                           config);
+        }
+        else
+        {
+            // Read one instance
+            sensors.resize(1);
+            std::tie(sensors[0], responseData->numInstances) =
+                dcmi::sensor_info::read(it->second,
+                                        requestData->entityInstance,
+                                        config);
+        }
+        responseData->numRecords = sensors.size();
+    }
+    catch (InternalFailure& e)
+    {
+        return IPMI_CC_UNSPECIFIED_ERROR;
+    }
+
+    responseData->groupID = dcmi::groupExtId;
+    size_t payloadSize = sensors.size() * sizeof(dcmi::sensor_info::Response);
+    if (!sensors.empty())
+    {
+        memcpy(responseData + 1, // copy payload right after the response header
+               sensors.data(),
+               payloadSize);
+    }
+    *data_len = sizeof(dcmi::GetSensorInfoResponseHdr) + payloadSize;
+
+    return IPMI_CC_OK;
+}
+
 void register_netfn_dcmi_functions()
 {
     // <Get Power Limit>
@@ -1168,6 +1281,11 @@
     // <Get Power Reading>
     ipmi_register_callback(NETFUN_GRPEXT, dcmi::Commands::GET_POWER_READING,
                            NULL, getPowerReading, PRIVILEGE_USER);
+
+    // <Get Sensor Info>
+    ipmi_register_callback(NETFUN_GRPEXT, dcmi::Commands::GET_SENSOR_INFO,
+                           NULL, getSensorInfo, PRIVILEGE_USER);
+
     return;
 }
 // 956379
diff --git a/dcmihandler.hpp b/dcmihandler.hpp
index 4e95eba..09921a0 100644
--- a/dcmihandler.hpp
+++ b/dcmihandler.hpp
@@ -10,6 +10,9 @@
 namespace dcmi
 {
 
+using NumInstances = size_t;
+using Json = nlohmann::json;
+
 enum Commands
 {
     // Get capability bits
@@ -19,6 +22,7 @@
     SET_POWER_LIMIT = 0x04,
     APPLY_POWER_LIMIT = 0x05,
     GET_ASSET_TAG = 0x06,
+    GET_SENSOR_INFO = 0x07,
     SET_ASSET_TAG = 0x08,
     GET_MGMNT_CTRL_ID_STR = 0x09,
     SET_MGMNT_CTRL_ID_STR = 0x0A,
@@ -36,6 +40,9 @@
         "xyz.openbmc_project.Network.SystemConfiguration";
 static constexpr auto hostNameProp = "HostName";
 static constexpr auto temperatureSensorType = 0x01;
+static constexpr auto maxInstances = 255;
+static constexpr auto configFile =
+    "/usr/share/ipmi-providers/dcmi_sensors.json";
 
 namespace assettag
 {
@@ -50,10 +57,7 @@
 namespace temp_readings
 {
     static constexpr auto maxDataSets = 8;
-    static constexpr auto maxInstances = 255;
     static constexpr auto maxTemp = 127; // degrees C
-    static constexpr auto configFile =
-        "/usr/share/ipmi-providers/dcmi_temp_readings.json";
 
     /** @struct Response
      *
@@ -73,13 +77,28 @@
     } __attribute__((packed));
 
     using ResponseList = std::vector<Response>;
-    using NumInstances = size_t;
     using Value = uint8_t;
     using Sign = bool;
     using Temperature = std::tuple<Value, Sign>;
-    using Json = nlohmann::json;
 }
 
+namespace sensor_info
+{
+    static constexpr auto maxRecords = 8;
+
+    /** @struct Response
+     *
+     *  DCMI payload for Get Sensor Info response
+     */
+    struct Response
+    {
+        uint8_t recordIdLsb;       //!< SDR record id LS byte
+        uint8_t recordIdMsb;       //!< SDR record id MS byte
+    } __attribute__((packed));
+
+    using ResponseList = std::vector<Response>;
+} // namespace sensor_info
+
 static constexpr auto groupExtId = 0xDC;
 
 static constexpr auto assetTagMaxOffset = 62;
@@ -381,6 +400,13 @@
     uint8_t numDataSets;            //!< No. of sets of temperature data
 } __attribute__((packed));
 
+/** @brief Parse out JSON config file containing information
+ *         related to sensors.
+ *
+ *  @return A json object
+ */
+Json parseSensorConfig();
+
 namespace temp_readings
 {
     /** @brief Read temperature from a d-bus object, scale it as per dcmi
@@ -394,13 +420,6 @@
     Temperature readTemp(const std::string& dbusService,
                          const std::string& dbusPath);
 
-    /** @brief Parse out JSON config file containing information
-     *         related to temperature readings.
-     *
-     *  @return A json object
-     */
-    Json parseConfig();
-
     /** @brief Read temperatures and fill up DCMI response for the Get
      *         Temperature Readings command. This looks at a specific
      *         instance.
@@ -428,6 +447,39 @@
                                                    uint8_t instanceStart);
 }
 
+namespace sensor_info
+{
+    /** @brief Read sensor info and fill up DCMI response for the Get
+     *         Sensor Info command. This looks at a specific
+     *         instance.
+     *
+     *  @param[in] type - one of "inlet", "cpu", "baseboard"
+     *  @param[in] instance - A non-zero Entity instance number
+     *  @param[in] config - JSON config info about DCMI sensors
+     *
+     *  @return A tuple, containing a sensor info response and
+     *          number of instances.
+     */
+    std::tuple<Response, NumInstances> read(const std::string& type,
+                                            uint8_t instance,
+                                            const Json& config);
+
+    /** @brief Read sensor info and fill up DCMI response for the Get
+     *         Sensor Info command. This looks at a range of
+     *         instances.
+     *
+     *  @param[in] type - one of "inlet", "cpu", "baseboard"
+     *  @param[in] instanceStart - Entity instance start index
+     *  @param[in] config - JSON config info about DCMI sensors
+     *
+     *  @return A tuple, containing a list of sensor info responses and the
+     *          number of instances.
+     */
+    std::tuple<ResponseList, NumInstances> readAll(const std::string& type,
+                                                   uint8_t instanceStart,
+                                                   const Json& config);
+} // namespace sensor_info
+
 /** @brief Read power reading from power reading sensor object
  *
  *  @param[in] bus - dbus connection
@@ -469,6 +521,30 @@
     uint8_t powerReadingState;  //!< Power Reading State
 } __attribute__((packed));
 
+/** @struct GetSensorInfoRequest
+ *
+ *  DCMI payload for Get Sensor Info request
+ */
+struct GetSensorInfoRequest
+{
+    uint8_t groupID;             //!< Group extension identification.
+    uint8_t sensorType;          //!< Type of the sensor
+    uint8_t entityId;            //!< Entity ID
+    uint8_t entityInstance;      //!< Entity Instance (0 means all instances)
+    uint8_t instanceStart;       //!< Instance start (used if instance is 0)
+} __attribute__((packed));
+
+/** @struct GetSensorInfoResponseHdr
+ *
+ *  DCMI header for Get Sensor Info response
+ */
+struct GetSensorInfoResponseHdr
+{
+    uint8_t groupID;                //!< Group extension identification.
+    uint8_t numInstances;           //!< No. of instances for requested id
+    uint8_t numRecords;             //!< No. of record ids in the response
+} __attribute__((packed));
+
 } // namespace dcmi
 
 #endif
diff --git a/host-ipmid-whitelist.conf b/host-ipmid-whitelist.conf
index 0c9c382..97544a0 100644
--- a/host-ipmid-whitelist.conf
+++ b/host-ipmid-whitelist.conf
@@ -31,4 +31,5 @@
 0x2C:0x02    //<Group Extension>:<Get Power Reading>
 0x2C:0x03    //<Group Extension>:<Get Power Limit>
 0x2C:0x06    //<Group Extension>:<Get Asset Tag>
+0x2C:0x07    //<Group Extension>:<Get Sensor Info>
 0x2C:0x10    //<Group Extension>:<Get Temperature Readings>