dcmi : implement get temperature readings

This commit implements the plumbing around the 'Get Temperature
Readings' command. It doesn't read the temperatures from the relevant
sensors. That code is coming up in a subsequent commit.

Change-Id: I4514b65eb3ca65d52dd35185338a2c397e9a76ca
Signed-off-by: Deepak Kodihalli <dkodihal@in.ibm.com>
diff --git a/dcmihandler.cpp b/dcmihandler.cpp
index aa9f14e..e5a5384 100644
--- a/dcmihandler.cpp
+++ b/dcmihandler.cpp
@@ -731,6 +731,117 @@
     return IPMI_CC_OK;
 }
 
+namespace dcmi
+{
+namespace temp_readings
+{
+
+std::tuple<Response, NumInstances> read(const std::string& type,
+                                        uint8_t instance)
+{
+    Response empty{};
+    return std::make_tuple(empty, 0);
+}
+
+std::tuple<ResponseList, NumInstances> readAll(const std::string& type,
+                                               uint8_t instanceStart)
+{
+    ResponseList empty{};
+    return std::make_tuple(empty, 0);
+}
+
+} // namsespace temp_readings
+} // namsepace dcmi
+
+ipmi_ret_t getTempReadings(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)
+{
+    // 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 =
+        reinterpret_cast<dcmi::GetTempReadingsResponseHdr*>(response);
+
+    if (*data_len != sizeof(dcmi::GetTempReadingsRequest))
+    {
+        log<level::ERR>("Malformed request data",
+                        entry("DATA_SIZE=%d", *data_len));
+        return IPMI_CC_REQ_DATA_LEN_INVALID;
+    }
+    *data_len = 0;
+
+    auto it = entityIdToName.find(requestData->entityId);
+    if (it == 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::temp_readings::ResponseList temps{};
+    try
+    {
+        if (!requestData->entityInstance)
+        {
+            // Read all instances
+            std::tie(temps, responseData->numInstances) =
+                dcmi::temp_readings::readAll(it->second,
+                                             requestData->instanceStart);
+        }
+        else
+        {
+            // Read one instance
+            temps.resize(1);
+            std::tie(temps[0], responseData->numInstances) =
+                dcmi::temp_readings::read(it->second,
+                                          requestData->entityInstance);
+        }
+        responseData->numDataSets = temps.size();
+    }
+    catch (InternalFailure& e)
+    {
+        return IPMI_CC_UNSPECIFIED_ERROR;
+    }
+
+    responseData->groupID = dcmi::groupExtId;
+    size_t payloadSize =
+        temps.size() * sizeof(dcmi::temp_readings::Response);
+    if (!temps.empty())
+    {
+        memcpy(responseData + 1, // copy payload right after the response header
+               temps.data(),
+               payloadSize);
+    }
+    *data_len = sizeof(dcmi::GetTempReadingsResponseHdr) + payloadSize;
+
+    return IPMI_CC_OK;
+}
+
 void register_netfn_dcmi_functions()
 {
     // <Get Power Limit>
@@ -784,6 +895,11 @@
     // <Get DCMI capabilities>
     ipmi_register_callback(NETFUN_GRPEXT, dcmi::Commands::GET_CAPABILITIES,
         NULL, getDCMICapabilities, PRIVILEGE_USER);
+
+    // <Get Temperature Readings>
+    ipmi_register_callback(NETFUN_GRPEXT, dcmi::Commands::GET_TEMP_READINGS,
+                           NULL, getTempReadings, PRIVILEGE_USER);
+
     return;
 }
 // 956379
diff --git a/dcmihandler.hpp b/dcmihandler.hpp
index 288e4d7..a7cfe8a 100644
--- a/dcmihandler.hpp
+++ b/dcmihandler.hpp
@@ -20,6 +20,7 @@
     SET_ASSET_TAG = 0x08,
     GET_MGMNT_CTRL_ID_STR = 0x09,
     SET_MGMNT_CTRL_ID_STR = 0x0A,
+    GET_TEMP_READINGS = 0x10,
 };
 
 static constexpr auto propIntf = "org.freedesktop.DBus.Properties";
