Add Intel-specific IPMI FRU commands
Includes commands that are required to support the 'ipmitool
fru print' command.
Change-Id: If7768de1441cc620ad6db4c99c4254305cf5bbbc
Signed-off-by: Jason M. Bills <jason.m.bills@linux.intel.com>
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 4362cc7..eb4a366 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -95,4 +95,12 @@
target_link_libraries (sensorcommands ${CMAKE_THREAD_LIBS_INIT})
target_link_libraries (sensorcommands sdbusplus)
-install (TARGETS oemcmds sensorcommands DESTINATION lib/ipmid-providers)
+add_library (zstoragecommands SHARED src/storagecommands.cpp)
+set_target_properties (zstoragecommands PROPERTIES VERSION "0.1.0")
+set_target_properties (zstoragecommands PROPERTIES SOVERSION "0")
+target_link_libraries (zstoragecommands ${CMAKE_THREAD_LIBS_INIT})
+
+install (
+ TARGETS oemcmds sensorcommands zstoragecommands
+ DESTINATION lib/ipmid-providers
+)
diff --git a/include/storagecommands.hpp b/include/storagecommands.hpp
index ef32c74..0d32ea5 100644
--- a/include/storagecommands.hpp
+++ b/include/storagecommands.hpp
@@ -66,8 +66,35 @@
uint8_t allocUnitLargestFreeMSB;
uint8_t maxRecordSize;
};
+
+struct GetFRUAreaReq
+{
+ uint8_t fruDeviceID;
+ uint16_t fruInventoryOffset;
+ uint8_t countToRead;
+};
+
+struct GetFRUAreaResp
+{
+ uint8_t inventorySizeLSB;
+ uint8_t inventorySizeMSB;
+ uint8_t accessType;
+};
+
+struct WriteFRUDataReq
+{
+ uint8_t fruDeviceID;
+ uint16_t fruInventoryOffset;
+ uint8_t data[];
+};
#pragma pack(pop)
+enum class GetFRUAreaAccessType : uint8_t
+{
+ byte = 0x0,
+ words = 0x1
+};
+
enum class SensorTypeCodes : uint8_t
{
reserved = 0x0,
@@ -107,6 +134,20 @@
ipmiCmdSetSELTime = 0x49,
};
+#pragma pack(push, 1)
+struct FRUHeader
+{
+ uint8_t commonHeaderFormat;
+ uint8_t internalOffset;
+ uint8_t chassisOffset;
+ uint8_t boardOffset;
+ uint8_t productOffset;
+ uint8_t multiRecordOffset;
+ uint8_t pad;
+ uint8_t checksum;
+};
+#pragma pack(pop)
+
namespace ipmi
{
namespace storage
diff --git a/src/storagecommands.cpp b/src/storagecommands.cpp
index e71d47f..6a8da91 100644
--- a/src/storagecommands.cpp
+++ b/src/storagecommands.cpp
@@ -30,6 +30,7 @@
namespace storage
{
+constexpr static const size_t maxMessageSize = 64;
constexpr static const size_t maxFruSdrNameSize = 16;
using ManagedObjectType = boost::container::flat_map<
sdbusplus::message::object_path,
@@ -41,6 +42,7 @@
std::string, boost::container::flat_map<std::string, DbusVariant>>>;
constexpr static const char* fruDeviceServiceName = "com.intel.FruDevice";
+constexpr static const size_t cacheTimeoutSeconds = 10;
static std::vector<uint8_t> fruCache;
static uint8_t cacheBus = 0xFF;
@@ -52,6 +54,7 @@
// collision to verify our dev-id
boost::container::flat_map<uint8_t, std::pair<uint8_t, uint8_t>> deviceHashes;
+void registerStorageFunctions() __attribute__((constructor));
static sdbusplus::bus::bus dbus(ipmid_get_sd_bus_connection());
bool writeFru()
@@ -74,6 +77,14 @@
return true;
}
+void createTimer()
+{
+ if (cacheTimer == nullptr)
+ {
+ cacheTimer = std::make_unique<phosphor::Timer>(writeFru);
+ }
+}
+
ipmi_ret_t replaceCacheFru(uint8_t devId)
{
static uint8_t lastDevId = 0xFF;
@@ -194,6 +205,179 @@
return IPMI_CC_OK;
}
+ipmi_ret_t ipmiStorageReadFRUData(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
+ ipmi_request_t request,
+ ipmi_response_t response,
+ ipmi_data_len_t dataLen,
+ ipmi_context_t context)
+{
+ if (*dataLen != 4)
+ {
+ *dataLen = 0;
+ return IPMI_CC_REQ_DATA_LEN_INVALID;
+ }
+ *dataLen = 0; // default to 0 in case of an error
+
+ auto req = static_cast<GetFRUAreaReq*>(request);
+
+ if (req->countToRead > maxMessageSize - 1)
+ {
+ return IPMI_CC_INVALID_FIELD_REQUEST;
+ }
+ ipmi_ret_t status = replaceCacheFru(req->fruDeviceID);
+
+ if (status != IPMI_CC_OK)
+ {
+ return status;
+ }
+
+ size_t fromFRUByteLen = 0;
+ if (req->countToRead + req->fruInventoryOffset < fruCache.size())
+ {
+ fromFRUByteLen = req->countToRead;
+ }
+ else if (fruCache.size() > req->fruInventoryOffset)
+ {
+ fromFRUByteLen = fruCache.size() - req->fruInventoryOffset;
+ }
+ size_t padByteLen = req->countToRead - fromFRUByteLen;
+ uint8_t* respPtr = static_cast<uint8_t*>(response);
+ *respPtr = req->countToRead;
+ std::copy(fruCache.begin() + req->fruInventoryOffset,
+ fruCache.begin() + req->fruInventoryOffset + fromFRUByteLen,
+ ++respPtr);
+ // if longer than the fru is requested, fill with 0xFF
+ if (padByteLen)
+ {
+ respPtr += fromFRUByteLen;
+ std::fill(respPtr, respPtr + padByteLen, 0xFF);
+ }
+ *dataLen = fromFRUByteLen + 1;
+
+ return IPMI_CC_OK;
+}
+
+ipmi_ret_t ipmiStorageWriteFRUData(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
+ ipmi_request_t request,
+ ipmi_response_t response,
+ ipmi_data_len_t dataLen,
+ ipmi_context_t context)
+{
+ if (*dataLen < 4 ||
+ *dataLen >=
+ 0xFF + 3) // count written return is one byte, so limit to one byte
+ // of data after the three request data bytes
+ {
+ *dataLen = 0;
+ return IPMI_CC_REQ_DATA_LEN_INVALID;
+ }
+
+ auto req = static_cast<WriteFRUDataReq*>(request);
+ size_t writeLen = *dataLen - 3;
+ *dataLen = 0; // default to 0 in case of an error
+
+ ipmi_ret_t status = replaceCacheFru(req->fruDeviceID);
+ if (status != IPMI_CC_OK)
+ {
+ return status;
+ }
+ int lastWriteAddr = req->fruInventoryOffset + writeLen;
+ if (fruCache.size() < lastWriteAddr)
+ {
+ fruCache.resize(req->fruInventoryOffset + writeLen);
+ }
+
+ std::copy(req->data, req->data + writeLen,
+ fruCache.begin() + req->fruInventoryOffset);
+
+ bool atEnd = false;
+
+ if (fruCache.size() >= sizeof(FRUHeader))
+ {
+
+ FRUHeader* header = reinterpret_cast<FRUHeader*>(fruCache.data());
+
+ int lastRecordStart = std::max(
+ header->internalOffset,
+ std::max(header->chassisOffset,
+ std::max(header->boardOffset, header->productOffset)));
+ // TODO: Handle Multi-Record FRUs?
+
+ lastRecordStart *= 8; // header starts in are multiples of 8 bytes
+
+ // get the length of the area in multiples of 8 bytes
+ if (lastWriteAddr > (lastRecordStart + 1))
+ {
+ // second byte in record area is the length
+ int areaLength(fruCache[lastRecordStart + 1]);
+ areaLength *= 8; // it is in multiples of 8 bytes
+
+ if (lastWriteAddr >= (areaLength + lastRecordStart))
+ {
+ atEnd = true;
+ }
+ }
+ }
+ uint8_t* respPtr = static_cast<uint8_t*>(response);
+ if (atEnd)
+ {
+ // cancel timer, we're at the end so might as well send it
+ cacheTimer->stop();
+ if (!writeFru())
+ {
+ return IPMI_CC_INVALID_FIELD_REQUEST;
+ }
+ *respPtr = std::min(fruCache.size(), static_cast<size_t>(0xFF));
+ }
+ else
+ {
+ // start a timer, if no further data is sent in cacheTimeoutSeconds
+ // seconds, check to see if it is valid
+ createTimer();
+ cacheTimer->start(std::chrono::duration_cast<std::chrono::microseconds>(
+ std::chrono::seconds(cacheTimeoutSeconds)));
+ *respPtr = 0;
+ }
+
+ *dataLen = 1;
+
+ return IPMI_CC_OK;
+}
+
+ipmi_ret_t ipmiStorageGetFRUInvAreaInfo(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
+ ipmi_request_t request,
+ ipmi_response_t response,
+ ipmi_data_len_t dataLen,
+ ipmi_context_t context)
+{
+ if (*dataLen != 1)
+ {
+ *dataLen = 0;
+ return IPMI_CC_REQ_DATA_LEN_INVALID;
+ }
+ *dataLen = 0; // default to 0 in case of an error
+
+ uint8_t reqDev = *(static_cast<uint8_t*>(request));
+ if (reqDev == 0xFF)
+ {
+ return IPMI_CC_INVALID_FIELD_REQUEST;
+ }
+ ipmi_ret_t status = replaceCacheFru(reqDev);
+
+ if (status != IPMI_CC_OK)
+ {
+ return status;
+ }
+
+ GetFRUAreaResp* respPtr = static_cast<GetFRUAreaResp*>(response);
+ respPtr->inventorySizeLSB = fruCache.size() & 0xFF;
+ respPtr->inventorySizeMSB = fruCache.size() >> 8;
+ respPtr->accessType = static_cast<uint8_t>(GetFRUAreaAccessType::byte);
+
+ *dataLen = sizeof(GetFRUAreaResp);
+ return IPMI_CC_OK;
+}
+
ipmi_ret_t getFruSdrCount(size_t& count)
{
ipmi_ret_t ret = replaceCacheFru(0);
@@ -311,5 +495,26 @@
return IPMI_CC_OK;
}
+
+void registerStorageFunctions()
+{
+ // <Get FRU Inventory Area Info>
+ ipmiPrintAndRegister(
+ NETFUN_STORAGE,
+ static_cast<ipmi_cmd_t>(IPMINetfnStorageCmds::ipmiCmdGetFRUInvAreaInfo),
+ NULL, ipmiStorageGetFRUInvAreaInfo, PRIVILEGE_OPERATOR);
+
+ // <Add READ FRU Data
+ ipmiPrintAndRegister(
+ NETFUN_STORAGE,
+ static_cast<ipmi_cmd_t>(IPMINetfnStorageCmds::ipmiCmdReadFRUData), NULL,
+ ipmiStorageReadFRUData, PRIVILEGE_OPERATOR);
+
+ // <Add WRITE FRU Data
+ ipmiPrintAndRegister(
+ NETFUN_STORAGE,
+ static_cast<ipmi_cmd_t>(IPMINetfnStorageCmds::ipmiCmdWriteFRUData),
+ NULL, ipmiStorageWriteFRUData, PRIVILEGE_OPERATOR);
+}
} // namespace storage
} // namespace ipmi
\ No newline at end of file