SDR: Adding fru records as part of Get SDR command

Currently Get SDR only responds with physical/virtual sensor
records,it doesn't support for FRU records,This commit adds
the support for FRU records.

Resolves openbmc/openbmc#2776

Change-Id: I34edfa892b32f4e866cf0c084d97c2f3482d40f4
Signed-off-by: Ratan Gupta <ratagupt@in.ibm.com>
diff --git a/ipmid.cpp b/ipmid.cpp
index f06adc5..a05992b 100644
--- a/ipmid.cpp
+++ b/ipmid.cpp
@@ -380,10 +380,11 @@
     if(r != 0)
     {
         fprintf(stderr,"ERROR:[0x%X] handling NetFn:[0x%X], Cmd:[0x%X]\n",r, netfn, cmd);
-
-        if(r < 0) {
-           response[0] = IPMI_CC_UNSPECIFIED_ERROR;
-        }
+        resplen = 0;
+    }
+    else
+    {
+        resplen = resplen - 1; // first byte is for return code.
     }
 
     fprintf(ipmiio, "IPMI Response:\n");
@@ -391,7 +392,7 @@
 
     // Send the response buffer from the ipmi command
     r = send_ipmi_message(m, sequence, netfn, lun, cmd, response[0],
-		    ((unsigned char *)response) + 1, resplen - 1);
+		    ((unsigned char *)response) + 1, resplen);
     if (r < 0) {
         fprintf(stderr, "Failed to send the response message\n");
         return -1;
diff --git a/sensorhandler.cpp b/sensorhandler.cpp
index 036eb22..1bd10b7 100644
--- a/sensorhandler.cpp
+++ b/sensorhandler.cpp
@@ -9,15 +9,23 @@
 #include "host-ipmid/ipmid-api.h"
 #include <phosphor-logging/log.hpp>
 #include <phosphor-logging/elog-errors.hpp>
+#include "fruread.hpp"
 #include "ipmid.hpp"
 #include "sensorhandler.h"
 #include "types.hpp"
 #include "utils.hpp"
 #include "xyz/openbmc_project/Common/error.hpp"
 
+static constexpr uint8_t fruInventoryDevice = 0x10;
+static constexpr uint8_t IPMIFruInventory = 0x02;
+static constexpr uint8_t BMCSlaveAddress = 0x20;
+
 extern int updateSensorRecordFromSSRAESC(const void *);
 extern sd_bus *bus;
 extern const ipmi::sensor::IdInfoMap sensors;
+extern const FruMap frus;
+
+
 using namespace phosphor::logging;
 using InternalFailure =
     sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
@@ -825,7 +833,7 @@
         get_sdr_info::request::get_count(request) == false)
     {
         // Get Sensor Count
-        resp->count = sensors.size();
+        resp->count = sensors.size() + frus.size();
     }
     else
     {
@@ -1009,6 +1017,97 @@
     return IPMI_CC_OK;
 };
 