@@ -32,6 +33,7 @@
 static constexpr auto networkConfigIntf =
         "xyz.openbmc_project.Network.SystemConfiguration";
 static constexpr auto hostNameProp = "HostName";
+static constexpr auto temperatureSensorType = 0x01;
 
 namespace assettag
 {
@@ -43,6 +45,33 @@
 
 } //namespace assettag
 
+namespace temp_readings
+{
+    static constexpr auto maxDataSets = 8;
+    static constexpr auto maxInstances = 255;
+    static constexpr auto maxTemp = 128; // degrees C
+
+    /** @struct Response
+     *
+     *  DCMI payload for Get Temperature Readings response
+     */
+    struct Response
+    {
+#if BYTE_ORDER == LITTLE_ENDIAN
+        uint8_t temperature: 7;   //!< Temperature reading in Celsius
+        uint8_t sign: 1;          //!< Sign bit
+#endif
+#if BYTE_ORDER == BIG_ENDIAN
+        uint8_t sign: 1;          //!< Sign bit
+        uint8_t temperature: 7;   //!< Temperature reading in Celsius
+#endif
+        uint8_t instance;       //!< Entity instance number
+    } __attribute__((packed));
+
+    using ResponseList = std::vector<Response>;
+    using NumInstances = size_t;
+}
+
 static constexpr auto groupExtId = 0xDC;
 
 static constexpr auto assetTagMaxOffset = 62;
@@ -320,6 +349,59 @@
 
 using DCMICaps = std::map<DCMICapParameters, DCMICapEntry>;
 
+/** @struct GetTempReadingsRequest
+ *
+ *  DCMI payload for Get Temperature Readings request
+ */
+struct GetTempReadingsRequest
+{
+    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 GetTempReadingsResponse
+ *
+ *  DCMI header for Get Temperature Readings response
+ */
+struct GetTempReadingsResponseHdr
+{
+    uint8_t groupID;                //!< Group extension identification.
+    uint8_t numInstances;           //!< No. of instances for requested id
+    uint8_t numDataSets;            //!< No. of sets of temperature data
+} __attribute__((packed));
+
+namespace temp_readings
+{
+    /** @brief Read temperatures and fill up DCMI response for the Get
+     *         Temperature Readings command. This looks at a specific
+     *         instance.
+     *
+     *  @param[in] type - one of "inlet", "cpu", "baseboard"
+     *  @param[in] instance - A non-zero Entity instance number
+     *
+     *  @return A tuple, containing a temperature reading and the
+     *          number of instances.
+     */
+    std::tuple<Response, NumInstances> read (const std::string& type,
+                                             uint8_t instance);
+
+    /** @brief Read temperatures and fill up DCMI response for the Get
+     *         Temperature Readings command. This looks at a range of
+     *         instances.
+     *
+     *  @param[in] type - one of "inlet", "cpu", "baseboard"
+     *  @param[in] instanceStart - Entity instance start index
+     *
+     *  @return A tuple, containing a list of temperature readings and the
+     *          number of instances.
+     */
+    std::tuple<ResponseList, NumInstances> readAll(const std::string& type,
+                                                   uint8_t instanceStart);
+}
+
 } // namespace dcmi
 
 #endif
diff --git a/host-ipmid-whitelist.conf b/host-ipmid-whitelist.conf
index 941800a..f1084d0 100644
--- a/host-ipmid-whitelist.conf
+++ b/host-ipmid-whitelist.conf
@@ -30,3 +30,4 @@
 0x2C:0x01    //<Group Extension>:<Get DCMI Capabilities>
 0x2C:0x03    //<Group Extension>:<Get Power Limit>
 0x2C:0x06    //<Group Extension>:<Get Asset Tag>
+0x2C:0x10    //<Group Extension>:<Get Temperature Readings>