+ipmi_ret_t ipmi_fru_get_sdr(ipmi_request_t request, ipmi_response_t response,
+                            ipmi_data_len_t data_len)
+{
+    auto req = reinterpret_cast<get_sdr::GetSdrReq*>(request);
+    auto resp = reinterpret_cast<get_sdr::GetSdrResp*>(response);
+    get_sdr::SensorDataFruRecord record {};
+    auto dataLength = 0;
+
+    auto fru = frus.begin();
+    uint8_t fruID {};
+    auto recordID = get_sdr::request::get_record_id(req);
+
+    fruID = recordID - FRU_RECORD_ID_START;
+    fru = frus.find(fruID);
+    if (fru == frus.end())
+    {
+        return IPMI_CC_SENSOR_INVALID;
+    }
+
+    /* Header */
+    get_sdr::header::set_record_id(recordID, &(record.header));
+    record.header.sdr_version = SDR_VERSION; // Based on IPMI Spec v2.0 rev 1.1
+    record.header.record_type = get_sdr::SENSOR_DATA_FRU_RECORD;
+    record.header.record_length = sizeof(record.key) + sizeof(record.body);
+
+    /* Key */
+    record.key.fruID = fruID;
+    record.key.accessLun |= IPMI_LOGICAL_FRU;
+    record.key.deviceAddress = BMCSlaveAddress;
+
+    /* Body */
+    record.body.entityID = fru->second[0].entityID;
+    record.body.entityInstance = fru->second[0].entityInstance;
+    record.body.deviceType = fruInventoryDevice;
+    record.body.deviceTypeModifier = IPMIFruInventory;
+
+    /* Device ID string */
+    auto deviceID = fru->second[0].path.substr(
+            fru->second[0].path.find_last_of('/') + 1,
+            fru->second[0].path.length());
+
+
+    if (deviceID.length() > get_sdr::FRU_RECORD_DEVICE_ID_MAX_LENGTH)
+    {
+        get_sdr::body::set_device_id_strlen(
+                get_sdr::FRU_RECORD_DEVICE_ID_MAX_LENGTH,
+                &(record.body));
+    }
+    else
+    {
+        get_sdr::body::set_device_id_strlen(deviceID.length(),
+                &(record.body));
+    }
+
+    strncpy(record.body.deviceID, deviceID.c_str(),
+            get_sdr::body::get_device_id_strlen(&(record.body)));
+
+    if (++fru == frus.end())
+    {
+        get_sdr::response::set_next_record_id(END_OF_RECORD, resp); // last record
+    }
+    else
+    {
+        get_sdr::response::set_next_record_id(
+                (FRU_RECORD_ID_START + fru->first), resp);
+    }
+
+    if (req->bytes_to_read > (sizeof(*resp) - req->offset))
+    {
+        dataLength = (sizeof(*resp) - req->offset);
+    }
+    else
+    {
+        dataLength =  req->bytes_to_read;
+    }
+
+    if (dataLength <= 0)
+    {
+        return IPMI_CC_REQ_DATA_LEN_INVALID;
+    }
+
+    memcpy(resp->record_data,
+            reinterpret_cast<uint8_t*>(&record) + req->offset,
+            (dataLength));
+
+    *data_len = dataLength;
+    *data_len += 2; // additional 2 bytes for next record ID
+
+    return IPMI_CC_OK;
+}
+
 ipmi_ret_t ipmi_sen_get_sdr(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)
@@ -1022,13 +1121,25 @@
         // Note: we use an iterator so we can provide the next ID at the end of
         // the call.
         auto sensor = sensors.begin();
+        auto recordID = get_sdr::request::get_record_id(req);
 
         // At the beginning of a scan, the host side will send us id=0.
-        if (get_sdr::request::get_record_id(req) != 0)
+        if (recordID != 0)
         {
-            sensor = sensors.find(get_sdr::request::get_record_id(req));
-            if(sensor == sensors.end()) {
-                return IPMI_CC_SENSOR_INVALID;
+            // recordID greater then 255,it means it is a FRU record.
+            // Currently we are supporting two record types either FULL record
+            // or FRU record.
+            if (recordID >= FRU_RECORD_ID_START)
+            {
+                return ipmi_fru_get_sdr(request, response, data_len);
+            }
+            else
+            {
+                sensor = sensors.find(recordID);
+                if (sensor == sensors.end())
+                {
+                    return IPMI_CC_SENSOR_INVALID;
+                }
             }
         }
 
@@ -1056,7 +1167,13 @@
 
         if (++sensor == sensors.end())
         {
-            get_sdr::response::set_next_record_id(0xFFFF, resp); // last record
+            // we have reached till end of sensor, so assign the next record id
+            // to 256(Max Sensor ID = 255) + FRU ID(may start with 0).
+            auto next_record_id = (frus.size()) ?
+                frus.begin()->first + FRU_RECORD_ID_START :
+                END_OF_RECORD;
+
+            get_sdr::response::set_next_record_id(next_record_id, resp);
         }
         else
         {
diff --git a/sensorhandler.h b/sensorhandler.h
index 1cf43ca..e32405d 100644
--- a/sensorhandler.h
+++ b/sensorhandler.h
@@ -16,6 +16,16 @@
     IPMI_CMD_GET_SENSOR_THRESHOLDS = 0x27,
 };
 
+/**
+ * @enum device_type
+ * IPMI FRU device types
+ */
+enum device_type
+{
+    IPMI_PHYSICAL_FRU = 0x00,
+    IPMI_LOGICAL_FRU = 0x80,
+};
+
 // Discrete sensor types.
 enum ipmi_sensor_types
 {
@@ -40,6 +50,11 @@
 int set_sensor_dbus_state_y(uint8_t , const char *, const uint8_t);
 int find_openbmc_path(uint8_t , dbus_interface_t *);
 
+static const uint16_t FRU_RECORD_ID_START = 256;
+static const uint8_t SDR_VERSION = 0x51;
+static const uint16_t END_OF_RECORD = 0xFFFF;
+static const uint8_t LENGTH_MASK = 0x1F;
+
 /**
  * Get SDR Info
  */
@@ -109,7 +124,7 @@
     return (req->reservation_id_lsb + (req->reservation_id_msb << 8));
 };
 
-inline uint8_t get_record_id(GetSdrReq* req)
+inline uint16_t get_record_id(GetSdrReq* req)
 {
     return (req->record_id_lsb + (req->record_id_msb << 8));
 };
@@ -127,7 +142,7 @@
 namespace response
 {
 
-inline void set_next_record_id(int next, GetSdrResp* resp)
+inline void set_next_record_id(uint16_t next, GetSdrResp* resp)
 {
     resp->next_record_id_lsb = next & 0xff;
     resp->next_record_id_msb = (next >> 8) & 0xff;
@@ -158,7 +173,8 @@
 
 enum SensorDataRecordType
 {
-    SENSOR_DATA_FULL_RECORD = 1,
+    SENSOR_DATA_FULL_RECORD = 0x1,
+    SENSOR_DATA_FRU_RECORD = 0x11,
 };
 
 // Record key
@@ -169,6 +185,18 @@
     uint8_t sensor_number;
 } __attribute__((packed));
 
+/** @struct SensorDataFruRecordKey
+ *
+ *  FRU Device Locator Record(key) - SDR Type 11
+ */
+struct SensorDataFruRecordKey
+{
+    uint8_t deviceAddress;
+    uint8_t fruID;
+    uint8_t accessLun;
+    uint8_t channelNumber;
+} __attribute__((packed));
+
 namespace key
 {
 
@@ -219,6 +247,9 @@
 
 // Body - full record
 #define FULL_RECORD_ID_STR_MAX_LENGTH 16
+
+static const int FRU_RECORD_DEVICE_ID_MAX_LENGTH = 16;
+
 struct SensorDataFullRecordBody
 {
     uint8_t entity_id;
@@ -260,6 +291,22 @@
     char id_string[FULL_RECORD_ID_STR_MAX_LENGTH];
 } __attribute__((packed));
 
+/** @struct SensorDataFruRecordBody
+ *
+ *  FRU Device Locator Record(body) - SDR Type 11
+ */
+struct SensorDataFruRecordBody
+{
+    uint8_t reserved;
+    uint8_t deviceType;
+    uint8_t deviceTypeModifier;
+    uint8_t entityID;
+    uint8_t entityInstance;
+    uint8_t oem;
+    uint8_t deviceIDLen;
+    char deviceID[FRU_RECORD_DEVICE_ID_MAX_LENGTH];
+} __attribute__((packed));
+
 namespace body
 {
 
@@ -464,6 +511,17 @@
     body->id_string_info |= (type & 0x3)<<6;
 };
 
+inline void set_device_id_strlen(uint8_t len, SensorDataFruRecordBody* body)
+{
+    body->deviceIDLen &= ~(LENGTH_MASK);
+    body->deviceIDLen |= len & LENGTH_MASK;
+};
+
+inline uint8_t get_device_id_strlen(SensorDataFruRecordBody* body)
+{
+    return body->deviceIDLen & LENGTH_MASK;
+};
+
 } // namespace body
 
 // More types contained in section 43.17 Sensor Unit Type Codes,
@@ -486,6 +544,17 @@
     SensorDataFullRecordBody body;
 } __attribute__((packed));
 
+/** @struct SensorDataFruRecord
+ *
+ *  FRU Device Locator Record - SDR Type 11
+ */
+struct SensorDataFruRecord
+{
+    SensorDataRecordHeader header;
+    SensorDataFruRecordKey key;
+    SensorDataFruRecordBody body;
+} __attribute__((packed));
+
 } // get_sdr
 
 namespace ipmi